├── .expeditor ├── config.yml ├── update_version.sh └── verify.pipeline.yml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── BUG_TEMPLATE.md │ ├── DESIGN_PROPOSAL.md │ ├── ENHANCEMENT_REQUEST_TEMPLATE.md │ └── SUPPORT_QUESTION.md └── dependabot.yml ├── .gitignore ├── .rubocop.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── artifactory.gemspec ├── lib ├── artifactory.rb └── artifactory │ ├── client.rb │ ├── collections │ ├── artifact.rb │ ├── base.rb │ └── build.rb │ ├── configurable.rb │ ├── defaults.rb │ ├── errors.rb │ ├── resources │ ├── artifact.rb │ ├── backup.rb │ ├── base.rb │ ├── build.rb │ ├── build_component.rb │ ├── certificate.rb │ ├── group.rb │ ├── layout.rb │ ├── ldap_setting.rb │ ├── mail_server.rb │ ├── permission_target.rb │ ├── plugin.rb │ ├── repository.rb │ ├── system.rb │ ├── url_base.rb │ └── user.rb │ ├── util.rb │ └── version.rb └── spec ├── integration └── resources │ ├── artifact_spec.rb │ ├── backup.rb │ ├── build_component_spec.rb │ ├── build_spec.rb │ ├── certificate_spec.rb │ ├── group_spec.rb │ ├── layout_spec.rb │ ├── ldap_setting_spec.rb │ ├── mail_server_spec.rb │ ├── permission_target_spec.rb │ ├── repository_spec.rb │ ├── system_spec.rb │ ├── url_base_spec.rb │ └── user_spec.rb ├── spec_helper.rb ├── support ├── api_server.rb └── api_server │ ├── artifact_endpoints.rb │ ├── build_component_endpoints.rb │ ├── build_endpoints.rb │ ├── group_endpoints.rb │ ├── permission_target_endpoints.rb │ ├── repository_endpoints.rb │ ├── status_endpoints.rb │ ├── system_endpoints.rb │ └── user_endpoints.rb └── unit ├── artifactory_spec.rb ├── client_spec.rb └── resources ├── artifact_spec.rb ├── backup_spec.rb ├── base_spec.rb ├── build_component_spec.rb ├── build_spec.rb ├── certificate_spec.rb ├── defaults_spec.rb ├── group_spec.rb ├── layout_spec.rb ├── ldap_setting_spec.rb ├── mail_server_spec.rb ├── permission_target_spec.rb ├── plugin_spec.rb ├── repository_spec.rb ├── system_spec.rb ├── url_base_spec.rb └── user_spec.rb /.expeditor/config.yml: -------------------------------------------------------------------------------- 1 | # Documentation available at https://expeditor.chef.io/docs/getting-started/ 2 | --- 3 | # Slack channel in Chef Software slack to send notifications about build failures, etc 4 | slack: 5 | notify_channel: chef-notify 6 | 7 | # This publish is triggered by the `built_in:publish_rubygems` artifact_action. 8 | rubygems: 9 | - artifactory 10 | 11 | github: 12 | # This deletes the GitHub PR branch after successfully merged into the release branch 13 | delete_branch_on_merge: true 14 | # The tag format to use (e.g. v1.0.0) 15 | version_tag_format: "v{{version}}" 16 | # allow bumping the minor release via label 17 | minor_bump_labels: 18 | - "Expeditor: Bump Version Minor" 19 | # allow bumping the major release via label 20 | major_bump_labels: 21 | - "Expeditor: Bump Version Major" 22 | 23 | changelog: 24 | rollup_header: Changes not yet released to rubygems.org 25 | 26 | subscriptions: 27 | # These actions are taken, in order they are specified, anytime a Pull Request is merged. 28 | - workload: pull_request_merged:{{github_repo}}:{{release_branch}}:* 29 | actions: 30 | - built_in:bump_version: 31 | ignore_labels: 32 | - "Expeditor: Skip Version Bump" 33 | - "Expeditor: Skip All" 34 | - bash:.expeditor/update_version.sh: 35 | only_if: built_in:bump_version 36 | - built_in:update_changelog: 37 | ignore_labels: 38 | - "Expeditor: Skip Changelog" 39 | - "Expeditor: Skip All" 40 | - built_in:build_gem: 41 | only_if: built_in:bump_version 42 | 43 | - workload: project_promoted:{{agent_id}}:* 44 | actions: 45 | - built_in:rollover_changelog 46 | - built_in:publish_rubygems 47 | 48 | pipelines: 49 | - verify: 50 | description: Pull Request validation tests 51 | public: true 52 | -------------------------------------------------------------------------------- /.expeditor/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # After a PR merge, Chef Expeditor will bump the PATCH version in the VERSION file. 4 | # It then executes this file to update any other files/components with that new version. 5 | # 6 | 7 | set -evx 8 | 9 | sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"$(cat VERSION)\"/" lib/artifactory/version.rb 10 | 11 | # Once Expeditor finshes executing this script, it will commit the changes and push 12 | # the commit as a new tag corresponding to the value in the VERSION file. 13 | -------------------------------------------------------------------------------- /.expeditor/verify.pipeline.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | 3 | - label: run-lint-and-specs-ruby-2.4 4 | command: 5 | - bundle config set --local without docs debug 6 | - bundle install --jobs=7 --retry=3 7 | - bundle exec rake 8 | expeditor: 9 | executor: 10 | docker: 11 | image: ruby:2.4-buster 12 | 13 | - label: run-lint-and-specs-ruby-2.5 14 | command: 15 | - bundle config set --local without docs debug 16 | - bundle install --jobs=7 --retry=3 17 | - bundle exec rake 18 | expeditor: 19 | executor: 20 | docker: 21 | image: ruby:2.5-buster 22 | 23 | - label: run-lint-and-specs-ruby-2.6 24 | command: 25 | - bundle config set --local without docs debug 26 | - bundle install --jobs=7 --retry=3 27 | - bundle exec rake 28 | expeditor: 29 | executor: 30 | docker: 31 | image: ruby:2.6-buster 32 | 33 | - label: run-lint-and-specs-ruby-2.7 34 | command: 35 | - bundle config set --local without docs debug 36 | - bundle install --jobs=7 --retry=3 37 | - bundle exec rake 38 | expeditor: 39 | executor: 40 | docker: 41 | image: ruby:2.7-buster 42 | 43 | - label: run-lint-and-specs-windows 44 | command: 45 | - bundle config set --local without docs debug 46 | - bundle install --jobs=7 --retry=3 47 | - bundle exec rake 48 | expeditor: 49 | executor: 50 | docker: 51 | host_os: windows -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Order is important. The last matching pattern has the most precedence. 2 | 3 | * @chef/jex-team 4 | README.md @chef/docs-team 5 | RELEASE_NOTES.md @chef/docs-team 6 | .github/ISSUE_TEMPLATE/** @chef/docs-team 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: � Bug Report 3 | about: If something isn't working as expected �. 4 | labels: "Status: Untriaged" 5 | --- 6 | 7 | # Version: 8 | 9 | [Version of the project installed] 10 | 11 | # Environment: 12 | 13 | [Details about the environment such as the Operating System, cookbook details, etc...] 14 | 15 | # Scenario: 16 | 17 | [What you are trying to achieve and you can't?] 18 | 19 | # Steps to Reproduce: 20 | 21 | [If you are filing an issue what are the things we need to do in order to repro your problem?] 22 | 23 | # Expected Result: 24 | 25 | [What are you expecting to happen as the consequence of above reproduction steps?] 26 | 27 | # Actual Result: 28 | 29 | [What actually happens after the reproduction steps?] 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/DESIGN_PROPOSAL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Design Proposal 3 | about: I have a significant change I would like to propose and discuss before starting 4 | labels: "Status: Untriaged" 5 | --- 6 | 7 | ### When a Change Needs a Design Proposal 8 | 9 | A design proposal should be opened any time a change meets one of the following qualifications: 10 | 11 | - Significantly changes the user experience of a project in a way that impacts users. 12 | - Significantly changes the underlying architecture of the project in a way that impacts other developers. 13 | - Changes the development or testing process of the project such as a change of CI systems or test frameworks. 14 | 15 | ### Why We Use This Process 16 | 17 | - Allows all interested parties (including any community member) to discuss large impact changes to a project. 18 | - Serves as a durable paper trail for discussions regarding project architecture. 19 | - Forces design discussions to occur before PRs are created. 20 | - Reduces PR refactoring and rejected PRs. 21 | 22 | --- 23 | 24 | 25 | 26 | ## Motivation 27 | 28 | 33 | 34 | ## Specification 35 | 36 | 37 | 38 | ## Downstream Impact 39 | 40 | 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ENHANCEMENT_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Enhancement Request 3 | about: I have a suggestion (and may want to implement it 🙂)! 4 | labels: "Status: Untriaged" 5 | --- 6 | 7 | ### Describe the Enhancement: 8 | 9 | 10 | ### Describe the Need: 11 | 12 | 13 | ### Current Alternative 14 | 15 | 16 | ### Can We Help You Implement This?: 17 | 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🤗 Support Question 3 | about: If you have a question 💬, please check out our Slack! 4 | --- 5 | 6 | We use GitHub issues to track bugs and feature requests. If you need help please post to our Mailing List or join the Chef Community Slack. 7 | 8 | * Chef Community Slack at http://community-slack.chef.io/. 9 | * Chef Mailing List https://discourse.chef.io/ 10 | 11 | Support issues opened here will be closed and redirected to Slack or Discourse. 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "06:00" 8 | timezone: America/Los_Angeles 9 | open-pull-requests-limit: 10 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *# 3 | .#* 4 | \#*# 5 | .*.sw[a-z] 6 | *.un~ 7 | *.tmp 8 | *.bk 9 | *.bkup 10 | .DS_Store 11 | .kitchen.local.yml 12 | .rspec 13 | Berksfile.lock 14 | Gemfile.lock 15 | 16 | .bundle/ 17 | .cache/ 18 | .kitchen/ 19 | .vagrant/ 20 | .vagrant.d/ 21 | .yardoc/ 22 | bin/ 23 | coverage/ 24 | doc/ 25 | pkg/ 26 | tmp/ 27 | vendor/ 28 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # these should all probably get resolved 2 | 3 | Layout/HashAlignment: 4 | EnforcedHashRocketStyle: table 5 | EnforcedColonStyle: table 6 | 7 | Lint/ShadowingOuterLocalVariable: 8 | Exclude: 9 | - 'lib/artifactory/resources/base.rb' 10 | 11 | Lint/UselessAssignment: 12 | Exclude: 13 | - 'lib/artifactory/resources/build_component.rb' 14 | - 'lib/artifactory/resources/repository.rb' 15 | - 'lib/artifactory/resources/user.rb' 16 | - 'spec/integration/resources/artifact_spec.rb' 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Artifactory Client CHANGELOG 2 | ============================ 3 | This file is used to document the changes between releases of the Artifactory 4 | Ruby client. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ## [v3.0.17](https://github.com/chef/artifactory-client/tree/v3.0.17) (2024-03-04) 14 | 15 | #### Merged Pull Requests 16 | - Fix verify pipeline [#145](https://github.com/chef/artifactory-client/pull/145) ([jayashrig158](https://github.com/jayashrig158)) 17 | - Upgrade to GitHub-native Dependabot [#140](https://github.com/chef/artifactory-client/pull/140) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot])) 18 | 19 | 20 | ## [v3.0.5](https://github.com/chef/artifactory-client/tree/v3.0.5) (2024-03-04) 21 | 22 | ## [v3.0.15](https://github.com/chef/artifactory-client/tree/v3.0.15) (2020-05-29) 23 | 24 | #### Merged Pull Requests 25 | - Follow redirects in client.get [#132](https://github.com/chef/artifactory-client/pull/132) ([jgitlin-p21](https://github.com/jgitlin-p21)) 26 | 27 | ## [v3.0.13](https://github.com/chef/artifactory-client/tree/v3.0.13) (2020-05-15) 28 | 29 | #### Merged Pull Requests 30 | - Update Rubocop and fix deprecated URI methods [#130](https://github.com/chef/artifactory-client/pull/130) ([tduffield](https://github.com/tduffield)) 31 | 32 | ## [v3.0.12](https://github.com/chef/artifactory-client/tree/v3.0.12) (2019-12-31) 33 | 34 | #### Merged Pull Requests 35 | - Update rspec-mocks requirement from 3.4.0 to 3.9.0 [#121](https://github.com/chef/artifactory-client/pull/121) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot])) 36 | - Update webmock requirement from ~> 2.3 to ~> 3.7 [#120](https://github.com/chef/artifactory-client/pull/120) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot])) 37 | - Update README.md [#117](https://github.com/chef/artifactory-client/pull/117) ([johnlabarge](https://github.com/johnlabarge)) 38 | - Use require_relative for link and remove appveyor links [#123](https://github.com/chef/artifactory-client/pull/123) ([tas50](https://github.com/tas50)) 39 | - Test on Ruby 2.7 and use buster containers [#124](https://github.com/chef/artifactory-client/pull/124) ([tas50](https://github.com/tas50)) 40 | - Resolve all chefstyle warnings [#125](https://github.com/chef/artifactory-client/pull/125) ([tas50](https://github.com/tas50)) 41 | - Remove rainbow test constraint in the gemfile [#126](https://github.com/chef/artifactory-client/pull/126) ([tas50](https://github.com/tas50)) 42 | 43 | ## [v3.0.5](https://github.com/chef/artifactory-client/tree/v3.0.5) (2019-07-25) 44 | 45 | #### Merged Pull Requests 46 | - Added empty string if success response body is nil [#99](https://github.com/chef/artifactory-client/pull/99) ([APayden](https://github.com/APayden)) 47 | - Setup PR verification with Buildkite [#113](https://github.com/chef/artifactory-client/pull/113) ([tas50](https://github.com/tas50)) 48 | - Remove Travis CI pull request testing [#114](https://github.com/chef/artifactory-client/pull/114) ([tas50](https://github.com/tas50)) 49 | - Replace AppVeyor with Buildkite as well [#115](https://github.com/chef/artifactory-client/pull/115) ([tas50](https://github.com/tas50)) 50 | - Add support for managing SSL/TLS certificates [#110](https://github.com/chef/artifactory-client/pull/110) ([bodgit](https://github.com/bodgit)) 51 | 52 | ## [v3.0.0](https://github.com/chef/artifactory-client/tree/v3.0.0) (2018-12-12) 53 | 54 | #### Merged Pull Requests 55 | - Require Ruby 2.3+ and update Travis / Appveyor config [#101](https://github.com/chef/artifactory-client/pull/101) ([tas50](https://github.com/tas50)) 56 | - Add code of conduct and a contributing doc [#103](https://github.com/chef/artifactory-client/pull/103) ([tas50](https://github.com/tas50)) 57 | - Update the maintainer [#104](https://github.com/chef/artifactory-client/pull/104) ([tas50](https://github.com/tas50)) 58 | - Don't ship the test / development files in the gem artifact [#105](https://github.com/chef/artifactory-client/pull/105) ([tas50](https://github.com/tas50)) 59 | - Bump copyrights and bump to 3.0 [#106](https://github.com/chef/artifactory-client/pull/106) ([tas50](https://github.com/tas50)) 60 | 61 | v2.8.2 (06-14-2017) 62 | ------------------- 63 | - Properly parse empty response bodies 64 | 65 | v2.8.1 (03-21-2017) 66 | ------------------- 67 | - Allow downloading of large files. Fixes #83. 68 | 69 | v2.8.0 (03-17-2017) 70 | ------------------- 71 | - Include statuses in Build resource 72 | 73 | v2.7.0 (02-21-2017) 74 | ------------------- 75 | - Include statuses in Build resource 76 | 77 | v2.6.0 (02-02-2017) 78 | ------------------- 79 | - Add API Key authentication 80 | - Add new Ruby versions to test matrix 81 | - Add ChefStyle for linting 82 | 83 | v2.5.2 (01-27-2017) 84 | ------------------- 85 | - Update tests to run properly on Windows 86 | - Begin testing PRs with Appveyor 87 | - Ensure URI from artifacts are escaped 88 | 89 | v2.5.1 (11-10-2016) 90 | ------------------- 91 | - Ensure `Artifact#Upload_from_archive` returns empty response 92 | - Additional test coverage 93 | 94 | v2.5.0 (09-15-2016) 95 | ------------------- 96 | - Add support for extended YUM repo attributes 97 | 98 | v2.4.0 (09-13-2016) 99 | ------------------- 100 | - Coerce `ARTIFACTORY_READ_TIMEOUT` value to integer 101 | - Add url attribute to support remote repositories 102 | 103 | v2.3.3 (06-27-2016) 104 | ------------------- 105 | - Artifactory 4 requires setting package type during repository creation 106 | 107 | v2.3.2 (11-20-2015) 108 | ------------------- 109 | - Fix embedded requests when endpoint has a non-empty path 110 | 111 | v2.3.1 (11-13-2015) 112 | ------------------- 113 | - Ensure embedded requests respect configured settings 114 | 115 | v2.3.0 (08-04-2015) 116 | ------------------- 117 | - Support for Build endpoints 118 | 119 | v2.2.1 (12-16-2014) 120 | ------------------- 121 | - provide data to post in `Artifact#copy_or_move` 122 | - pass correct variable to redirect request in `Client#request` 123 | - use CGI escape to encode data values 124 | - when checksums are available, upload using the checksum headers. 125 | 126 | v2.2.0 (11-20-2014) 127 | ------------------- 128 | - Add artifact usage search 129 | - Add artifact creation search 130 | - Add support for configuring permission targets 131 | 132 | v2.1.3 (08-29-2014) 133 | ------------------- 134 | - CGI escape matrix properties 135 | 136 | v2.1.2 (08-26-2014) 137 | ------------------- 138 | - Use the proper REST verbs on various resources to prevent a bug 139 | 140 | v2.1.1 (08-25-2014) 141 | ------------------- 142 | - Use the proper name for POM consistency checks 143 | - Use the proper name for checksum policies 144 | - Use the proper name for max unique snapshots 145 | 146 | **Other Changes** 147 | - Improve integration test coverage 148 | - Enable running integration tests in `Rakefile` 149 | 150 | v2.1.0 (08-21-2014) 151 | ------------------- 152 | - Add `Content-Size` header 153 | - Expose `read_timeout` as a configurable (defaulting to 120 seconds) 154 | 155 | v2.0.0 (07-15-2014) 156 | ------------------- 157 | **Breaking Changes** 158 | - Change the airity of uploading an Artifact 159 | - Automatically upload checksum files during Artifact upload 160 | 161 | **Other Changes** 162 | - Bump to RSpec 3 163 | - Add support for configuring the Backup resource 164 | - Add support for configuring the LDAPSetting resource 165 | - Add support for configuring the MailServer resource 166 | - Add support for configuring the URLBase resource 167 | - Set `Transfer-Encoding` to "chunked" 168 | - Do not swallow returned errors from the server 169 | 170 | v1.2.0 (2014-06-02) 171 | ------------------- 172 | - Change the airty of Repository#find to align with other resources 173 | - Add the ability to save/create repositories 174 | - Remove i18n 175 | - Make proxy configuration more verbose 176 | - Remove HTTPClient in favor of raw Net::HTTP 177 | - Add custom SSL configuration options 178 | - Make Configurable#proxy_port a string because #ocd 179 | - Allow file uploads 180 | - Return an Artifact object after uploading 181 | - Allow repositories to be deleted 182 | - Add required attribute for repository layout 183 | - Move upload method from Repository to Artifact 184 | - Implement Repository.upload via Artifact.upload 185 | - Move to_matrix_properties method to base class 186 | - Add the ability to list/find repository layouts from xml configuration 187 | - Specify content-type for updating config 188 | 189 | v1.1.0 (2014-02-11) 190 | ------------------- 191 | - Add support for group resources 192 | - Add support for user resources 193 | 194 | v1.0.0 (2014-02-10) 195 | ------------------- 196 | - Initial release -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Please refer to the Chef Community Code of Conduct at https://www.chef.io/code-of-conduct/ 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to https://github.com/chef/chef/blob/master/CONTRIBUTING.md 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | 4 | group :test do 5 | gem "chefstyle" 6 | gem "sinatra", "~> 1.4" 7 | gem "rspec", "~> 3.0" 8 | gem "webmock", "~> 3.7" 9 | gem "rspec-mocks", "3.9.0" 10 | gem "simplecov", "~> 0.18.5" # Pin until we drop ruby support 2.4 11 | gem "simplecov-console" 12 | gem "rake" 13 | gem "parallel", "~> 1.20.1" # Pin until we drop ruby support 2.4 14 | gem "rubocop-ast", "= 1.4.1" # Pin until we drop ruby support 2.4 15 | gem "docile", "= 1.3.5" # Pin until we drop ruby support 2.4 16 | end 17 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | require "rspec/core/rake_task" 4 | RSpec::Core::RakeTask.new(:integration) do |t| 5 | t.rspec_opts = "--tag integration" 6 | end 7 | RSpec::Core::RakeTask.new(:unit) do |t| 8 | t.rspec_opts = "--tag ~integration" 9 | end 10 | 11 | begin 12 | require "chefstyle" 13 | require "rubocop/rake_task" 14 | desc "Run Chefstyle tests" 15 | RuboCop::RakeTask.new(:style) do |task| 16 | task.options += ["--display-cop-names", "--no-color"] 17 | end 18 | rescue LoadError 19 | puts "chefstyle gem is not installed. bundle install first to make sure all dependencies are installed." 20 | end 21 | 22 | begin 23 | require "yard" 24 | YARD::Rake::YardocTask.new(:docs) 25 | rescue LoadError 26 | puts "yard is not available. bundle install first to make sure all dependencies are installed." 27 | end 28 | 29 | desc "Generate coverage report" 30 | RSpec::Core::RakeTask.new(:coverage) do |t| 31 | ENV["COVERAGE"] = "true" 32 | end 33 | 34 | task default: %w{style unit integration} 35 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 3.0.17 -------------------------------------------------------------------------------- /artifactory.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("lib", __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require "artifactory/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "artifactory" 7 | spec.version = Artifactory::VERSION 8 | spec.author = "Chef Release Engineering Team" 9 | spec.email = "releng@chef.io" 10 | spec.description = "A Ruby client for Artifactory" 11 | spec.summary = "Artifactory is a simple, lightweight Ruby client for " \ 12 | "interacting with the Artifactory and Artifactory Pro " \ 13 | "APIs." 14 | spec.homepage = "https://github.com/chef/artifactory-client" 15 | spec.license = "Apache-2.0" 16 | 17 | spec.files = %w{LICENSE} + Dir.glob("lib/**/*") 18 | spec.require_paths = ["lib"] 19 | 20 | spec.required_ruby_version = ">= 2.3" 21 | end 22 | -------------------------------------------------------------------------------- /lib/artifactory.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "pathname" unless defined?(Pathname) 18 | require_relative "artifactory/version" 19 | 20 | module Artifactory 21 | autoload :Client, "artifactory/client" 22 | autoload :Configurable, "artifactory/configurable" 23 | autoload :Defaults, "artifactory/defaults" 24 | autoload :Error, "artifactory/errors" 25 | autoload :Util, "artifactory/util" 26 | 27 | module Collection 28 | autoload :Artifact, "artifactory/collections/artifact" 29 | autoload :Base, "artifactory/collections/base" 30 | autoload :Build, "artifactory/collections/build" 31 | end 32 | 33 | module Resource 34 | autoload :Artifact, "artifactory/resources/artifact" 35 | autoload :Backup, "artifactory/resources/backup" 36 | autoload :Base, "artifactory/resources/base" 37 | autoload :Build, "artifactory/resources/build" 38 | autoload :BuildComponent, "artifactory/resources/build_component" 39 | autoload :Certificate, "artifactory/resources/certificate" 40 | autoload :Group, "artifactory/resources/group" 41 | autoload :Layout, "artifactory/resources/layout" 42 | autoload :LDAPSetting, "artifactory/resources/ldap_setting" 43 | autoload :MailServer, "artifactory/resources/mail_server" 44 | autoload :PermissionTarget, "artifactory/resources/permission_target" 45 | autoload :Plugin, "artifactory/resources/plugin" 46 | autoload :Repository, "artifactory/resources/repository" 47 | autoload :System, "artifactory/resources/system" 48 | autoload :URLBase, "artifactory/resources/url_base" 49 | autoload :User, "artifactory/resources/user" 50 | end 51 | 52 | class << self 53 | include Artifactory::Configurable 54 | 55 | # 56 | # The root of the Artifactory gem. This method is useful for finding files 57 | # relative to the root of the repository. 58 | # 59 | # @return [Pathname] 60 | # 61 | def root 62 | @root ||= Pathname.new(File.expand_path("..", __dir__)) 63 | end 64 | 65 | # 66 | # API client object based off the configured options in {Configurable}. 67 | # 68 | # @return [Artifactory::Client] 69 | # 70 | def client 71 | unless defined?(@client) && @client.same_options?(options) 72 | @client = Artifactory::Client.new(options) 73 | end 74 | 75 | @client 76 | end 77 | 78 | # 79 | # Delegate all methods to the client object, essentially making the module 80 | # object behave like a {Client}. 81 | # 82 | def method_missing(m, *args, &block) 83 | if client.respond_to?(m) 84 | client.send(m, *args, &block) 85 | else 86 | super 87 | end 88 | end 89 | 90 | # 91 | # Delegating +respond_to+ to the {Client}. 92 | # 93 | def respond_to_missing?(m, include_private = false) 94 | client.respond_to?(m) || super 95 | end 96 | end 97 | end 98 | 99 | # Load the initial default values 100 | Artifactory.setup 101 | -------------------------------------------------------------------------------- /lib/artifactory/collections/artifact.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | class Collection::Artifact < Collection::Base 19 | # 20 | # Create a new artifact collection. 21 | # 22 | # @param (see Collection::Base#initialize) 23 | # 24 | def initialize(parent, options = {}, &block) 25 | super(Resource::Artifact, parent, options, &block) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/artifactory/collections/base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | class Collection::Base 19 | # 20 | # Create a new collection object (proxy). 21 | # 22 | # @param [Class] klass 23 | # the child class object 24 | # @param [Object] parent 25 | # the parent object who created the collection 26 | # @param [Hash] options 27 | # the list of options given by the parent 28 | # @param [Proc] block 29 | # the block to evaluate for the instance 30 | # 31 | def initialize(klass, parent, options = {}, &block) 32 | @klass = klass 33 | @parent = parent 34 | @options = options 35 | @block = block 36 | end 37 | 38 | # 39 | # Use method missing to delegate methods to the class object or instance 40 | # object. 41 | # 42 | def method_missing(m, *args, &block) 43 | if klass.respond_to?(m) 44 | if args.last.is_a?(Hash) 45 | args.last.merge(options) 46 | end 47 | 48 | klass.send(m, *args, &block) 49 | else 50 | instance.send(m, *args, &block) 51 | end 52 | end 53 | 54 | private 55 | 56 | attr_reader :klass 57 | attr_reader :parent 58 | attr_reader :options 59 | attr_reader :block 60 | 61 | def instance 62 | @instance ||= parent.instance_eval(&block) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/artifactory/collections/build.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | class Collection::Build < Collection::Base 19 | # 20 | # Create a new build collection. 21 | # 22 | # @param (see Collection::Base#initialize) 23 | # 24 | def initialize(parent, options = {}, &block) 25 | super(Resource::Build, parent, options, &block) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/artifactory/configurable.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | # 19 | # A re-usable class containing configuration information for the {Client}. See 20 | # {Defaults} for a list of default values. 21 | # 22 | module Configurable 23 | class << self 24 | # 25 | # The list of configurable keys. 26 | # 27 | # @return [Array] 28 | # 29 | def keys 30 | @keys ||= %i{ 31 | endpoint 32 | username 33 | password 34 | api_key 35 | proxy_address 36 | proxy_password 37 | proxy_port 38 | proxy_username 39 | ssl_pem_file 40 | ssl_verify 41 | user_agent 42 | read_timeout 43 | } 44 | end 45 | end 46 | 47 | # 48 | # Create one attribute getter and setter for each key. 49 | # 50 | Artifactory::Configurable.keys.each do |key| 51 | attr_accessor key 52 | end 53 | 54 | # 55 | # Set the configuration for this config, using a block. 56 | # 57 | # @example Configure the API endpoint 58 | # Artifactory.configure do |config| 59 | # config.endpoint = "http://www.my-artifactory-server.com/artifactory" 60 | # end 61 | # 62 | def configure 63 | yield self 64 | end 65 | 66 | # 67 | # Reset all configuration options to their default values. 68 | # 69 | # @example Reset all settings 70 | # Artifactory.reset! 71 | # 72 | # @return [self] 73 | # 74 | def reset! 75 | Artifactory::Configurable.keys.each do |key| 76 | instance_variable_set(:"@#{key}", Defaults.options[key]) 77 | end 78 | self 79 | end 80 | alias_method :setup, :reset! 81 | 82 | private 83 | 84 | # 85 | # The list of configurable keys, as an options hash. 86 | # 87 | # @return [Hash] 88 | # 89 | def options 90 | map = Artifactory::Configurable.keys.map do |key| 91 | [key, instance_variable_get(:"@#{key}")] 92 | end 93 | Hash[map] 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/artifactory/defaults.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require_relative "version" 18 | 19 | module Artifactory 20 | module Defaults 21 | # Default API endpoint 22 | ENDPOINT = "http://localhost:8080/artifactory".freeze 23 | 24 | # Default User Agent header string 25 | USER_AGENT = "Artifactory Ruby Gem #{Artifactory::VERSION}".freeze 26 | 27 | class << self 28 | # 29 | # The list of calculated default options for the configuration. 30 | # 31 | # @return [Hash] 32 | # 33 | def options 34 | Hash[Configurable.keys.map { |key| [key, send(key)] }] 35 | end 36 | 37 | # 38 | # The endpoint where artifactory lives 39 | # 40 | # @return [String] 41 | # 42 | def endpoint 43 | ENV["ARTIFACTORY_ENDPOINT"] || ENDPOINT 44 | end 45 | 46 | # 47 | # The User Agent header to send along 48 | # 49 | # @return [String] 50 | # 51 | def user_agent 52 | ENV["ARTIFACTORY_USER_AGENT"] || USER_AGENT 53 | end 54 | 55 | # 56 | # The HTTP Basic Authentication username 57 | # 58 | # @return [String, nil] 59 | # 60 | def username 61 | ENV["ARTIFACTORY_USERNAME"] 62 | end 63 | 64 | # 65 | # The HTTP Basic Authentication password 66 | # 67 | # @return [String, nil] 68 | # 69 | def password 70 | ENV["ARTIFACTORY_PASSWORD"] 71 | end 72 | 73 | # 74 | # The API Key for authentication 75 | # 76 | # @return [String, nil] 77 | # 78 | def api_key 79 | ENV["ARTIFACTORY_API_KEY"] 80 | end 81 | 82 | # 83 | # The HTTP Proxy server address as a string 84 | # 85 | # @return [String, nil] 86 | # 87 | def proxy_address 88 | ENV["ARTIFACTORY_PROXY_ADDRESS"] 89 | end 90 | 91 | # 92 | # The HTTP Proxy user password as a string 93 | # 94 | # @return [String, nil] 95 | # 96 | def proxy_password 97 | ENV["ARTIFACTORY_PROXY_PASSWORD"] 98 | end 99 | 100 | # 101 | # The HTTP Proxy server port as a string 102 | # 103 | # @return [String, nil] 104 | # 105 | def proxy_port 106 | ENV["ARTIFACTORY_PROXY_PORT"] 107 | end 108 | 109 | # 110 | # The HTTP Proxy server username as a string 111 | # 112 | # @return [String, nil] 113 | # 114 | def proxy_username 115 | ENV["ARTIFACTORY_PROXY_USERNAME"] 116 | end 117 | 118 | # 119 | # The path to a pem file on disk for use with a custom SSL verification 120 | # 121 | # @return [String, nil] 122 | # 123 | def ssl_pem_file 124 | ENV["ARTIFACTORY_SSL_PEM_FILE"] 125 | end 126 | 127 | # 128 | # Verify SSL requests (default: true) 129 | # 130 | # @return [true, false] 131 | # 132 | def ssl_verify 133 | if ENV["ARTIFACTORY_SSL_VERIFY"].nil? 134 | true 135 | else 136 | %w{t y}.include?(ENV["ARTIFACTORY_SSL_VERIFY"].downcase[0]) 137 | end 138 | end 139 | 140 | # 141 | # Number of seconds to wait for a response from Artifactory 142 | # 143 | # @return [Integer] 144 | # 145 | def read_timeout 146 | if ENV["ARTIFACTORY_READ_TIMEOUT"] 147 | ENV["ARTIFACTORY_READ_TIMEOUT"].to_i 148 | else 149 | 120 150 | end 151 | end 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /lib/artifactory/errors.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | module Error 19 | # Base class for all errors 20 | class ArtifactoryError < StandardError; end 21 | 22 | # Class for all HTTP errors 23 | class HTTPError < ArtifactoryError 24 | attr_reader :code 25 | 26 | def initialize(hash = {}) 27 | @code = hash["status"].to_i 28 | @http = hash["message"].to_s 29 | 30 | super "The Artifactory server responded with an HTTP Error " \ 31 | "#{@code}: `#{@http}'" 32 | end 33 | end 34 | 35 | # A general connection error with a more informative message 36 | class ConnectionError < ArtifactoryError 37 | def initialize(endpoint) 38 | super "The Artifactory server at `#{endpoint}' is not currently " \ 39 | "accepting connections. Please ensure that the server is " \ 40 | "running an that your authentication information is correct." 41 | end 42 | end 43 | 44 | # A general connection error with a more informative message 45 | class InvalidBuildType < ArtifactoryError 46 | def initialize(given_type) 47 | super <<~EOH 48 | '#{given_type}' is not a valid build type. 49 | 50 | Valid build types include: 51 | 52 | #{Resource::Build::BUILD_TYPES.join("\n ")}" 53 | 54 | EOH 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/artifactory/resources/backup.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "rexml/document" unless defined?(REXML::Document) 18 | 19 | module Artifactory 20 | class Resource::Backup < Resource::Base 21 | class << self 22 | # 23 | # Get a list of all backup jobs in the system. 24 | # 25 | # @param [Hash] options 26 | # the list of options 27 | # 28 | # @option options [Artifactory::Client] :client 29 | # the client object to make the request with 30 | # 31 | # @return [Array] 32 | # the list of backup jobs 33 | # 34 | def all(options = {}) 35 | config = Resource::System.configuration(options) 36 | list_from_config("config/backups/backup", config, options) 37 | end 38 | 39 | # 40 | # Find (fetch) a backup job by its key. 41 | # 42 | # @example Find a Backup by its key. 43 | # backup.find('backup-daily') #=> # 44 | # 45 | # @param [String] key 46 | # the name of the backup job to find 47 | # @param [Hash] options 48 | # the list of options 49 | # 50 | # @option options [Artifactory::Client] :client 51 | # the client object to make the request with 52 | # 53 | # @return [Resource::Backup, nil] 54 | # an instance of the backup job that matches the given key, or +nil+ 55 | # if one does not exist 56 | # 57 | def find(key, options = {}) 58 | config = Resource::System.configuration(options) 59 | find_from_config("config/backups/backup/key[text()='#{key}']", config, options) 60 | rescue Error::HTTPError => e 61 | raise unless e.code == 404 62 | 63 | nil 64 | end 65 | 66 | private 67 | 68 | # 69 | # List all the child text elements in the Artifactory configuration file 70 | # of a node matching the specified xpath 71 | # 72 | # @param [String] xpath 73 | # xpath expression for the parent element whose children are to be listed 74 | # 75 | # @param [REXML] config 76 | # Artifactory config as an REXML file 77 | # 78 | # @param [Hash] options 79 | # the list of options 80 | # 81 | # @return [~Resource::Base] 82 | # 83 | def list_from_config(xpath, config, options = {}) 84 | REXML::XPath.match(config, xpath).map do |r| 85 | hash = Util.xml_to_hash(r, "excludedRepositories", false) 86 | from_hash(hash, options) 87 | end 88 | end 89 | 90 | # 91 | # Find all the sibling text elements in the Artifactory configuration file 92 | # of a node matching the specified xpath 93 | # 94 | # @param [String] xpath 95 | # xpath expression for the element whose siblings are to be found 96 | # 97 | # @param [REXML] config 98 | # Artifactory configuration file as an REXML doc 99 | # 100 | # @param [Hash] options 101 | # the list of options 102 | # 103 | def find_from_config(xpath, config, options = {}) 104 | name_node = REXML::XPath.match(config, xpath) 105 | return nil if name_node.empty? 106 | 107 | properties = Util.xml_to_hash(name_node[0].parent, "excludedRepositories", false) 108 | from_hash(properties, options) 109 | end 110 | end 111 | 112 | attribute :key, -> { raise "name missing!" } 113 | attribute :enabled, true 114 | attribute :dir 115 | attribute :cron_exp 116 | attribute :retention_period_hours 117 | attribute :create_archive 118 | attribute :excluded_repositories 119 | attribute :send_mail_on_error 120 | attribute :exclude_builds 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/artifactory/resources/build.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "time" unless defined?(Time.zone_offset) 18 | 19 | module Artifactory 20 | class Resource::Build < Resource::Base 21 | BUILD_SCHEMA_VERSION = "1.0.1".freeze 22 | # valid build types as dictated by the Artifactory API 23 | BUILD_TYPES = %w{ ANT IVY MAVEN GENERIC GRADLE }.freeze 24 | 25 | class << self 26 | # 27 | # Search for all builds in the system. 28 | # 29 | # @param [String] name 30 | # the name of the build component 31 | # @param [Hash] options 32 | # the list of options 33 | # 34 | # @option options [Artifactory::Client] :client 35 | # the client object to make the request with 36 | # 37 | # @return [Array] 38 | # the list of builds 39 | # 40 | def all(name, options = {}) 41 | client = extract_client!(options) 42 | client.get("/api/build/#{url_safe(name)}")["buildsNumbers"].map do |build_number| 43 | # Remove the leading / from the `uri` value. Converts `/484` to `484`. 44 | number = build_number["uri"].slice(1..-1) 45 | find(name, number, client: client) 46 | end.compact.flatten 47 | rescue Error::HTTPError => e 48 | # Artifactory returns a 404 instead of an empty list when there are no 49 | # builds. Whoever decided that was a good idea clearly doesn't 50 | # understand the point of REST interfaces... 51 | raise unless e.code == 404 52 | 53 | [] 54 | end 55 | 56 | # 57 | # Find (fetch) data for a particular build of a component 58 | # 59 | # @example Find data for a build of a component 60 | # Build.find('wicket', 25) #=> # 61 | # 62 | # @param [String] name 63 | # the name of the build component 64 | # @param [String] number 65 | # the number of the build 66 | # @param [Hash] options 67 | # the list of options 68 | # 69 | # @option options [Artifactory::Client] :client 70 | # the client object to make the request with 71 | # 72 | # @return [Resource::Build, nil] 73 | # an instance of the build that matches the given name/number 74 | # combination, or +nil+ if one does not exist 75 | # 76 | def find(name, number, options = {}) 77 | client = extract_client!(options) 78 | response = client.get("/api/build/#{url_safe(name)}/#{url_safe(number)}") 79 | from_hash(response["buildInfo"], client: client) 80 | rescue Error::HTTPError => e 81 | raise unless e.code == 404 82 | 83 | nil 84 | end 85 | 86 | # 87 | # @see Artifactory::Resource::Base.from_hash 88 | # 89 | def from_hash(hash, options = {}) 90 | super.tap do |instance| 91 | instance.started = Time.parse(instance.started) rescue nil 92 | instance.duration_millis = instance.duration_millis.to_i 93 | end 94 | end 95 | end 96 | 97 | # Based on https://github.com/JFrogDev/build-info/blob/master/README.md#build-info-json-format 98 | attribute :properties, {} 99 | attribute :version, BUILD_SCHEMA_VERSION 100 | attribute :name, -> { raise "Build component missing!" } 101 | attribute :number, -> { raise "Build number missing!" } 102 | attribute :type, "GENERIC" 103 | attribute :build_agent, {} 104 | attribute :agent, {} 105 | attribute :started, Time.now.utc.iso8601(3) 106 | attribute :duration_millis 107 | attribute :artifactory_principal 108 | attribute :url 109 | attribute :vcs_revision 110 | attribute :vcs_url 111 | attribute :license_control, {} 112 | attribute :build_retention, {} 113 | attribute :modules, [] 114 | attribute :governance 115 | attribute :statuses, [] 116 | 117 | # 118 | # Compare a build artifacts/dependencies/environment with an older 119 | # build to see what has changed (new artifacts added, old dependencies 120 | # deleted etc). 121 | # 122 | # @example List all properties for an artifact 123 | # build.diff(35) #=> { 'artifacts'=>{}, 'dependencies'=>{}, 'properties'=>{} } 124 | # 125 | # @param [String] previous_build_number 126 | # the number of the previous build to compare against 127 | # 128 | # @return [Hash] 129 | # the list of properties 130 | # 131 | def diff(previous_build_number) 132 | endpoint = api_path + "?" "diff=#{url_safe(previous_build_number)}" 133 | client.get(endpoint, {}) 134 | end 135 | 136 | # 137 | # Move a build's artifacts to a new repository optionally moving or 138 | # copying the build's dependencies to the target repository 139 | # and setting properties on promoted artifacts. 140 | # 141 | # @example promote the build to 'omnibus-stable-local' 142 | # build.promote('omnibus-stable-local') 143 | # @example promote a build attaching some new properites 144 | # build.promote('omnibus-stable-local' 145 | # properties: { 146 | # 'promoted_by' => 'hipchat:schisamo@chef.io' 147 | # } 148 | # ) 149 | # 150 | # @param [String] target_repo 151 | # repository to move or copy the build's artifacts and/or dependencies 152 | # @param [Hash] options 153 | # the list of options to pass 154 | # 155 | # @option options [String] :status (default: 'promoted') 156 | # new build status (any string) 157 | # @option options [String] :comment (default: '') 158 | # an optional comment describing the reason for promotion 159 | # @option options [String] :user (default: +Artifactory.username+) 160 | # the user that invoked promotion 161 | # @option options [Boolean] :dry_run (default: +false+) 162 | # pretend to do the promotion 163 | # @option options [Boolean] :copy (default: +false+) 164 | # whether to copy instead of move 165 | # @option options [Boolean] :dependencies (default: +false+) 166 | # whether to move/copy the build's dependencies 167 | # @option options [Array] :scopes (default: []) 168 | # an array of dependency scopes to include when "dependencies" is true 169 | # @option options [Hash>] :properties (default: []) 170 | # a list of properties to attach to the build's artifacts 171 | # @option options [Boolean] :fail_fast (default: +true+) 172 | # fail and abort the operation upon receiving an error 173 | # 174 | # @return [Hash] 175 | # the parsed JSON response from the server 176 | # 177 | def promote(target_repo, options = {}) 178 | request_body = {}.tap do |body| 179 | body[:status] = options[:status] || "promoted" 180 | body[:comment] = options[:comment] || "" 181 | body[:ciUser] = options[:user] || Artifactory.username 182 | body[:dryRun] = options[:dry_run] || false 183 | body[:targetRepo] = target_repo 184 | body[:copy] = options[:copy] || false 185 | body[:artifacts] = true # always move/copy the build's artifacts 186 | body[:dependencies] = options[:dependencies] || false 187 | body[:scopes] = options[:scopes] || [] 188 | body[:properties] = options[:properties] || {} 189 | body[:failFast] = options[:fail_fast] || true 190 | end 191 | 192 | endpoint = "/api/build/promote/#{url_safe(name)}/#{url_safe(number)}" 193 | client.post(endpoint, JSON.fast_generate(request_body), 194 | "Content-Type" => "application/json") 195 | end 196 | 197 | # 198 | # Creates data about a build. 199 | # 200 | # @return [Boolean] 201 | # 202 | def save 203 | raise Error::InvalidBuildType.new(type) unless BUILD_TYPES.include?(type) 204 | 205 | file = Tempfile.new("build.json") 206 | file.write(to_json) 207 | file.rewind 208 | 209 | client.put("/api/build", file, 210 | "Content-Type" => "application/json") 211 | true 212 | ensure 213 | if file 214 | file.close 215 | file.unlink 216 | end 217 | end 218 | 219 | private 220 | 221 | # 222 | # The path to this build on the server. 223 | # 224 | # @return [String] 225 | # 226 | def api_path 227 | "/api/build/#{url_safe(name)}/#{url_safe(number)}" 228 | end 229 | end 230 | end 231 | -------------------------------------------------------------------------------- /lib/artifactory/resources/build_component.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "time" unless defined?(Time.zone_offset) 18 | 19 | module Artifactory 20 | class Resource::BuildComponent < Resource::Base 21 | class << self 22 | # 23 | # Search for all compoenents for which build data exists. 24 | # 25 | # @param [Hash] options 26 | # the list of options 27 | # 28 | # @option options [Artifactory::Client] :client 29 | # the client object to make the request with 30 | # 31 | # @return [Array] 32 | # the list of builds 33 | # 34 | def all(options = {}) 35 | client = extract_client!(options) 36 | client.get("/api/build")["builds"].map do |component| 37 | from_hash(component, client: client) 38 | end.compact.flatten 39 | rescue Error::HTTPError => e 40 | # Artifactory returns a 404 instead of an empty list when there are no 41 | # builds. Whoever decided that was a good idea clearly doesn't 42 | # understand the point of REST interfaces... 43 | raise unless e.code == 404 44 | 45 | [] 46 | end 47 | 48 | # 49 | # Find (fetch) data for a particular build component 50 | # 51 | # @example Find a particular build component 52 | # BuildComponent.find('wicket') #=> # 53 | # 54 | # @param [String] name 55 | # the name of the build component 56 | # @param [Hash] options 57 | # the list of options 58 | # 59 | # @option options [Artifactory::Client] :client 60 | # the client object to make the request with 61 | # 62 | # @return [Resource::BuildComponent, nil] 63 | # an instance of the build component that matches the given name, 64 | # or +nil+ if one does not exist 65 | # 66 | def find(name, options = {}) 67 | client = extract_client!(options) 68 | all.find do |component| 69 | component.name == name 70 | end 71 | end 72 | 73 | # 74 | # @see Artifactory::Resource::Base.from_hash 75 | # 76 | def from_hash(hash, options = {}) 77 | super.tap do |instance| 78 | # Remove the leading / from the `uri` value. Converts `/foo` to `foo`. 79 | instance.name = instance.uri.slice(1..-1) 80 | instance.last_started = Time.parse(instance.last_started) rescue nil 81 | end 82 | end 83 | end 84 | 85 | attribute :uri 86 | attribute :name, -> { raise "Name missing!" } 87 | attribute :last_started 88 | 89 | # 90 | # The list of build data for this component. 91 | # 92 | # @example Get the list of artifacts for a repository 93 | # component = BuildComponent.new(name: 'wicket') 94 | # component.builds #=> [#, ...] 95 | # 96 | # @return [Collection::Build] 97 | # the list of builds 98 | # 99 | def builds 100 | @builds ||= Collection::Build.new(self, name: name) do 101 | Resource::Build.all(name) 102 | end 103 | end 104 | 105 | # 106 | # Remove this component's build data stored in Artifactory 107 | # 108 | # @option options [Array] :build_numbers (default: nil) 109 | # an array of build numbers that should be deleted; if not given 110 | # all builds (for this component) are deleted 111 | # @option options [Boolean] :artifacts (default: +false+) 112 | # if true the component's artifacts are also removed 113 | # @option options [Boolean] :delete_all (default: +false+) 114 | # if true the entire component is removed 115 | # 116 | # @return [Boolean] 117 | # true if the object was deleted successfully, false otherwise 118 | # 119 | def delete(options = {}) 120 | params = {}.tap do |param| 121 | param[:buildNumbers] = options[:build_numbers].join(",") if options[:build_numbers] 122 | param[:artifacts] = 1 if options[:artifacts] 123 | param[:deleteAll] = 1 if options[:delete_all] 124 | end 125 | 126 | endpoint = api_path + "?#{to_query_string_parameters(params)}" 127 | client.delete(endpoint, {}) 128 | true 129 | rescue Error::HTTPError => e 130 | false 131 | end 132 | 133 | # 134 | # Rename a build component. 135 | # 136 | # @param [String] new_name 137 | # new name for the component 138 | # 139 | # @return [Boolean] 140 | # true if the object was renamed successfully, false otherwise 141 | # 142 | def rename(new_name, options = {}) 143 | endpoint = "/api/build/rename/#{url_safe(name)}" + "?to=#{new_name}" 144 | client.post(endpoint, {}) 145 | true 146 | rescue Error::HTTPError => e 147 | false 148 | end 149 | 150 | private 151 | 152 | # 153 | # The path to this build component on the server. 154 | # 155 | # @return [String] 156 | # 157 | def api_path 158 | "/api/build/#{url_safe(name)}" 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /lib/artifactory/resources/certificate.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | class Resource::Certificate < Resource::Base 3 | class << self 4 | # 5 | # Get a list of all certificates in the system. 6 | # 7 | # @param [Hash] options 8 | # the list of options 9 | # 10 | # @option options [Artifactory::Client] :client 11 | # the client object to make the request with 12 | # 13 | # @return [Array] 14 | # the list of builds 15 | # 16 | def all(options = {}) 17 | client = extract_client!(options) 18 | client.get("/api/system/security/certificates").map do |cert| 19 | from_hash(cert, client: client) 20 | end.compact 21 | end 22 | 23 | # 24 | # @see Artifactory::Resource::Base.from_hash 25 | # 26 | def from_hash(hash, options = {}) 27 | super.tap do |instance| 28 | instance.issued_on = Time.parse(instance.issued_on) rescue nil 29 | instance.valid_until = Time.parse(instance.valid_until) rescue nil 30 | end 31 | end 32 | end 33 | 34 | attribute :certificate_alias, -> { raise "Certificate alias missing!" } 35 | attribute :fingerprint 36 | attribute :issued_by 37 | attribute :issued_on 38 | attribute :issued_to 39 | attribute :local_path, -> { raise "Local destination missing!" } 40 | attribute :valid_until 41 | 42 | # 43 | # Delete this certificate from artifactory, suppressing any +ResourceNotFound+ 44 | # exceptions might occur. 45 | # 46 | # @return [Boolean] 47 | # true if the object was deleted successfully, false otherwise 48 | # 49 | def delete 50 | client.delete(api_path) 51 | true 52 | rescue Error::HTTPError 53 | false 54 | end 55 | 56 | # 57 | # Upload a certificate. If the first parameter is a File object, that file 58 | # descriptor is passed to the uploader. If the first parameter is a string, 59 | # it is assumed to be a path to a local file on disk. This method will 60 | # automatically construct the File object from the given path. 61 | # 62 | # @example Upload a certificate from a File instance 63 | # certificate = Certificate.new(local_path: '/path/to/cert.pem', certificate_alias: 'test') 64 | # certificate.upload 65 | # 66 | # @return [Resource::Certificate] 67 | # 68 | def upload 69 | file = File.new(File.expand_path(local_path)) 70 | headers = { "Content-Type" => "application/text" } 71 | 72 | response = client.post(api_path, file, headers) 73 | 74 | return unless response.is_a?(Hash) 75 | 76 | self.class.all.select { |x| x.certificate_alias.eql?(certificate_alias) }.first 77 | end 78 | 79 | private 80 | 81 | # 82 | # The path to this certificate on the server. 83 | # 84 | # @return [String] 85 | # 86 | def api_path 87 | "/api/system/security/certificates/#{url_safe(certificate_alias)}" 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/artifactory/resources/group.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | class Resource::Group < Resource::Base 19 | class << self 20 | # 21 | # Get a list of all groups in the system. 22 | # 23 | # @param [Hash] options 24 | # the list of options 25 | # 26 | # @option options [Artifactory::Client] :client 27 | # the client object to make the request with 28 | # 29 | # @return [Array] 30 | # the list of groups 31 | # 32 | def all(options = {}) 33 | client = extract_client!(options) 34 | client.get("/api/security/groups").map do |hash| 35 | from_url(hash["uri"], client: client) 36 | end 37 | end 38 | 39 | # 40 | # Find (fetch) a group by its name. 41 | # 42 | # @example Find a group by its name 43 | # Group.find('readers') #=> # 44 | # 45 | # @param [String] name 46 | # the name of the group to find 47 | # @param [Hash] options 48 | # the list of options 49 | # 50 | # @option options [Artifactory::Client] :client 51 | # the client object to make the request with 52 | # 53 | # @return [Resource::Group, nil] 54 | # an instance of the group that matches the given name, or +nil+ 55 | # if one does not exist 56 | # 57 | def find(name, options = {}) 58 | client = extract_client!(options) 59 | 60 | response = client.get("/api/security/groups/#{url_safe(name)}") 61 | from_hash(response, client: client) 62 | rescue Error::HTTPError => e 63 | raise unless e.code == 404 64 | 65 | nil 66 | end 67 | end 68 | 69 | attribute :auto_join 70 | attribute :description 71 | attribute :name, -> { raise "Name missing!" } 72 | attribute :realm 73 | attribute :realm_attributes 74 | 75 | # 76 | # Delete this group from artifactory, suppressing any +ResourceNotFound+ 77 | # exceptions might occur. 78 | # 79 | # @return [Boolean] 80 | # true if the object was deleted successfully, false otherwise 81 | # 82 | def delete 83 | client.delete(api_path) 84 | true 85 | rescue Error::HTTPError 86 | false 87 | end 88 | 89 | # 90 | # Creates or updates a group configuration depending on if the 91 | # group configuration previously existed. 92 | # 93 | # @return [Boolean] 94 | # 95 | def save 96 | if self.class.find(name, client: client) 97 | client.post(api_path, to_json, headers) 98 | else 99 | client.put(api_path, to_json, headers) 100 | end 101 | true 102 | end 103 | 104 | private 105 | 106 | # 107 | # The path to this group on the server. 108 | # 109 | # @return [String] 110 | # 111 | def api_path 112 | @api_path ||= "/api/security/groups/#{url_safe(name)}" 113 | end 114 | 115 | # 116 | # The default headers for this object. This includes the +Content-Type+. 117 | # 118 | # @return [Hash] 119 | # 120 | def headers 121 | @headers ||= { 122 | "Content-Type" => "application/vnd.org.jfrog.artifactory.security.Group+json", 123 | } 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/artifactory/resources/layout.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "rexml/document" unless defined?(REXML::Document) 18 | 19 | module Artifactory 20 | class Resource::Layout < Resource::Base 21 | class << self 22 | # 23 | # Get a list of all repository layouts in the system. 24 | # 25 | # @param [Hash] options 26 | # the list of options 27 | # 28 | # @option options [Artifactory::Client] :client 29 | # the client object to make the request with 30 | # 31 | # @return [Array] 32 | # the list of layouts 33 | # 34 | def all(options = {}) 35 | config = Resource::System.configuration(options) 36 | list_from_config("config/repoLayouts/repoLayout", config, options) 37 | end 38 | 39 | # 40 | # Find (fetch) a layout by its name. 41 | # 42 | # @example Find a layout by its name 43 | # Layout.find('maven-2-default') #=> # 44 | # 45 | # @param [String] name 46 | # the name of the layout to find 47 | # @param [Hash] options 48 | # the list of options 49 | # 50 | # @option options [Artifactory::Client] :client 51 | # the client object to make the request with 52 | # 53 | # @return [Resource::Layout, nil] 54 | # an instance of the layout that matches the given name, or +nil+ 55 | # if one does not exist 56 | # 57 | def find(name, options = {}) 58 | config = Resource::System.configuration(options) 59 | find_from_config("config/repoLayouts/repoLayout/name[text()='#{name}']", config, options) 60 | rescue Error::HTTPError => e 61 | raise unless e.code == 404 62 | 63 | nil 64 | end 65 | end 66 | 67 | attribute :name, -> { raise "Name missing!" } 68 | attribute :artifact_path_pattern 69 | attribute :distinctive_descriptor_path_pattern, true 70 | attribute :descriptor_path_pattern 71 | attribute :folder_integration_revision_reg_exp 72 | attribute :file_integration_revision_reg_exp 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/artifactory/resources/ldap_setting.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "rexml/document" unless defined?(REXML::Document) 18 | 19 | module Artifactory 20 | class Resource::LDAPSetting < Resource::Base 21 | class << self 22 | # 23 | # Get a list of all ldap settings in the system. 24 | # 25 | # @param [Hash] options 26 | # the list of options 27 | # 28 | # @option options [Artifactory::Client] :client 29 | # the client object to make the request with 30 | # 31 | # @return [Array] 32 | # the list of layouts 33 | # 34 | def all(options = {}) 35 | config = Resource::System.configuration(options) 36 | list_from_config("config/security/ldapSettings/ldapSetting", config, options) 37 | end 38 | 39 | # 40 | # Find (fetch) an ldap setting by its name. 41 | # 42 | # @example Find an LDAPSetting by its name. 43 | # ldap_config.find('ldap.example.com') #=> # 44 | # 45 | # @param [String] name 46 | # the name of the ldap config setting to find 47 | # @param [Hash] options 48 | # the list of options 49 | # 50 | # @option options [Artifactory::Client] :client 51 | # the client object to make the request with 52 | # 53 | # @return [Resource::LDAPSetting, nil] 54 | # an instance of the ldap setting that matches the given name, or +nil+ 55 | # if one does not exist 56 | # 57 | def find(name, options = {}) 58 | config = Resource::System.configuration(options) 59 | find_from_config("config/security/ldapSettings/ldapSetting/key[text()='#{name}']", config, options) 60 | rescue Error::HTTPError => e 61 | raise unless e.code == 404 62 | 63 | nil 64 | end 65 | 66 | private 67 | 68 | # 69 | # List all the child text elements in the Artifactory configuration file 70 | # of a node matching the specified xpath 71 | # 72 | # @param [String] xpath 73 | # xpath expression for the parent element whose children are to be listed 74 | # 75 | # @param [REXML] config 76 | # Artifactory config as an REXML file 77 | # 78 | # @param [Hash] options 79 | # the list of options 80 | # 81 | def list_from_config(xpath, config, options = {}) 82 | REXML::XPath.match(config, xpath).map do |r| 83 | hash = Util.xml_to_hash(r, "search") 84 | from_hash(hash, options) 85 | end 86 | end 87 | 88 | # 89 | # Find all the sibling text elements in the Artifactory configuration file 90 | # of a node matching the specified xpath 91 | # 92 | # @param [String] xpath 93 | # xpath expression for the element whose siblings are to be found 94 | # 95 | # @param [REXML] config 96 | # Artifactory configuration file as an REXML doc 97 | # 98 | # @param [Hash] options 99 | # the list of options 100 | # 101 | def find_from_config(xpath, config, options = {}) 102 | name_node = REXML::XPath.match(config, xpath) 103 | return nil if name_node.empty? 104 | 105 | properties = Util.xml_to_hash(name_node[0].parent, "search") 106 | from_hash(properties, options) 107 | end 108 | end 109 | 110 | # Ordered to match the artifactory xsd to make consuming the attributes 111 | # hash when writing artifactory xml more convenient. 112 | # http://bit.ly/UHMrHc 113 | attribute :key, -> { raise "name missing!" } 114 | attribute :enabled, true 115 | attribute :ldap_url 116 | attribute :search_filter 117 | attribute :search_base 118 | attribute :search_sub_tree 119 | attribute :manager_dn 120 | attribute :manager_password 121 | attribute :auto_create_user 122 | attribute :email_attribute, "mail" 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /lib/artifactory/resources/mail_server.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "rexml/document" unless defined?(REXML::Document) 18 | 19 | module Artifactory 20 | class Resource::MailServer < Resource::Base 21 | class << self 22 | # 23 | # Get a list of all mail servers in the system. 24 | # 25 | # @param [Hash] options 26 | # the list of options 27 | # 28 | # @option options [Artifactory::Client] :client 29 | # the client object to make the request with 30 | # 31 | # @return [Array] 32 | # the list of layouts 33 | # 34 | def all(options = {}) 35 | config = Resource::System.configuration(options) 36 | list_from_config("config/mailServer", config, options) 37 | end 38 | 39 | # 40 | # Find (fetch) a mail server by its host. 41 | # 42 | # @example Find a MailServer by its host. 43 | # mail_server.find('smtp.gmail.com') #=> # 44 | # 45 | # @param [String] host 46 | # the host of the mail server to find 47 | # @param [Hash] options 48 | # the list of options 49 | # 50 | # @option options [Artifactory::Client] :client 51 | # the client object to make the request with 52 | # 53 | # @return [Resource::MailServer, nil] 54 | # an instance of the mail server that matches the given host, or +nil+ 55 | # if one does not exist 56 | # 57 | def find(host, options = {}) 58 | config = Resource::System.configuration(options) 59 | find_from_config("config/mailServer/host[text()='#{host}']", config, options) 60 | rescue Error::HTTPError => e 61 | raise unless e.code == 404 62 | 63 | nil 64 | end 65 | end 66 | 67 | attribute :enabled 68 | attribute :host, -> { raise "host missing!" } 69 | attribute :port 70 | attribute :username 71 | attribute :password 72 | attribute :from 73 | attribute :subject_prefix 74 | attribute :tls 75 | attribute :ssl 76 | attribute :artifactory_url 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/artifactory/resources/permission_target.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | 15 | module Artifactory 16 | class Resource::PermissionTarget < Resource::Base 17 | VERBOSE_PERMS = { 18 | "d" => "delete", 19 | "m" => "admin", 20 | "n" => "annotate", 21 | "r" => "read", 22 | "w" => "deploy", 23 | }.freeze 24 | class << self 25 | # 26 | # Get a list of all PermissionTargets in the system. 27 | # 28 | # @param [Hash] options 29 | # the list of options 30 | # 31 | # @option options [Artifactory::Client] :client 32 | # the client object to make the request with 33 | # 34 | # @return [Array] 35 | # the list of PermissionTargets 36 | # 37 | def all(options = {}) 38 | client = extract_client!(options) 39 | client.get("/api/security/permissions").map do |hash| 40 | from_url(hash["uri"], client: client) 41 | end 42 | end 43 | 44 | # 45 | # Find (fetch) a permission target by its name. 46 | # 47 | # @example Find a permission target by its name 48 | # PermissionTarget.find('readers') #=> # 49 | # 50 | # @param [String] name 51 | # the name of the permission target to find 52 | # @param [Hash] options 53 | # the list of options 54 | # 55 | # @option options [Artifactory::Client] :client 56 | # the client object to make the request with 57 | # 58 | # @return [Resource::PermissionTarget, nil] 59 | # an instance of the permission target that matches the given name, or +nil+ 60 | # if one does not exist 61 | # 62 | def find(name, options = {}) 63 | client = extract_client!(options) 64 | 65 | response = client.get("/api/security/permissions/#{url_safe(name)}") 66 | from_hash(response, client: client) 67 | rescue Error::HTTPError => e 68 | raise unless e.code == 404 69 | 70 | nil 71 | end 72 | 73 | # 74 | # @see Resource::Base.from_hash 75 | # Additionally use verbose names for permissions (e.g. 'read' for 'r') 76 | # 77 | def from_hash(hash, options = {}) 78 | super.tap do |instance| 79 | %w{users groups}.each do |key| 80 | if instance.principals[key] && !instance.principals[key].nil? 81 | instance.principals[key] = Hash[instance.principals[key].map { |k, v| [k, verbose(v)] } ] 82 | end 83 | end 84 | end 85 | end 86 | 87 | private 88 | 89 | # 90 | # Replace an array of permissions with one using verbose permission names 91 | # 92 | def verbose(array) 93 | array.map { |elt| VERBOSE_PERMS[elt] }.sort 94 | end 95 | end 96 | 97 | class Principal 98 | attr_accessor :users, :groups 99 | 100 | def initialize(users = {}, groups = {}) 101 | @users = users 102 | @groups = groups 103 | end 104 | 105 | # 106 | # Converts the user-friendly form of the principals hash to one suitable 107 | # for posting to Artifactory. 108 | # @return [Hash] 109 | # 110 | def to_abbreviated 111 | { "users" => abbreviate_principal(@users), "groups" => abbreviate_principal(@groups) } 112 | end 113 | 114 | private 115 | 116 | # 117 | # Replace an array of verbose permission names with an equivalent array of abbreviated permission names. 118 | # 119 | def abbreviate_permissions(array) 120 | inverse = VERBOSE_PERMS.invert 121 | if (inverse.keys & array).sort != array.sort 122 | raise "One of your principals contains an invalid permission. Valid permissions are #{inverse.keys.join(", ")}" 123 | end 124 | 125 | array.map { |elt| inverse[elt] }.sort 126 | end 127 | 128 | # 129 | # Replace a principal with verbose permissions with an equivalent one with abbreviated permissions. 130 | # 131 | def abbreviate_principal(principal_hash) 132 | Hash[principal_hash.map { |k, v| [k, abbreviate_permissions(v)] } ] 133 | end 134 | end 135 | 136 | attribute :name, -> { raise "Name missing!" } 137 | attribute :includes_pattern, "**" 138 | attribute :excludes_pattern, "" 139 | attribute :repositories 140 | attribute :principals, { "users" => {}, "groups" => {} } 141 | 142 | def client_principal 143 | @client_principal ||= Principal.new(principals["users"], principals["groups"]) 144 | end 145 | 146 | # 147 | # Delete this PermissionTarget from artifactory, suppressing any +ResourceNotFound+ 148 | # exceptions might occur. 149 | # 150 | # @return [Boolean] 151 | # true if the object was deleted successfully, false otherwise 152 | # 153 | def delete 154 | client.delete(api_path) 155 | true 156 | rescue Error::HTTPError 157 | false 158 | end 159 | 160 | # 161 | # Save the PermissionTarget to the artifactory server. 162 | # See http://bit.ly/1qMOw0L 163 | # 164 | # @return [Boolean] 165 | # 166 | def save 167 | send("principals=", client_principal.to_abbreviated) 168 | client.put(api_path, to_json, headers) 169 | true 170 | end 171 | 172 | # 173 | # Getter for groups 174 | # 175 | def groups 176 | client_principal.groups 177 | end 178 | 179 | # 180 | # Setter for groups (groups_hash expected to be friendly) 181 | # 182 | def groups=(groups_hash) 183 | client_principal.groups = Hash[groups_hash.map { |k, v| [k, v.sort] } ] 184 | end 185 | 186 | # 187 | # Getter for users 188 | # 189 | def users 190 | client_principal.users 191 | end 192 | 193 | # 194 | # Setter for users (expecting users_hash to be friendly) 195 | # 196 | def users=(users_hash) 197 | client_principal.users = Hash[users_hash.map { |k, v| [k, v.sort] } ] 198 | end 199 | 200 | private 201 | 202 | # 203 | # The path to this PermissionTarget on the server. 204 | # 205 | # @return [String] 206 | # 207 | def api_path 208 | @api_path ||= "/api/security/permissions/#{url_safe(name)}" 209 | end 210 | 211 | # 212 | # The default headers for this object. This includes the +Content-Type+. 213 | # 214 | # @return [Hash] 215 | # 216 | def headers 217 | @headers ||= { 218 | "Content-Type" => "application/vnd.org.jfrog.artifactory.security.PermissionTarget+json", 219 | } 220 | end 221 | end 222 | end 223 | -------------------------------------------------------------------------------- /lib/artifactory/resources/plugin.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | class Resource::Plugin < Resource::Base 19 | class << self 20 | # 21 | # Get a list of all plugins in the system. 22 | # 23 | # @param [Hash] options 24 | # the list of options 25 | # 26 | # @option options [Artifactory::Client] :client 27 | # the client object to make the request with 28 | # 29 | # @return [Array] 30 | # the list of builds 31 | # 32 | def all(options = {}) 33 | client = extract_client!(options) 34 | client.get("/api/plugins") 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/artifactory/resources/repository.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | class Resource::Repository < Resource::Base 19 | class << self 20 | # 21 | # Get a list of all repositories in the system. 22 | # 23 | # @param [Hash] options 24 | # the list of options 25 | # 26 | # @option options [Artifactory::Client] :client 27 | # the client object to make the request with 28 | # 29 | # @return [Array] 30 | # the list of builds 31 | # 32 | def all(options = {}) 33 | client = extract_client!(options) 34 | client.get("/api/repositories").map do |hash| 35 | find(hash["key"], client: client) 36 | end.compact 37 | end 38 | 39 | # 40 | # Find (fetch) a repository by name. 41 | # 42 | # @example Find a repository by named key 43 | # Repository.find(name: 'libs-release-local') #=> # 44 | # 45 | # @param [Hash] options 46 | # the list of options 47 | # 48 | # @option options [String] :name 49 | # the name of the repository to find 50 | # @option options [Artifactory::Client] :client 51 | # the client object to make the request with 52 | # 53 | # @return [Resource::Repository, nil] 54 | # an instance of the repository that matches the given name, or +nil+ 55 | # if one does not exist 56 | # 57 | def find(name, options = {}) 58 | client = extract_client!(options) 59 | 60 | response = client.get("/api/repositories/#{url_safe(name)}") 61 | from_hash(response, client: client) 62 | rescue Error::HTTPError => e 63 | raise unless e.code == 400 64 | 65 | nil 66 | end 67 | end 68 | 69 | attribute :blacked_out, false 70 | attribute :description 71 | attribute :checksum_policy_type, "client-checksums" 72 | attribute :excludes_pattern, "" 73 | attribute :handle_releases, true 74 | attribute :handle_snapshots, true 75 | attribute :includes_pattern, "**/*" 76 | attribute :key, -> { raise "Key is missing!" } 77 | attribute :max_unique_snapshots, 0 78 | attribute :notes 79 | attribute :package_type, "generic" 80 | attribute :property_sets, [] 81 | attribute :repo_layout_ref, "simple-default" 82 | attribute :rclass, "local" 83 | attribute :snapshot_version_behavior, "non-unique" 84 | attribute :suppress_pom_consistency_checks, false 85 | attribute :url, "" 86 | attribute :yum_root_depth, 0 87 | attribute :calculate_yum_metadata, false 88 | attribute :repositories, [] 89 | attribute :external_dependencies_enabled, false 90 | attribute :client_tls_certificate, "" 91 | 92 | # 93 | # Creates or updates a repository configuration depending on if the 94 | # repository configuration previously existed. This method also works 95 | # around Artifactory's dangerous default behavior: 96 | # 97 | # > An existing repository with the same key are removed from the 98 | # > configuration and its content is removed! 99 | # 100 | # @return [Boolean] 101 | # 102 | def save 103 | if self.class.find(key, client: client) 104 | client.post(api_path, to_json, headers) 105 | else 106 | client.put(api_path, to_json, headers) 107 | end 108 | true 109 | end 110 | 111 | # 112 | # Upload to a given repository 113 | # 114 | # @see Artifact#upload Upload syntax examples 115 | # 116 | # @return [Resource::Artifact] 117 | # 118 | def upload(local_path, remote_path, properties = {}, headers = {}) 119 | artifact = Resource::Artifact.new(local_path: local_path) 120 | artifact.upload(key, remote_path, properties, headers) 121 | end 122 | 123 | # 124 | # Upload an artifact with the given SHA checksum. Consult the artifactory 125 | # documentation for the possible responses when the checksums fail to 126 | # match. 127 | # 128 | # @see Artifact#upload_with_checksum More syntax examples 129 | # 130 | def upload_with_checksum(local_path, remote_path, checksum, properties = {}) 131 | artifact = Resource::Artifact.new(local_path: local_path) 132 | artifact.upload_with_checksum(key, remote_path, checksum, properties) 133 | end 134 | 135 | # 136 | # Upload an artifact with the given archive. Consult the artifactory 137 | # documentation for the format of the archive to upload. 138 | # 139 | # @see Artifact#upload_from_archive More syntax examples 140 | # 141 | def upload_from_archive(local_path, remote_path, properties = {}) 142 | artifact = Resource::Artifact.new(local_path: local_path) 143 | artifact.upload_from_archive(key, remote_path, properties) 144 | end 145 | 146 | # 147 | # The list of artifacts in this repository on the remote artifactory 148 | # server. 149 | # 150 | # @see Artifact.search Search syntax examples 151 | # 152 | # @example Get the list of artifacts for a repository 153 | # repo = Repository.new('libs-release-local') 154 | # repo.artifacts #=> [#, ...] 155 | # 156 | # @return [Collection::Artifact] 157 | # the list of artifacts 158 | # 159 | def artifacts 160 | @artifacts ||= Collection::Artifact.new(self, repos: key) do 161 | Resource::Artifact.search(name: ".*", repos: key) 162 | end 163 | end 164 | 165 | # 166 | # 167 | # 168 | def files 169 | response = client.get("/api/storage/#{url_safe(key)}", { 170 | deep: 0, 171 | listFolders: 0, 172 | mdTimestamps: 0, 173 | includeRootPath: 0, 174 | }) 175 | 176 | response["children"] 177 | end 178 | 179 | # 180 | # Delete this repository from artifactory, suppressing any +ResourceNotFound+ 181 | # exceptions might occur. 182 | # 183 | # @return [Boolean] 184 | # true if the object was deleted successfully, false otherwise 185 | # 186 | def delete 187 | client.delete(api_path) 188 | true 189 | rescue Error::HTTPError => e 190 | false 191 | end 192 | 193 | private 194 | 195 | # 196 | # The path to this repository on the server. 197 | # 198 | # @return [String] 199 | # 200 | def api_path 201 | "/api/repositories/#{url_safe(key)}" 202 | end 203 | 204 | # 205 | # The default headers for this object. This includes the +Content-Type+. 206 | # 207 | # @return [Hash] 208 | # 209 | def headers 210 | @headers ||= { 211 | "Content-Type" => content_type, 212 | } 213 | end 214 | 215 | # 216 | # The default Content-Type for this repository. It varies based on the 217 | # repository type. 218 | # 219 | # @return [String] 220 | # 221 | def content_type 222 | case rclass.to_s.downcase 223 | when "local" 224 | "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json" 225 | when "remote" 226 | "application/vnd.org.jfrog.artifactory.repositories.RemoteRepositoryConfiguration+json" 227 | when "virtual" 228 | "application/vnd.org.jfrog.artifactory.repositories.VirtualRepositoryConfiguration+json" 229 | else 230 | raise "Unknown Repository type `#{rclass}'!" 231 | end 232 | end 233 | end 234 | end 235 | -------------------------------------------------------------------------------- /lib/artifactory/resources/system.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | class Resource::System < Resource::Base 19 | class << self 20 | # 21 | # Get general system information. 22 | # 23 | # @example Get the system information 24 | # System.info #=> "..." 25 | # 26 | # @param [Hash] options 27 | # the list of options 28 | # 29 | # @option options [Artifactory::Client] :client 30 | # the client object to make the request with 31 | # 32 | # @return [String] 33 | # a "table" of the system information as returned by the API 34 | # 35 | def info(options = {}) 36 | client = extract_client!(options) 37 | client.get("/api/system") 38 | end 39 | 40 | # 41 | # Check the status of the Artifactory server and API. This method will 42 | # always return a boolean response, so it's safe to call without 43 | # exception handling. 44 | # 45 | # @example Wait until the Artifactory server is ready 46 | # until System.ping 47 | # sleep(0.5) 48 | # print '.' 49 | # end 50 | # 51 | # @param [Hash] options 52 | # the list of options 53 | # 54 | # @option options [Artifactory::Client] :client 55 | # the client object to make the request with 56 | # 57 | # @return [Boolean] 58 | # true if the Artifactory server is ready, false otherwise 59 | # 60 | def ping(options = {}) 61 | client = extract_client!(options) 62 | !!client.get("/api/system/ping") 63 | rescue Error::ConnectionError 64 | false 65 | end 66 | 67 | # 68 | # Get the current system configuration as XML. 69 | # 70 | # @example Get the current configuration 71 | # System.configuration 72 | # 73 | # @param [Hash] options 74 | # the list of options 75 | # 76 | # @option options [Artifactory::Client] :client 77 | # the client object to make the request with 78 | # 79 | # @return [REXML::Document] 80 | # the parsed XML document 81 | # 82 | def configuration(options = {}) 83 | client = extract_client!(options) 84 | response = client.get("/api/system/configuration") 85 | 86 | REXML::Document.new(response) 87 | end 88 | 89 | # 90 | # Update the configuration with the given XML. 91 | # 92 | # @example Update the configuration 93 | # new_config = File.new('/path/to/new.xml') 94 | # System.update_configuration(new_config) 95 | # 96 | # @param [Hash] options 97 | # the list of options 98 | # @param [File] xml 99 | # a pointer to the file descriptor of the XML to upload 100 | # 101 | # @option options [Artifactory::Client] :client 102 | # the client object to make the request with 103 | # 104 | def update_configuration(xml, options = {}) 105 | client = extract_client!(options) 106 | 107 | # The Artifactory api requires a content type of 'application/xml'. 108 | # See http://bit.ly/1l2IvZY 109 | headers = { "Content-Type" => "application/xml" } 110 | client.post("/api/system/configuration", xml, headers) 111 | end 112 | 113 | # 114 | # Get the version information from the server. 115 | # 116 | # @example Get the version information 117 | # System.version #=> { ... } 118 | # 119 | # @param [Hash] options 120 | # the list of options 121 | # 122 | # @option options [Artifactory::Client] :client 123 | # the client object to make the request with 124 | # 125 | # @return [Hash] 126 | # the parsed JSON from the response 127 | # 128 | def version(options = {}) 129 | client = extract_client!(options) 130 | client.get("/api/system/version") 131 | end 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /lib/artifactory/resources/url_base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "rexml/document" unless defined?(REXML::Document) 18 | 19 | module Artifactory 20 | class Resource::URLBase < Resource::Base 21 | class << self 22 | # 23 | # List UrlBase in the system configuration. 24 | # 25 | # @param [Hash] options 26 | # the list of options 27 | # 28 | # @option options [Artifactory::Client] :client 29 | # the client object to make the request with 30 | # 31 | # @return [Array] 32 | # the list of UrlBases 33 | # 34 | def all(options = {}) 35 | config = Resource::System.configuration(options) 36 | simple_text_from_config("config/urlBase", config, options) 37 | end 38 | 39 | # 40 | # Find (fetch) the url base. 41 | # 42 | # @example Find a URLBase by its url_base. 43 | # url_base.find('http://33.33.33.20/artifactory') #=> # 44 | # 45 | # @param [String] url 46 | # the base url to find 47 | # @param [Hash] options 48 | # the list of options 49 | # 50 | # @option options [Artifactory::Client] :client 51 | # the client object to make the request with 52 | # 53 | # @return [Resource::MailServer, nil] 54 | # an instance of the mail server that matches the given host, or +nil+ 55 | # if one does not exist 56 | # 57 | def find(url, options = {}) 58 | config = Resource::System.configuration(options) 59 | find_from_config("config/urlBase[text()='#{url}']", config, options) 60 | rescue Error::HTTPError => e 61 | raise unless e.code == 404 62 | 63 | nil 64 | end 65 | 66 | private 67 | 68 | # 69 | # List all the text elements in the Artifactory configuration file 70 | # matching the given xpath. Ignore any children of elements that match the xpath. 71 | # 72 | # @param [String] xpath 73 | # xpath expression for which matches are to be listed 74 | # 75 | # @param [REXML] config 76 | # Artifactory config as an REXML file 77 | # 78 | # @param [Hash] options 79 | # the list of options 80 | # 81 | def simple_text_from_config(xpath, config, options = {}) 82 | REXML::XPath.match(config, xpath).map do |r| 83 | hash = {} 84 | hash[r.name] = r.text 85 | from_hash(hash, options) 86 | end 87 | end 88 | end 89 | 90 | attribute :url_base, -> { raise "URL base missing!" } 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/artifactory/resources/user.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | class Resource::User < Resource::Base 19 | class << self 20 | # 21 | # Get a list of all users in the system. 22 | # 23 | # @param [Hash] options 24 | # the list of options 25 | # 26 | # @option options [Artifactory::Client] :client 27 | # the client object to make the request with 28 | # 29 | # @return [Array] 30 | # the list of users 31 | # 32 | def all(options = {}) 33 | client = extract_client!(options) 34 | client.get("/api/security/users").map do |hash| 35 | from_url(hash["uri"], client: client) 36 | end 37 | end 38 | 39 | # 40 | # Find (fetch) a user by its name. 41 | # 42 | # @example Find a user by its name 43 | # User.find('readers') #=> # 44 | # 45 | # @param [String] name 46 | # the name of the user to find 47 | # @param [Hash] options 48 | # the list of options 49 | # 50 | # @option options [Artifactory::Client] :client 51 | # the client object to make the request with 52 | # 53 | # @return [Resource::User, nil] 54 | # an instance of the user that matches the given name, or +nil+ 55 | # if one does not exist 56 | # 57 | def find(name, options = {}) 58 | client = extract_client!(options) 59 | 60 | response = client.get("/api/security/users/#{url_safe(name)}") 61 | from_hash(response, client: client) 62 | rescue Error::HTTPError => e 63 | raise unless e.code == 404 64 | 65 | nil 66 | end 67 | end 68 | 69 | attribute :admin, false 70 | attribute :email 71 | attribute :groups, [] 72 | attribute :internal_password_disabled, false 73 | attribute :last_logged_in 74 | attribute :name, -> { raise "Name missing" } 75 | attribute :password # write only, never returned 76 | attribute :profile_updatable, true 77 | attribute :realm 78 | 79 | # 80 | # Delete this user from artifactory, suppressing any +ResourceNotFound+ 81 | # exceptions might occur. 82 | # 83 | # @return [Boolean] 84 | # true if the object was deleted successfully, false otherwise 85 | # 86 | def delete 87 | client.delete(api_path) 88 | true 89 | rescue Error::HTTPError => e 90 | false 91 | end 92 | 93 | # 94 | # Creates or updates a user configuration depending on if the 95 | # user configuration previously existed. 96 | # 97 | # @return [Boolean] 98 | # 99 | def save 100 | if self.class.find(name, client: client) 101 | client.post(api_path, to_json, headers) 102 | else 103 | client.put(api_path, to_json, headers) 104 | end 105 | true 106 | end 107 | 108 | private 109 | 110 | # 111 | # The path to this user on the server. 112 | # 113 | # @return [String] 114 | # 115 | def api_path 116 | @api_path ||= "/api/security/users/#{url_safe(name)}" 117 | end 118 | 119 | # 120 | # The default headers for this object. This includes the +Content-Type+. 121 | # 122 | # @return [Hash] 123 | # 124 | def headers 125 | @headers ||= { 126 | "Content-Type" => "application/vnd.org.jfrog.artifactory.security.User+json", 127 | } 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /lib/artifactory/util.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | module Util 19 | extend self 20 | 21 | # 22 | # Covert the given CaMelCaSeD string to under_score. Graciously borrowed 23 | # from http://stackoverflow.com/questions/1509915. 24 | # 25 | # @param [String] string 26 | # the string to use for transformation 27 | # 28 | # @return [String] 29 | # 30 | def underscore(string) 31 | string 32 | .to_s 33 | .gsub(/::/, "/") 34 | .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') 35 | .gsub(/([a-z\d])([A-Z])/, '\1_\2') 36 | .tr("-", "_") 37 | .downcase 38 | end 39 | 40 | # 41 | # Convert an underscored string to it's camelcase equivalent constant. 42 | # 43 | # @param [String] string 44 | # the string to convert 45 | # 46 | # @return [String] 47 | # 48 | def camelize(string, lowercase = false) 49 | result = string 50 | .to_s 51 | .split("_") 52 | .map(&:capitalize) 53 | .join 54 | 55 | if lowercase 56 | result[0, 1].downcase + result[1..-1] 57 | else 58 | result 59 | end 60 | end 61 | 62 | # 63 | # Truncate the given string to a certain number of characters. 64 | # 65 | # @param [String] string 66 | # the string to truncate 67 | # @param [Hash] options 68 | # the list of options (such as +length+) 69 | # 70 | def truncate(string, options = {}) 71 | length = options[:length] || 30 72 | 73 | if string.length > length 74 | string[0..length - 3] + "..." 75 | else 76 | string 77 | end 78 | end 79 | 80 | # 81 | # Rename a list of keys to the given map. 82 | # 83 | # @example Rename the given keys 84 | # rename_keys(hash, foo: :bar, zip: :zap) 85 | # 86 | # @param [Hash] options 87 | # the options to map 88 | # @param [Hash] map 89 | # the map of keys to map 90 | # 91 | # @return [Hash] 92 | # 93 | def rename_keys(options, map = {}) 94 | Hash[options.map { |k, v| [map[k] || k, v] }] 95 | end 96 | 97 | # 98 | # Slice the given list of options with the given keys. 99 | # 100 | # @param [Hash] options 101 | # the list of options to slice 102 | # @param [Array] keys 103 | # the keys to slice 104 | # 105 | # @return [Hash] 106 | # the sliced hash 107 | # 108 | def slice(options, *keys) 109 | keys.inject({}) do |hash, key| 110 | hash[key] = options[key] if options[key] 111 | hash 112 | end 113 | end 114 | 115 | # 116 | # Flatten an xml element with at most one child node with children 117 | # into a hash. 118 | # 119 | # @param [REXML] element 120 | # xml element 121 | # 122 | def xml_to_hash(element, child_with_children = "", unique_children = true) 123 | properties = {} 124 | element.each_element_with_text do |e| 125 | if e.name.eql?(child_with_children) 126 | if unique_children 127 | e.each_element_with_text do |t| 128 | properties[t.name] = to_type(t.text) 129 | end 130 | else 131 | children = [] 132 | e.each_element_with_text do |t| 133 | properties[t.name] = children.push(to_type(t.text)) 134 | end 135 | end 136 | else 137 | properties[e.name] = to_type(e.text) 138 | end 139 | end 140 | properties 141 | end 142 | 143 | def to_type(string) 144 | return true if string.eql?("true") 145 | return false if string.eql?("false") 146 | return string.to_i if numeric?(string) 147 | 148 | string 149 | end 150 | 151 | private 152 | 153 | def numeric?(string) 154 | string.to_i.to_s == string || string.to_f.to_s == string 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /lib/artifactory/version.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014-2018 Chef Software, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module Artifactory 18 | VERSION = "3.0.17".freeze 19 | end 20 | -------------------------------------------------------------------------------- /spec/integration/resources/artifact_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Artifact, :integration do 5 | shared_examples "an artifact search endpoint" do |name, options = {}| 6 | it "finds artifacts by #{name}" do 7 | response = described_class.send(name, options) 8 | expect(response).to be_a(Array) 9 | expect(response.size).to eq(3) 10 | 11 | artifact_1 = response[0] 12 | expect(artifact_1).to be_a(described_class) 13 | expect(artifact_1.repo).to eq("libs-release-local") 14 | 15 | artifact_2 = response[1] 16 | expect(artifact_2).to be_a(described_class) 17 | expect(artifact_2.repo).to eq("ext-release-local") 18 | 19 | artifact_3 = response[2] 20 | expect(artifact_3).to be_a(described_class) 21 | expect(artifact_3.repo).to eq("bin-release-local") 22 | end 23 | 24 | it "finds artifacts by repo" do 25 | response = described_class.send(name, options.merge(repos: "libs-release-local")) 26 | expect(response).to be_a(Array) 27 | expect(response.size).to eq(1) 28 | 29 | artifact = response.first 30 | expect(artifact).to be_a(described_class) 31 | expect(artifact.repo).to eq("libs-release-local") 32 | end 33 | end 34 | 35 | describe ".properties" do 36 | let(:artifact) do 37 | artifact = described_class.send(:search, repos: "libs-properties-local", name: "artifact.deb").first 38 | end 39 | 40 | it "returns artifact properties when reading" do 41 | expect(artifact.properties).to include({ "licenses" => [ "Apache 2" ] }) 42 | end 43 | 44 | it "writes artifact properties" do 45 | expect(artifact.properties({ author: "Jörg", "status" => "public" })).to eq({ "licenses" => [ "Apache 2" ], "author" => "Jörg", "status" => "public" }) 46 | end 47 | end 48 | 49 | describe ".search" do 50 | it_behaves_like "an artifact search endpoint", :search, name: "artifact.deb" 51 | end 52 | 53 | describe ".gavc_search" do 54 | it_behaves_like "an artifact search endpoint", :gavc_search, { 55 | group: "org.acme", 56 | name: "artifact.deb", 57 | version: "1.0", 58 | classifier: "sources", 59 | } 60 | end 61 | 62 | describe ".property_search" do 63 | it_behaves_like "an artifact search endpoint", :property_search, { 64 | branch: "master", 65 | committer: "Seth Vargo", 66 | } 67 | end 68 | 69 | describe ".checksum_search" do 70 | it_behaves_like "an artifact search endpoint", :checksum_search, md5: "abcd1234" 71 | end 72 | 73 | describe ".usage_search" do 74 | it_behaves_like "an artifact search endpoint", :usage_search, notUsedSince: "1414800000" 75 | end 76 | 77 | describe ".creation_search" do 78 | it_behaves_like "an artifact search endpoint", :creation_search, from: "1414800000" 79 | end 80 | 81 | describe ".versions" do 82 | it "returns an array of versions" do 83 | response = described_class.versions 84 | expect(response).to be_a(Array) 85 | expect(response.size).to eq(3) 86 | 87 | expect(response[0]["version"]).to eq("1.2") 88 | end 89 | end 90 | 91 | describe ".latest_version" do 92 | it "finds the latest version of the artifact" do 93 | response = described_class.latest_version 94 | expect(response).to be_a(String) 95 | expect(response).to eq("1.0-201203131455-2") 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /spec/integration/resources/backup.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Backup, :integration do 5 | describe ".all" do 6 | it "returns an array of Backups" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | end 12 | 13 | describe ".find" do 14 | it "finds a Backup by key" do 15 | backup = described_class.find("backup-daily") 16 | 17 | expect(backup).to be_a(described_class) 18 | expect(backup.key).to eq("backup-daily") 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/resources/build_component_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::BuildComponent, :integration do 5 | let(:build_component) { described_class.find("wicket") } 6 | 7 | describe ".all" do 8 | it "returns an array of build component objects" do 9 | results = described_class.all 10 | expect(results).to be_a(Array) 11 | expect(results.first).to be_a(described_class) 12 | end 13 | end 14 | 15 | describe ".find" do 16 | it "finds a build component by name" do 17 | expect(build_component).to be_a(described_class) 18 | expect(build_component.name).to eq("wicket") 19 | expect(build_component.last_started).to eq(Time.parse("2015-06-19T20:13:20.222Z")) 20 | end 21 | end 22 | 23 | describe "#delete" do 24 | let(:build_numbers) { %w{ 51 52 } } 25 | let(:delete_all) { false } 26 | 27 | it "deletes the specified builds from the component" do 28 | 29 | expect(build_component.delete( 30 | build_numbers: build_numbers, 31 | delete_all: delete_all 32 | )).to be_truthy 33 | end 34 | 35 | context "no build numbers provided" do 36 | let(:build_numbers) { nil } 37 | 38 | it "deletes no builds" do 39 | 40 | expect(build_component.delete( 41 | delete_all: delete_all 42 | )).to be_falsey 43 | end 44 | 45 | context "the delete_all flag is true" do 46 | let(:delete_all) { true } 47 | 48 | it "deletes the component and all builds" do 49 | expect(build_component.delete( 50 | delete_all: delete_all 51 | )).to be_truthy 52 | end 53 | end 54 | end 55 | end 56 | 57 | describe "#rename" do 58 | it "renames the build component on the server" do 59 | 60 | expect(build_component.rename("fricket")).to be_truthy 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/integration/resources/build_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Build, :integration do 5 | describe ".all" do 6 | it "returns an array of build objects" do 7 | results = described_class.all("wicket") 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | end 12 | 13 | describe ".find" do 14 | it "finds a build by component and number" do 15 | build = described_class.find("wicket", 51) 16 | 17 | expect(build).to be_a(described_class) 18 | expect(build.name).to eq("wicket") 19 | expect(build.number).to eq("51") 20 | expect(build.started).to eq(Time.parse("2014-09-30T12:00:19.893+0300")) 21 | expect(build.statuses.first).to include("status" => "promoted") 22 | end 23 | end 24 | 25 | describe "#save" do 26 | it "saves the build data to the server" do 27 | build = described_class.new( 28 | name: "fricket", 29 | number: "1", 30 | properties: { 31 | "buildInfo.env.JAVA_HOME" => "/usr/jdk/latest", 32 | } 33 | ) 34 | 35 | expect(build.save).to be_truthy 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/integration/resources/certificate_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Certificate, :integration do 5 | describe ".all" do 6 | it "returns an array of certificate objects" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/integration/resources/group_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Group, :integration do 5 | describe ".all" do 6 | it "returns an array of group objects" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | 12 | it "includes the information from the server" do 13 | results = described_class.all 14 | readers = results[0] 15 | leads = results[1] 16 | 17 | expect(readers.name).to eq("readers") 18 | expect(leads.name).to eq("tech-leads") 19 | end 20 | end 21 | 22 | describe ".find" do 23 | it "finds a group by name" do 24 | readers = described_class.find("readers") 25 | 26 | expect(readers).to be_a(described_class) 27 | expect(readers.name).to eq("readers") 28 | end 29 | end 30 | 31 | describe "#delete" do 32 | it "deletes the group from the server" do 33 | readers = described_class.find("readers") 34 | expect(readers.delete).to be_truthy 35 | end 36 | end 37 | 38 | describe "#save" do 39 | it "saves the group to the server" do 40 | group = described_class.new(name: "testing") 41 | expect(group.save).to be_truthy 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/integration/resources/layout_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Layout, :integration do 5 | describe ".all" do 6 | it "returns an array of Layouts" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | end 12 | 13 | describe ".find" do 14 | it "finds a layout by name" do 15 | mvn_layout = described_class.find("maven-2-default") 16 | 17 | expect(mvn_layout).to be_a(described_class) 18 | expect(mvn_layout.name).to eq("maven-2-default") 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/resources/ldap_setting_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::LDAPSetting, :integration do 5 | describe ".all" do 6 | it "returns an array of LdapSetting" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | end 12 | 13 | describe ".find" do 14 | it "finds an LdapSetting by key" do 15 | ldap = described_class.find("example-ldap") 16 | 17 | expect(ldap).to be_a(described_class) 18 | expect(ldap.key).to eq("example-ldap") 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/resources/mail_server_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::MailServer, :integration do 5 | describe ".all" do 6 | it "returns an array of MailServer" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | end 12 | 13 | describe ".find" do 14 | it "finds a MailServer by host" do 15 | smtp = described_class.find("smtp.gmail.com") 16 | 17 | expect(smtp).to be_a(described_class) 18 | expect(smtp.host).to eq("smtp.gmail.com") 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/resources/permission_target_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::PermissionTarget, :integration do 5 | describe ".all" do 6 | it "returns an array of PermissionTarget objects" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | 12 | it "includes the information from the server" do 13 | results = described_class.all 14 | anything = results[0] 15 | any_remote = results[1] 16 | 17 | expect(anything.name).to eq("Anything") 18 | expect(any_remote.name).to eq("Any Remote") 19 | end 20 | end 21 | 22 | describe ".find" do 23 | it "finds a permission target by name" do 24 | readers = described_class.find("Anything") 25 | 26 | expect(readers).to be_a(described_class) 27 | expect(readers.name).to eq("Anything") 28 | end 29 | end 30 | 31 | describe "#delete" do 32 | it "deletes the permission target from the server" do 33 | readers = described_class.find("Anything") 34 | expect(readers.delete).to be_truthy 35 | end 36 | end 37 | 38 | describe "#save" do 39 | it "saves the permission target to the server" do 40 | target = described_class.new(name: "testing") 41 | expect(target.save).to be_truthy 42 | end 43 | end 44 | 45 | describe "#users" do 46 | it "displays the users hash with sorted verbose permissions" do 47 | target = described_class.find("Any Remote") 48 | expect(target.users).to eq( { "anonymous" => %w{deploy read} } ) 49 | end 50 | end 51 | 52 | describe "#groups" do 53 | it "displays the groups hash with sorted verbose permissions" do 54 | target = described_class.find("Anything") 55 | expect(target.groups).to eq( { "readers" => %w{admin read} } ) 56 | end 57 | end 58 | 59 | describe "#users=" do 60 | it "sets the users hash" do 61 | target = described_class.find("Any Remote") 62 | target.users = { "admins" => ["admin"] } 63 | expect(target.users).to eq( { "admins" => ["admin"] }) 64 | end 65 | end 66 | 67 | describe "#groups=" do 68 | it "sets the groups hash" do 69 | target = described_class.find("Anything") 70 | target.groups = { "deployers" => ["deploy"] } 71 | expect(target.groups).to eq( { "deployers" => ["deploy"] }) 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/integration/resources/repository_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Repository, :integration do 5 | describe ".all" do 6 | it "returns an array of repository objects" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | end 12 | 13 | describe ".find" do 14 | it "finds a repository by key" do 15 | repository = described_class.find("libs-snapshots-local") 16 | 17 | expect(repository).to be_a(described_class) 18 | expect(repository.key).to eq("libs-snapshots-local") 19 | expect(repository.max_unique_snapshots).to eq(10) 20 | expect(repository.package_type).to eql("generic") 21 | end 22 | 23 | it "finds debian repositories" do 24 | repo = "libs-debian-local" 25 | 26 | repository = described_class.find(repo) 27 | 28 | expect(repository.package_type).to eql("debian") 29 | expect(repository.key).to eql(repo) 30 | end 31 | end 32 | 33 | describe "#save" do 34 | it "saves the repository to the server" do 35 | repository = described_class.new(key: "libs-testing-local") 36 | expect(repository.save).to be_truthy 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/integration/resources/system_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::System, :integration do 5 | describe ".info" do 6 | it "gets the system information" do 7 | expect(described_class.info).to eq("This is some serious system info right here") 8 | end 9 | end 10 | 11 | describe ".ping" do 12 | it "returns ok" do 13 | expect(described_class.ping).to be_truthy 14 | end 15 | end 16 | 17 | describe ".configuration" do 18 | it "returns the system configuration" do 19 | expect(described_class.configuration).to be_a(REXML::Document) 20 | end 21 | end 22 | 23 | describe ".update_configuration" do 24 | let(:tempfile) { Tempfile.new(%w{config xml}) } 25 | let(:content) do 26 | <<-EOH.strip.gsub(/^ {10}/, "") 27 | 28 | true 29 | EOH 30 | end 31 | 32 | after do 33 | tempfile.close 34 | tempfile.unlink 35 | end 36 | 37 | it "posts the new configuration to the server" do 38 | tempfile.write(content) 39 | tempfile.rewind 40 | 41 | expect(described_class.update_configuration(tempfile)).to eq(content) 42 | end 43 | end 44 | 45 | describe ".version" do 46 | it "gets the version information" do 47 | expect(described_class.version).to eq({ 48 | "version" => "3.1.0", 49 | "revision" => "30062", 50 | "addons" => %w{ 51 | ldap 52 | license 53 | yum}, 54 | }) 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/integration/resources/url_base_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::URLBase, :integration do 5 | describe ".all" do 6 | it "returns an array of UrlBases" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | end 12 | 13 | describe ".find" do 14 | it "finds a urlBase by url" do 15 | base = described_class.find("http://33.33.33.20/artifactory") 16 | 17 | expect(base).to be_a(described_class) 18 | expect(base.url_base).to eq("http://33.33.33.20/artifactory") 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/resources/user_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::User, :integration do 5 | describe ".all" do 6 | it "returns an array of user objects" do 7 | results = described_class.all 8 | expect(results).to be_a(Array) 9 | expect(results.first).to be_a(described_class) 10 | end 11 | 12 | it "includes the information from the server" do 13 | results = described_class.all 14 | seth = results[0] 15 | yvonne = results[1] 16 | 17 | expect(seth.name).to eq("sethvargo") 18 | expect(yvonne.name).to eq("yzl") 19 | end 20 | end 21 | 22 | describe ".find" do 23 | it "finds a user by name" do 24 | seth = described_class.find("sethvargo") 25 | 26 | expect(seth).to be_a(described_class) 27 | expect(seth.name).to eq("sethvargo") 28 | end 29 | end 30 | 31 | describe "#delete" do 32 | it "deletes the user from the server" do 33 | sethvargo = described_class.find("sethvargo") 34 | expect(sethvargo.delete).to be_truthy 35 | end 36 | end 37 | 38 | describe "#save" do 39 | it "saves the user to the server" do 40 | user = described_class.new(name: "schisamo") 41 | expect(user.save).to be_truthy 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "rspec" 3 | require "webmock/rspec" 4 | 5 | if ENV["COVERAGE"] 6 | require "simplecov" 7 | require "simplecov-console" 8 | SimpleCov.start do 9 | add_filter "spec/" 10 | formatter SimpleCov::Formatter::MultiFormatter.new [ 11 | SimpleCov::Formatter::HTMLFormatter, 12 | SimpleCov::Formatter::Console, 13 | ] 14 | end 15 | end 16 | 17 | # Require our main library 18 | require "artifactory" 19 | 20 | # Require helpers 21 | require_relative "support/api_server" 22 | 23 | RSpec.configure do |config| 24 | # Custom helper modules and extensions 25 | 26 | # Prohibit using the should syntax 27 | config.expect_with :rspec do |spec| 28 | spec.syntax = :expect 29 | end 30 | 31 | # Allow tests to isolate a specific test using +focus: true+. If nothing 32 | # is focused, then all tests are executed. 33 | config.filter_run(focus: true) 34 | config.run_all_when_everything_filtered = true 35 | 36 | # Stuff to do on each run 37 | config.before(:each) { Artifactory.reset! } 38 | config.after(:each) { Artifactory.reset! } 39 | 40 | config.before(:each, :integration) do 41 | Artifactory.endpoint = "http://localhost:8889" 42 | Artifactory.username = nil 43 | Artifactory.password = nil 44 | stub_request(:any, /#{Artifactory.endpoint}/).to_rack(Artifactory::APIServer) 45 | end 46 | 47 | # Run specs in random order to surface order dependencies. If you find an 48 | # order dependency and want to debug it, you can fix the order by providing 49 | # the seed, which is printed after each run. 50 | # --seed 1234 51 | config.order = "random" 52 | end 53 | -------------------------------------------------------------------------------- /spec/support/api_server.rb: -------------------------------------------------------------------------------- 1 | require "sinatra/base" 2 | 3 | module Artifactory 4 | # 5 | # An in-memory, fully-API compliant Artifactory server. The data is all 6 | # fake, but it is based off of real responses from the Artifactory server. 7 | # 8 | class APIServer < Sinatra::Base 9 | require_relative "api_server/artifact_endpoints" 10 | require_relative "api_server/build_component_endpoints" 11 | require_relative "api_server/build_endpoints" 12 | require_relative "api_server/group_endpoints" 13 | require_relative "api_server/repository_endpoints" 14 | require_relative "api_server/permission_target_endpoints" 15 | require_relative "api_server/status_endpoints" 16 | require_relative "api_server/system_endpoints" 17 | require_relative "api_server/user_endpoints" 18 | 19 | register APIServer::ArtifactEndpoints 20 | register APIServer::BuildComponentEndpoints 21 | register APIServer::BuildEndpoints 22 | register APIServer::GroupEndpoints 23 | register APIServer::PermissionTargetEndpoints 24 | register APIServer::RepositoryEndpoints 25 | register APIServer::StatusEndpoints 26 | register APIServer::SystemEndpoints 27 | register APIServer::UserEndpoints 28 | 29 | private 30 | 31 | # 32 | # This server's URL, returned as a {Pathname} for easy joining. 33 | # 34 | # @example Construct a server url 35 | # server_url.join('libs-release-local', 'artifact.deb') 36 | # 37 | # @return [Pathname] 38 | # 39 | def server_url 40 | @server_url ||= begin 41 | scheme = request.env["rack.url_scheme"] 42 | address = request.env["SERVER_NAME"] 43 | port = request.env["SERVER_PORT"] 44 | 45 | Pathname.new("#{scheme}://#{address}:#{port}") 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/support/api_server/artifact_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::ArtifactEndpoints 3 | def self.registered(app) 4 | artifact_properties = { "licenses" => [ "Apache 2" ] } 5 | 6 | app.get("/api/search/artifact") do 7 | content_type "application/vnd.org.jfrog.artifactory.search.ArtifactSearchResult+json" 8 | artifacts_for_conditions do 9 | params["name"] == "artifact.deb" 10 | end 11 | end 12 | 13 | app.get("/api/search/gavc") do 14 | content_type "application/vnd.org.jfrog.artifactory.search.GavcSearchResult+json" 15 | artifacts_for_conditions do 16 | params["g"] == "org.acme" && 17 | params["a"] == "artifact.deb" && 18 | params["v"] == "1.0" && 19 | params["c"] == "sources" 20 | end 21 | end 22 | 23 | app.get("/api/search/prop") do 24 | content_type "application/vnd.org.jfrog.artifactory.search.MetadataSearchResult+json" 25 | artifacts_for_conditions do 26 | params["branch"] == "master" && params["committer"] == "Seth Vargo" 27 | end 28 | end 29 | 30 | app.get("/api/search/checksum") do 31 | content_type "application/vnd.org.jfrog.artifactory.search.ChecksumSearchResult+json" 32 | artifacts_for_conditions do 33 | params["md5"] == "abcd1234" 34 | end 35 | end 36 | 37 | app.get("/api/search/usage") do 38 | content_type "application/vnd.org.jfrog.artifactory.search.ArtifactUsageResult+json" 39 | artifacts_for_conditions do 40 | params["notUsedSince"] == "1414800000" 41 | end 42 | end 43 | 44 | app.get("/api/search/creation") do 45 | content_type "application/vnd.org.jfrog.artifactory.search.ArtifactCreationResult+json" 46 | artifacts_for_conditions do 47 | params["from"] == "1414800000" 48 | end 49 | end 50 | 51 | app.get("/api/search/versions") do 52 | content_type "application/vnd.org.jfrog.artifactory.search.ArtifactVersionsResult+json" 53 | JSON.fast_generate( 54 | "results" => [ 55 | { "version" => "1.2", "integration" => false }, 56 | { "version" => "1.0-SNAPSHOT", "integration" => true }, 57 | { "version" => "1.0", "integration" => false }, 58 | ] 59 | ) 60 | end 61 | 62 | app.get("/api/search/latestVersion") do 63 | "1.0-201203131455-2" 64 | end 65 | 66 | app.get("/api/storage/libs-release-local/org/acme/artifact.deb") do 67 | content_type "application/vnd.org.jfrog.artifactory.storage.FileInfo+json" 68 | JSON.fast_generate( 69 | "uri" => server_url.join("/api/storage/libs-release-local/org/acme/artifact.deb"), 70 | "downloadUri" => server_url.join("/artifactory/libs-release-local/org/acme/artifact.deb"), 71 | "repo" => "libs-release-local", 72 | "path" => "/org/acme/artifact.deb", 73 | "created" => Time.parse("1991-07-23 12:07am"), 74 | "createdBy" => "schisamo", 75 | "lastModified" => Time.parse("2013-12-24 11:50pm"), 76 | "modifiedBy" => "sethvargo", 77 | "lastUpdated" => Time.parse("2014-01-01 1:00pm"), 78 | "size" => "1024", 79 | "mimeType" => "application/tgz", 80 | "checksums" => { 81 | "md5" => "MD5123", 82 | "sha" => "SHA456", 83 | }, 84 | "originalChecksums" => { 85 | "md5" => "MD5123", 86 | "sha" => "SHA456", 87 | } 88 | ) 89 | end 90 | 91 | app.get("/api/storage/libs-properties-local/org/acme/artifact.deb") do 92 | content_type "application/vnd.org.jfrog.artifactory.storage.ItemProperties+json" 93 | JSON.fast_generate( 94 | "properties" => artifact_properties, 95 | "uri" => server_url.join("/api/storage/libs-properties-local/org/acme/artifact.deb") 96 | ) 97 | end 98 | 99 | app.put("/api/storage/libs-properties-local/org/acme/artifact.deb") do 100 | props = params["properties"].split(";").reject(&:empty?).map { |e| e.split("=") }.to_h 101 | artifact_properties.merge!(props) 102 | 103 | status 204 104 | body "" 105 | end 106 | 107 | app.get("/api/storage/ext-release-local/org/acme/artifact.deb") do 108 | content_type "application/vnd.org.jfrog.artifactory.storage.FileInfo+json" 109 | JSON.fast_generate( 110 | "uri" => server_url.join("/api/storage/ext-release-local/org/acme/artifact.deb"), 111 | "downloadUri" => server_url.join("/artifactory/ext-release-local/org/acme/artifact.deb"), 112 | "repo" => "ext-release-local", 113 | "path" => "/org/acme/artifact.deb", 114 | "created" => Time.parse("1995-04-11 11:05am"), 115 | "createdBy" => "yzl", 116 | "lastModified" => Time.parse("2013-11-10 10:10pm"), 117 | "modifiedBy" => "schisamo", 118 | "lastUpdated" => Time.parse("2014-02-02 2:00pm"), 119 | "size" => "1024", 120 | "mimeType" => "application/tgz", 121 | "checksums" => { 122 | "md5" => "MD5789", 123 | "sha" => "SHA101", 124 | }, 125 | "originalChecksums" => { 126 | "md5" => "MD5789", 127 | "sha" => "SHA101", 128 | } 129 | ) 130 | end 131 | 132 | app.get("/api/storage/bin-release-local/org/acme/artifact 1.0.0.msi") do 133 | content_type "application/vnd.org.jfrog.artifactory.storage.FileInfo+json" 134 | JSON.fast_generate( 135 | "uri" => server_url.join("/api/storage/bin-release-local/org/acme/artifact 1.0.0.msi"), 136 | "downloadUri" => server_url.join("/artifactory/bin-release-local/org/acme/artifact 1.0.0.msi"), 137 | "repo" => "bin-release-local", 138 | "path" => "/org/acme/artifact 1.0.0.msi", 139 | "created" => Time.parse("1995-04-11 11:05am"), 140 | "createdBy" => "yzl", 141 | "lastModified" => Time.parse("2013-11-10 10:10pm"), 142 | "modifiedBy" => "schisamo", 143 | "lastUpdated" => Time.parse("2014-02-02 2:00pm"), 144 | "size" => "1024", 145 | "mimeType" => "application/octet-stream", 146 | "checksums" => { 147 | "md5" => "MD5789", 148 | "sha" => "SHA101", 149 | }, 150 | "originalChecksums" => { 151 | "md5" => "MD5789", 152 | "sha" => "SHA101", 153 | } 154 | ) 155 | end 156 | 157 | app.class_eval do 158 | def artifacts_for_conditions(&block) 159 | if yield 160 | if params["repos"] == "libs-release-local" 161 | JSON.fast_generate( 162 | "results" => [ 163 | { "uri" => server_url.join("/api/storage/libs-release-local/org/acme/artifact.deb") }, 164 | ] 165 | ) 166 | elsif params["repos"] == "libs-properties-local" 167 | JSON.fast_generate( 168 | "results" => [ 169 | { "uri" => server_url.join("/api/storage/libs-properties-local/org/acme/artifact.deb") }, 170 | ] 171 | ) 172 | else 173 | JSON.fast_generate( 174 | "results" => [ 175 | { "uri" => server_url.join("/api/storage/libs-release-local/org/acme/artifact.deb") }, 176 | { "uri" => server_url.join("/api/storage/ext-release-local/org/acme/artifact.deb") }, 177 | { "uri" => server_url.join("/api/storage/bin-release-local/org/acme/artifact 1.0.0.msi") }, 178 | ] 179 | ) 180 | end 181 | end 182 | end 183 | end 184 | end 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /spec/support/api_server/build_component_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::BuildComponentEndpoints 3 | def self.registered(app) 4 | app.get("/api/build") do 5 | content_type "application/vnd.org.jfrog.build.Builds+json" 6 | JSON.fast_generate( 7 | "uri" => server_url.join("/api/build"), 8 | "builds" => [ 9 | { 10 | "uri" => "/wicket", 11 | "lastStarted" => "2015-06-19T20:13:20.222Z", 12 | }, 13 | { 14 | "uri" => "/jackrabbit", 15 | "lastStarted" => "2015-06-20T20:13:20.333Z", 16 | }, 17 | ] 18 | ) 19 | end 20 | 21 | app.post("/api/build/rename/:name") do 22 | content_type "text/plain" 23 | body "Build renaming of '#{params["name"]}' to '#{params["to"]}' was successfully started." 24 | end 25 | 26 | app.delete("/api/build/:name") do 27 | content_type "text/plain" 28 | 29 | if params["deleteAll"] 30 | body "All '#{params["name"]}' builds have been deleted successfully." 31 | elsif params["buildNumbers"].nil? 32 | status 400 33 | body "Please provide at least one build number to delete." 34 | else 35 | message = params["buildNumbers"].split(",").map do |n| 36 | "#{params["name"]}##{n}" 37 | end.join(", ") 38 | 39 | body "The following builds has been deleted successfully: #{message}." 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/support/api_server/build_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::BuildEndpoints 3 | def self.registered(app) 4 | app.get("/api/build/wicket") do 5 | content_type "application/vnd.org.jfrog.build.BuildsByName+json" 6 | JSON.fast_generate( 7 | "uri" => server_url.join("/api/build/wicket"), 8 | "buildsNumbers" => [ 9 | { 10 | "uri" => "/51", 11 | "started" => "2014-09-30T12:00:19.893+0300", 12 | }, 13 | ] 14 | ) 15 | end 16 | 17 | app.get("/api/build/wicket/51") do 18 | content_type "application/vnd.org.jfrog.build.BuildInfo+json" 19 | JSON.fast_generate( 20 | "uri" => server_url.join("/api/build/wicket/51"), 21 | "buildInfo" => { 22 | "properties" => { 23 | "buildInfo.env.JAVA_HOME" => "/usr/jdk/latest", 24 | }, 25 | "version" => "1.0.1", 26 | "name" => "wicket", 27 | "number" => "51", 28 | "type" => "MAVEN", 29 | "buildAgent" => { 30 | "name" => "Maven", 31 | "version" => "3.0.5", 32 | }, 33 | "agent" => { 34 | "name" => "Jenkins", 35 | "version" => "1.565.2", 36 | }, 37 | "started" => "2014-09-30T12:00:19.893+0300", 38 | "durationMillis" => 8926, 39 | "artifactoryPrincipal" => "admin", 40 | "url" => "http://localhost:8080/job/Maven2-3/9/", 41 | "vcsRevision" => "83049487ecc61bef3dce798838e7a9457e174a5a", 42 | "vcsUrl" => "https://github.com/aseftel/project-examples", 43 | "licenseControl" => { 44 | "runChecks" => false, 45 | "includePublishedArtifacts" => false, 46 | "autoDiscover" => true, 47 | "scopesList" => "", 48 | "licenseViolationsRecipientsList" => "", 49 | }, 50 | "buildRetention" => { 51 | "count" => -1, 52 | "deleteBuildArtifacts" => true, 53 | "buildNumbersNotToBeDiscarded" => [], 54 | }, 55 | "modules" => [ 56 | { 57 | "properties" => { 58 | "project.build.sourceEncoding" => "UTF-8", 59 | }, 60 | "id" => "org.jfrog.test:multi:2.19-SNAPSHOT", 61 | "artifacts" => [ 62 | { 63 | "type" => "pom", 64 | "sha1" => "045b66ebbf8504002b626f592d087612aca36582", 65 | "md5" => "c25542a353dab1089cd186465dc47a68", 66 | "name" => "multi-2.19-SNAPSHOT.pom", 67 | }, 68 | ], 69 | }, 70 | { 71 | "properties" => { 72 | "project.build.sourceEncoding" => "UTF-8", 73 | }, 74 | "id" => "org.jfrog.test:multi1:2.19-SNAPSHOT", 75 | "artifacts" => [ 76 | { 77 | "type" => "jar", 78 | "sha1" => "f4c5c9cb3091011ec2a895b3dedd7f10d847361c", 79 | "md5" => "d1fd850a3582efba41092c624e0b46b8", 80 | "name" => "multi1-2.19-SNAPSHOT.jar", 81 | }, 82 | { 83 | "type" => "pom", 84 | "sha1" => "2ddbf9824676f548d637726d3bcbb494ba823090", 85 | "md5" => "a64aa7f305f63a85e63a0155ff0fb404", 86 | "name" => "multi1-2.19-SNAPSHOT.pom", 87 | }, 88 | { 89 | "type" => "jar", 90 | "sha1" => "6fdd143a44cea3a2636660c5c266c95c27e50abc", 91 | "md5" => "12a1e438f4bef8c4b740fe848a1704a4", 92 | "id" => "org.slf4j:slf4j-simple:1.4.3", 93 | "scopes" => [ "compile" ], 94 | }, 95 | { 96 | "type" => "jar", 97 | "sha1" => "496e91f7df8a0417e00cecdba840cdf0e5f2472c", 98 | "md5" => "76a412a37c9d18659d2dacccdb1c24ff", 99 | "id" => "org.jenkins-ci.lib:dry-run-lib:0.1", 100 | "scopes" => [ "compile" ], 101 | }, 102 | ], 103 | }, 104 | { 105 | "properties" => { 106 | "daversion" => "2.19-SNAPSHOT", 107 | "project.build.sourceEncoding" => "UTF-8", 108 | }, 109 | "id" => "org.jfrog.test:multi2:2.19-SNAPSHOT", 110 | "artifacts" => [ 111 | { 112 | "type" => "txt", 113 | "name" => "multi2-2.19-SNAPSHOT.txt", 114 | }, 115 | { 116 | "type" => "java-source-jar", 117 | "sha1" => "49108b0c7db5fdb4efe3c29a5a9f54e806aecb62", 118 | "md5" => "0e2c5473cf2a9b694afb4a2e8da34b53", 119 | "name" => "multi2-2.19-SNAPSHOT-sources.jar" }, 120 | { 121 | "type" => "jar", 122 | "sha1" => "476e89d290ae36dabb38ff22f75f264ae019d542", 123 | "md5" => "fa9b3df58ac040fffcff9310f261be80", 124 | "name" => "multi2-2.19-SNAPSHOT.jar", 125 | }, 126 | { 127 | "type" => "pom", 128 | "sha1" => "b719b90364e5ae38cda358072f61f821bdae5d5d", 129 | "md5" => "8d5060005235d75907baca4490cf60bf", 130 | "name" => "multi2-2.19-SNAPSHOT.pom", 131 | }, 132 | ], 133 | "dependencies" => [ 134 | { 135 | "type" => "jar", 136 | "sha1" => "19d4e90b43059058f6e056f794f0ea4030d60b86", 137 | "md5" => "dcd95bcb84b09897b2b66d4684c040da", 138 | "id" => "xpp3:xpp3_min:1.1.4c", 139 | "scopes" => [ "provided" ], 140 | }, 141 | { 142 | "type" => "jar", 143 | "sha1" => "e2d866af5518e81282838301b49a1bd2452619d3", 144 | "md5" => "e9e4b59c69305ba3698dd61c5dfc4fc8", 145 | "id" => "org.jvnet.hudson.plugins:perforce:1.3.7", 146 | "scopes" => [ "compile" ], 147 | }, 148 | { 149 | "type" => "jar", 150 | "sha1" => "6fdd143a44cea3a2636660c5c266c95c27e50abc", 151 | "md5" => "12a1e438f4bef8c4b740fe848a1704a4", 152 | "id" => "org.slf4j:slf4j-simple:1.4.3", 153 | "scopes" => [ "compile" ], 154 | }, 155 | { 156 | "type" => "jar", 157 | "sha1" => "496e91f7df8a0417e00cecdba840cdf0e5f2472c", 158 | "md5" => "76a412a37c9d18659d2dacccdb1c24ff", 159 | "id" => "org.jenkins-ci.lib:dry-run-lib:0.1", 160 | "scopes" => [ "compile" ], 161 | }, 162 | ], 163 | }, 164 | { 165 | "properties" => { 166 | "project.build.sourceEncoding" => "UTF-8", 167 | }, 168 | "id" => "org.jfrog.test:multi3:2.19-SNAPSHOT", 169 | "artifacts" => [ 170 | { 171 | "type" => "java-source-jar", 172 | "sha1" => "3cd104785167ac37ef999431f308ffef10810348", 173 | "md5" => "c683276f8dda97078ae8eb5e26bb3ee5", 174 | "name" => "multi3-2.19-SNAPSHOT-sources.jar", 175 | }, 176 | { 177 | "type" => "war", 178 | "sha1" => "34aeebeb805b23922d9d05507404533518cf81e4", 179 | "md5" => "55af06a2175cfb23cc6dc3931475b57c", 180 | "name" => "multi3-2.19-SNAPSHOT.war", 181 | }, 182 | { 183 | "type" => "jar", 184 | "sha1" => "496e91f7df8a0417e00cecdba840cdf0e5f2472c", 185 | "md5" => "76a412a37c9d18659d2dacccdb1c24ff", 186 | "id" => "org.jenkins-ci.lib:dry-run-lib:0.1", 187 | "scopes" => [ "compile" ], 188 | }, 189 | { 190 | "type" => "jar", 191 | "sha1" => "7e9978fdb754bce5fcd5161133e7734ecb683036", 192 | "md5" => "7df83e09e41d742cc5fb20d16b80729c", 193 | "id" => "hsqldb:hsqldb:1.8.0.10", 194 | "scopes" => [ "runtime" ], 195 | }, 196 | ], 197 | }, 198 | ], 199 | "governance" => { 200 | "blackDuckProperties" => { 201 | "runChecks" => false, 202 | "includePublishedArtifacts" => false, 203 | "autoCreateMissingComponentRequests" => false, 204 | "autoDiscardStaleComponentRequests" => false, 205 | }, 206 | }, 207 | "statuses" => [ { 208 | "status" => "promoted", 209 | "comment" => "", 210 | "repository" => "omnibus-stable-local", 211 | "timestamp" => "2017-02-20T12:33:59.570+0100", 212 | "user" => "doge", 213 | "ciUser" => "doge", 214 | "timestampDate" => 1487590439570, 215 | } ], 216 | } 217 | ) 218 | end 219 | 220 | app.put("/api/build") do 221 | status 204 222 | body "" 223 | end 224 | 225 | app.post("/api/build/promote/wicket/51") do 226 | content_type "application/vnd.org.jfrog.artifactory.build.PromotionResult+json" 227 | 228 | JSON.fast_generate( 229 | "uri" => server_url.join("/api/build/wicket"), 230 | "buildsNumbers" => [ 231 | { 232 | "uri" => "/51", 233 | "started" => "2014-09-30T12:00:19.893+0300", 234 | }, 235 | ] 236 | ) 237 | end 238 | end 239 | end 240 | end 241 | -------------------------------------------------------------------------------- /spec/support/api_server/group_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::GroupEndpoints 3 | def self.registered(app) 4 | app.get("/api/security/groups") do 5 | content_type "application/vnd.org.jfrog.artifactory.security.Groups+json" 6 | JSON.fast_generate([ 7 | { 8 | "name" => "readers", 9 | "uri" => server_url.join("/api/security/groups/readers"), 10 | }, 11 | { 12 | "name" => "tech-leads", 13 | "uri" => server_url.join("/api/security/groups/tech-leads"), 14 | }, 15 | ]) 16 | end 17 | 18 | app.get("/api/security/groups/readers") do 19 | content_type "application/vnd.org.jfrog.artifactory.security.Group+json" 20 | JSON.fast_generate( 21 | "name" => "readers", 22 | "description" => "This list of read-only users", 23 | "autoJoin" => true, 24 | "realm" => "artifactory", 25 | "realmAttributes" => nil 26 | ) 27 | end 28 | 29 | app.get("/api/security/groups/tech-leads") do 30 | content_type "application/vnd.org.jfrog.artifactory.security.Group+json" 31 | JSON.fast_generate( 32 | "name" => "tech-leads", 33 | "description" => "This development leads group", 34 | "autoJoin" => false, 35 | "realm" => "artifactory", 36 | "realmAttributes" => nil 37 | ) 38 | end 39 | 40 | app.put("/api/security/groups/:name") do 41 | return 415 unless request.content_type == "application/vnd.org.jfrog.artifactory.security.Group+json" 42 | 43 | # Attempt to parse the response; if this succeeds, all is well... 44 | JSON.parse(request.body.read) 45 | nil 46 | end 47 | 48 | app.delete("/api/security/groups/:name") do 49 | nil 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/support/api_server/permission_target_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::PermissionTargetEndpoints 3 | def self.registered(app) 4 | app.get("/api/security/permissions") do 5 | content_type "application/vnd.org.jfrog.artifactory.security.PermissionTargets+json" 6 | JSON.fast_generate([ 7 | { 8 | "name" => "Anything", 9 | "uri" => server_url.join("/api/security/permissions/Anything"), 10 | }, 11 | { 12 | "name" => "Any Remote", 13 | "uri" => server_url.join("/api/security/permissions/Any%20Remote"), 14 | }, 15 | ]) 16 | end 17 | 18 | app.get("/api/security/permissions/Any%20Remote") do 19 | content_type "application/vnd.org.jfrog.artifactory.security.PermissionTargets+json" 20 | JSON.fast_generate( 21 | "name" => "Any Remote", 22 | "includes_pattern" => nil, 23 | "excludes_pattern" => "", 24 | "repositories" => ["ANY REMOTE"], 25 | "principals" => { "users" => { "anonymous" => %w{w r} }, "groups" => nil } 26 | ) 27 | end 28 | 29 | app.get("/api/security/permissions/Anything") do 30 | content_type "application/vnd.org.jfrog.artifactory.security.PermissionTargets+json" 31 | JSON.fast_generate( 32 | "name" => "Anything", 33 | "includes_pattern" => nil, 34 | "excludes_pattern" => "", 35 | "repositories" => ["ANY"], 36 | "principals" => { "users" => { "anonymous" => ["r"] }, "groups" => { "readers" => %w{r m} } } 37 | ) 38 | end 39 | 40 | app.put("/api/security/permissions/:name") do 41 | return 415 unless request.content_type == "application/vnd.org.jfrog.artifactory.security.PermissionTarget+json" 42 | 43 | # Attempt to parse the response; if this succeeds, all is well... 44 | JSON.parse(request.body.read) 45 | nil 46 | end 47 | 48 | app.delete("/api/security/permissions/:name") do 49 | nil 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/support/api_server/repository_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::RepositoryEndpoints 3 | def self.registered(app) 4 | app.get("/api/repositories") do 5 | content_type "application/vnd.org.jfrog.artifactory.repositories.RepositoryDetailsList+json" 6 | JSON.fast_generate([ 7 | { 8 | "key" => "libs-release-local", 9 | "type" => "LOCAL", 10 | "description" => "Local repository for in-house libraries", 11 | "url" => server_url.join("libs-release-local"), 12 | }, 13 | { 14 | "key" => "libs-snapshots-local", 15 | "type" => "LOCAL", 16 | "description" => "Local repository for in-house snapshots", 17 | "url" => server_url.join("libs-snapshots-local"), 18 | }, 19 | ]) 20 | end 21 | 22 | app.get("/api/repositories/libs-release-local") do 23 | content_type "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json" 24 | JSON.fast_generate({ 25 | "key" => "libs-release-local", 26 | "description" => "Local repository for in-house libraries", 27 | "notes" => "", 28 | "includesPattern" => "**/*", 29 | "excludesPattern" => "", 30 | "repoLayoutRef" => "simple-default", 31 | "enableNuGetSupport" => false, 32 | "enableGemsSupport" => false, 33 | "checksumPolicyType" => "client-checksums", 34 | "handleReleases" => true, 35 | "handleSnapshots" => false, 36 | "maxUniqueSnapshots" => 0, 37 | "snapshotVersionBehavior" => "unique", 38 | "suppressPomConsistencyChecks" => false, 39 | "blackedOut" => false, 40 | "propertySets" => ["artifactory"], 41 | "archiveBrowsingEnabled" => false, 42 | "calculateYumMetadata" => false, 43 | "yumRootDepth" => 0, 44 | "rclass" => "local", 45 | "packageType" => "generic", 46 | }) 47 | end 48 | 49 | app.get("/api/repositories/libs-snapshots-local") do 50 | content_type "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json" 51 | JSON.fast_generate({ 52 | "key" => "libs-snapshots-local", 53 | "description" => "Local repository for in-house snapshots", 54 | "notes" => "", 55 | "includesPattern" => "**/*", 56 | "excludesPattern" => "", 57 | "repoLayoutRef" => "simple-default", 58 | "enableNuGetSupport" => false, 59 | "enableGemsSupport" => false, 60 | "checksumPolicyType" => "client-checksums", 61 | "handleReleases" => false, 62 | "handleSnapshots" => true, 63 | "maxUniqueSnapshots" => 10, 64 | "snapshotVersionBehavior" => "unique", 65 | "suppressPomConsistencyChecks" => false, 66 | "blackedOut" => false, 67 | "propertySets" => ["artifactory"], 68 | "archiveBrowsingEnabled" => false, 69 | "calculateYumMetadata" => false, 70 | "yumRootDepth" => 0, 71 | "rclass" => "local", 72 | "packageType" => "generic", 73 | }) 74 | end 75 | 76 | app.get("/api/repositories/libs-debian-local") do 77 | content_type "application/vnd.org.jfrog.artifactory.repositories.LocalRepositoryConfiguration+json" 78 | JSON.fast_generate({ 79 | "key" => "libs-debian-local", 80 | "description" => "Local repository for in-house snapshots", 81 | "notes" => "", 82 | "includesPattern" => "**/*", 83 | "excludesPattern" => "", 84 | "repoLayoutRef" => "simple-default", 85 | "enableNuGetSupport" => false, 86 | "enableGemsSupport" => false, 87 | "checksumPolicyType" => "client-checksums", 88 | "handleReleases" => false, 89 | "handleSnapshots" => true, 90 | "maxUniqueSnapshots" => 10, 91 | "snapshotVersionBehavior" => "unique", 92 | "suppressPomConsistencyChecks" => false, 93 | "blackedOut" => false, 94 | "propertySets" => ["artifactory"], 95 | "archiveBrowsingEnabled" => false, 96 | "calculateYumMetadata" => false, 97 | "yumRootDepth" => 0, 98 | "rclass" => "local", 99 | "packageType" => "debian", 100 | }) 101 | end 102 | 103 | # Simulate a non-existent repository 104 | app.get("/api/repositories/libs-testing-local") do 105 | status 400 106 | end 107 | 108 | app.put("/api/repositories/libs-testing-local") do 109 | content_type "text/plain" 110 | "Repository libs-resting-local created successfully!\n" 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /spec/support/api_server/status_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::StatusEndpoints 3 | def self.registered(app) 4 | app.get("/status/:code") { code.to_i } 5 | app.post("/status/:code") { code.to_i } 6 | app.patch("/status/:code") { code.to_i } 7 | app.put("/status/:code") { code.to_i } 8 | app.delete("/status/:code") { code.to_i } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/api_server/system_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::SystemEndpoints 3 | def self.registered(app) 4 | app.get("/api/system") do 5 | "This is some serious system info right here" 6 | end 7 | 8 | app.get("/api/system/ping") do 9 | "OK" 10 | end 11 | 12 | app.get("/api/system/configuration") do 13 | content_type "application/xml" 14 | <<-EOH.gsub(/^ {10}/, "") 15 | 16 | 19 | false 20 | 21 | 22 | maven-2-default 23 | [orgPath]/[module]/[baseRev](-[folderItegRev])/[module]-[baseRev](-[fileItegRev])(-[classifier]).[ext] 24 | true 25 | [orgPath]/[module]/[baseRev](-[folderItegRev])/[module]-[baseRev](-[fileItegRev])(-[classifier]).pom 26 | SNAPSHOT 27 | SNAPSHOT|(?:(?:[0-9]{8}.[0-9]{6})-(?:[0-9]+)) 28 | 29 | 30 | gradle-default 31 | [orgPath]/[module]/[baseRev](-[folderItegRev])/[module]-[baseRev](-[fileItegRev])(-[classifier]).[ext] 32 | true 33 | [orgPath]/[module]/[baseRev](-[folderItegRev])/[module]-[baseRev](-[fileItegRev])(-[classifier]).pom 34 | SNAPSHOT 35 | SNAPSHOT|(?:(?:[0-9]{8}.[0-9]{6})-(?:[0-9]+)) 36 | 37 | 38 | http://33.33.33.20/artifactory 39 | 40 | true 41 | smtp.gmail.com 42 | 25 43 | us@example.com 44 | ihopethisisnotyourpassword 45 | artifactory@example.com 46 | [Artifactory] 47 | true 48 | false 49 | http://33.33.33.20/artifactory 50 | 51 | 52 | 53 | 54 | example-ldap 55 | true 56 | ldap://localhost/DC=examplecorp,DC=com 57 | 58 | sAMAccountName={0} 59 | OU=Domain Users 60 | true 61 | CN=ldapbind,OU=Employees,OU=Domain Users,DC=examplecorp,DC=com 62 | ldappassword 63 | 64 | false 65 | mail 66 | 67 | 68 | 69 | 70 | 71 | backup-daily 72 | true 73 | 0 0 2 ? * MON-FRI 74 | 0 75 | false 76 | 77 | true 78 | false 79 | 80 | 81 | 82 | EOH 83 | end 84 | 85 | app.post("/api/system/configuration") do 86 | # Just echo the response we got back, since it seems that's what a real 87 | # Artifactory server does... 88 | request.body.read 89 | end 90 | 91 | app.get("/api/system/security/certificates") do 92 | content_type "application/json" 93 | JSON.fast_generate([ 94 | { 95 | "certificateAlias" => "test", 96 | "issuedTo" => "An end user", 97 | "issuedBy" => "An authority", 98 | "issuedOn" => "2014-01-01 10:00 UTC", 99 | "validUntil" => "2014-01-01 11:00 UTC", 100 | "fingerprint" => "00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F", 101 | }, 102 | ]) 103 | end 104 | 105 | app.get("/api/system/version") do 106 | content_type "application/vnd.org.jfrog.artifactory.system.Version+json" 107 | JSON.generate({ 108 | "version" => "3.1.0", 109 | "revision" => "30062", 110 | "addons" => %w{ 111 | ldap 112 | license 113 | yum}, 114 | }) 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /spec/support/api_server/user_endpoints.rb: -------------------------------------------------------------------------------- 1 | module Artifactory 2 | module APIServer::UserEndpoints 3 | def self.registered(app) 4 | app.get("/api/security/users") do 5 | content_type "application/vnd.org.jfrog.artifactory.security.Users+json" 6 | JSON.fast_generate([ 7 | { 8 | "name" => "sethvargo", 9 | "uri" => server_url.join("/api/security/users/sethvargo"), 10 | }, 11 | { 12 | "name" => "yzl", 13 | "uri" => server_url.join("/api/security/users/yzl"), 14 | }, 15 | ]) 16 | end 17 | 18 | app.get("/api/security/users/sethvargo") do 19 | content_type "application/vnd.org.jfrog.artifactory.security.User+json" 20 | JSON.fast_generate( 21 | "admin" => false, 22 | "email" => "sethvargo@gmail.com", 23 | "groups" => ["admin"], 24 | "internalPasswordDisabled" => true, 25 | "lastLoggedIn" => nil, 26 | "name" => "sethvargo", 27 | "profileUpdatable" => true, 28 | "realm" => "artifactory" 29 | ) 30 | end 31 | 32 | app.get("/api/security/users/yzl") do 33 | content_type "application/vnd.org.jfrog.artifactory.security.User+json" 34 | JSON.fast_generate( 35 | "admin" => true, 36 | "email" => "yvonne@getchef.com", 37 | "groups" => ["admin"], 38 | "internalPasswordDisabled" => true, 39 | "lastLoggedIn" => nil, 40 | "name" => "yzl", 41 | "profileUpdatable" => true, 42 | "realm" => "crowd" 43 | ) 44 | end 45 | 46 | app.put("/api/security/users/:name") do 47 | return 415 unless request.content_type == "application/vnd.org.jfrog.artifactory.security.User+json" 48 | 49 | # Attempt to parse the response; if this succeeds, all is well... 50 | JSON.parse(request.body.read) 51 | nil 52 | end 53 | 54 | app.delete("/api/security/users/:name") do 55 | nil 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/unit/artifactory_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Artifactory do 4 | it "sets the default values" do 5 | Artifactory::Configurable.keys.each do |key| 6 | value = Artifactory::Defaults.send(key) 7 | expect(Artifactory.instance_variable_get(:"@#{key}")).to eq(value) 8 | end 9 | end 10 | 11 | describe ".client" do 12 | it "creates an Artifactory::Client" do 13 | expect(Artifactory.client).to be_a(Artifactory::Client) 14 | end 15 | 16 | it "caches the client when the same options are passed" do 17 | expect(Artifactory.client).to eq(Artifactory.client) 18 | end 19 | 20 | it "returns a fresh client when options are not the same" do 21 | original_client = Artifactory.client 22 | 23 | # Change settings 24 | Artifactory.username = "admin" 25 | new_client = Artifactory.client 26 | 27 | # Get it one more tmie 28 | current_client = Artifactory.client 29 | 30 | expect(original_client).to_not eq(new_client) 31 | expect(new_client).to eq(current_client) 32 | end 33 | end 34 | 35 | describe ".configure" do 36 | Artifactory::Configurable.keys.each do |key| 37 | it "sets the #{key.to_s.tr("_", " ")}" do 38 | Artifactory.configure do |config| 39 | config.send("#{key}=", key) 40 | end 41 | 42 | expect(Artifactory.instance_variable_get(:"@#{key}")).to eq(key) 43 | end 44 | end 45 | end 46 | 47 | describe ".method_missing" do 48 | context "when the client responds to the method" do 49 | let(:client) { double(:client) } 50 | before { allow(Artifactory).to receive(:client).and_return(client) } 51 | 52 | it "delegates the method to the client" do 53 | allow(client).to receive(:bacon).and_return("awesome") 54 | expect { Artifactory.bacon }.to_not raise_error 55 | end 56 | end 57 | 58 | context "when the client does not respond to the method" do 59 | it "calls super" do 60 | expect { Artifactory.bacon }.to raise_error(NoMethodError) 61 | end 62 | end 63 | end 64 | 65 | describe ".respond_to_missing?" do 66 | let(:client) { double(:client) } 67 | before { allow(Artifactory).to receive(:client).and_return(client) } 68 | 69 | it "delegates to the client" do 70 | expect { Artifactory.respond_to_missing?(:foo) }.to_not raise_error 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/unit/client_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Client do 5 | let(:client) { subject } 6 | 7 | context "configuration" do 8 | it "is a configurable object" do 9 | expect(client).to be_a(Configurable) 10 | end 11 | 12 | it "users the default configuration" do 13 | Defaults.options.each do |key, value| 14 | expect(client.send(key)).to eq(value) 15 | end 16 | end 17 | 18 | it "uses the values in the initializer" do 19 | client = described_class.new(username: "admin") 20 | expect(client.username).to eq("admin") 21 | end 22 | 23 | it "can be modified after initialization" do 24 | expect(client.username).to eq(Defaults.username) 25 | client.username = "admin" 26 | expect(client.username).to eq("admin") 27 | end 28 | end 29 | 30 | describe ".proxy" do 31 | before { described_class.proxy(Resource::Artifact) } 32 | 33 | it "defines a new method" do 34 | expect(described_class).to be_method_defined(:artifact_search) 35 | end 36 | 37 | it "delegates to the class, injecting the client" do 38 | allow(Resource::Artifact).to receive(:search) 39 | expect(Resource::Artifact).to receive(:search).with(client: subject) 40 | subject.artifact_search 41 | end 42 | end 43 | 44 | describe "#get" do 45 | it "delegates to the #request method" do 46 | expect(subject).to receive(:request).with(:get, "/foo", {}, {}) 47 | subject.get("/foo") 48 | end 49 | 50 | context "called with a block" do 51 | it "yields the response in chunks" do 52 | expect(subject).to receive(:request).with(:get, "/foo", {}, {}) { |&block| block.call("Chunked Body") } 53 | expect { |chunk| subject.get("/foo", &chunk) }.to yield_with_args("Chunked Body") 54 | end 55 | end 56 | end 57 | 58 | describe "#post" do 59 | let(:data) { double } 60 | 61 | it "delegates to the #request method" do 62 | expect(subject).to receive(:request).with(:post, "/foo", data, {}) 63 | subject.post("/foo", data) 64 | end 65 | end 66 | 67 | describe "#put" do 68 | let(:data) { double } 69 | 70 | it "delegates to the #request method" do 71 | expect(subject).to receive(:request).with(:put, "/foo", data, {}) 72 | subject.put("/foo", data) 73 | end 74 | end 75 | 76 | describe "#patch" do 77 | let(:data) { double } 78 | 79 | it "delegates to the #request method" do 80 | expect(subject).to receive(:request).with(:patch, "/foo", data, {}) 81 | subject.patch("/foo", data) 82 | end 83 | end 84 | 85 | describe "#delete" do 86 | it "delegates to the #request method" do 87 | expect(subject).to receive(:request).with(:delete, "/foo", {}, {}) 88 | subject.delete("/foo") 89 | end 90 | end 91 | 92 | describe "#request" do 93 | context "when the response is a 400" do 94 | before { stub_request(:get, /.+/).to_return(status: 5000, body: "No!") } 95 | 96 | it "raises an HTTPError error" do 97 | expect { subject.request(:get, "/") }.to raise_error(Error::HTTPError) 98 | end 99 | end 100 | 101 | context "called with a block" do 102 | before { stub_request(:get, /.+/).to_return(status: 200, body: "Chunked Body") } 103 | 104 | it "yields the response in chunks" do 105 | subject.request(:get, "/foo") do |chunk| 106 | expect(chunk).to eq("Chunked Body") 107 | end 108 | end 109 | end 110 | 111 | context "application/json response with an empty body" do 112 | before { stub_request(:get, /.+/).to_return(status: 200, headers: { "Content-Type" => "application/json" }, body: "") } 113 | 114 | it "should not raise an error" do 115 | expect { subject.request(:get, "/foo") }.to_not raise_error(TypeError) 116 | end 117 | end 118 | end 119 | 120 | describe "#to_query_string" do 121 | it 'converts spaces to "+" characters' do 122 | params = { user: "Seth Chisamore" } 123 | expect(subject.to_query_string(params)).to eq("user=Seth+Chisamore") 124 | end 125 | 126 | it 'converts "+" to "%2B"' do 127 | params = { version: "12.0.0-alpha.1+20140826080510.git.50.f5ff271" } 128 | expect(subject.to_query_string(params)).to eq("version=12.0.0-alpha.1%2B20140826080510.git.50.f5ff271") 129 | end 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /spec/unit/resources/backup_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Backup do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".all" do 13 | doc = <<-XML 14 | 15 | 16 | 17 | backup-daily 18 | 19 | 20 | 21 | XML 22 | let(:xml) do 23 | REXML::Document.new(doc) 24 | end 25 | 26 | before do 27 | allow(Resource::System).to receive(:configuration).and_return(xml) 28 | end 29 | 30 | it "returns the backup settings" do 31 | expect(described_class.all).to be_a(Array) 32 | expect(described_class.all.first).to be_a(described_class) 33 | expect(described_class.all.first.key).to eq("backup-daily") 34 | end 35 | end 36 | 37 | describe ".find" do 38 | doc = <<-XML 39 | 40 | 41 | 42 | backup-weekly 43 | 44 | 45 | 46 | XML 47 | let(:xml) do 48 | REXML::Document.new(doc) 49 | end 50 | 51 | before do 52 | allow(Resource::System).to receive(:configuration).and_return(xml) 53 | end 54 | 55 | it "returns the found backup setting" do 56 | expect(described_class.find("backup-weekly")).to be_a(described_class) 57 | expect(described_class.find("backup-weekly").key).to eq("backup-weekly") 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/unit/resources/base_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Base do 5 | let(:client) { double } 6 | let(:endpoint_host) { "http://33.33.33.11" } 7 | let(:endpoint) { "#{endpoint_host}/" } 8 | 9 | before do 10 | allow(Artifactory).to receive(:client).and_return(client) 11 | end 12 | 13 | describe ".attribute" do 14 | before { described_class.attribute(:bacon) } 15 | 16 | it "defines an accessor method" do 17 | expect(subject).to respond_to(:bacon) 18 | end 19 | 20 | it "defines a setter method" do 21 | expect(subject).to respond_to(:bacon=) 22 | end 23 | 24 | it "defines a boolean method" do 25 | expect(subject).to respond_to(:bacon?) 26 | end 27 | end 28 | 29 | describe ".extract_client!" do 30 | context "when the :client key is present" do 31 | let(:client) { double } 32 | let(:options) { { client: client } } 33 | 34 | it "extracts the client" do 35 | result = described_class.extract_client!(options) 36 | expect(result).to be(client) 37 | end 38 | 39 | it "removes the key from the hash" do 40 | described_class.extract_client!(options) 41 | expect(options).to_not have_key(:client) 42 | end 43 | end 44 | 45 | context "when the :client key is not present" do 46 | let(:client) { double } 47 | before { allow(Artifactory).to receive(:client).and_return(client) } 48 | 49 | it "uses Artifactory.client" do 50 | expect(described_class.extract_client!({})).to be(client) 51 | end 52 | end 53 | end 54 | 55 | describe ".format_repos!" do 56 | context "when the :repos key is present" do 57 | it "joins an array" do 58 | options = { repos: %w{bacon bits} } 59 | described_class.format_repos!(options) 60 | expect(options[:repos]).to eq("bacon,bits") 61 | end 62 | 63 | it "accepts a single repository" do 64 | options = { repos: "bacon" } 65 | described_class.format_repos!(options) 66 | expect(options[:repos]).to eq("bacon") 67 | end 68 | end 69 | 70 | context "when the :repos key is not present" do 71 | it "does not modify the hash" do 72 | options = {} 73 | described_class.format_repos!(options) 74 | expect(options).to eq(options) 75 | end 76 | end 77 | 78 | context "when the :repos key is empty" do 79 | it "does not modify the hash" do 80 | options = { repos: [] } 81 | described_class.format_repos!(options) 82 | expect(options).to eq(options) 83 | end 84 | end 85 | end 86 | 87 | describe ".from_url" do 88 | let(:relative_path) { "/api/storage/omnibus-unstable-local/com/getchef/harmony/0.1.0+20151111083608.git.15.8736e1e/el/5/harmony-0.1.0+20151111083608.git.15.8736e1e-1.el5.x86_64.rpm" } 89 | 90 | context "when endpoint path part is not empty" do 91 | let(:endpoint) { "#{endpoint_host}/artifactory" } 92 | let(:full_url) { "#{endpoint}#{relative_path}" } 93 | 94 | it "uses the path minus the path part of the endpoint" do 95 | expect(client).to receive(:endpoint).and_return(endpoint) 96 | expect(described_class).to receive(:from_hash) 97 | expect(client).to receive(:get).with(relative_path) 98 | described_class.from_url(full_url) 99 | end 100 | end 101 | 102 | context "when endpoint has empty path part" do 103 | let(:endpoint) { "#{endpoint_host}/" } 104 | let(:full_url) { "#{endpoint}#{relative_path}" } 105 | 106 | it "only uses the path from absolute URLs" do 107 | expect(client).to receive(:endpoint).and_return(endpoint) 108 | expect(described_class).to receive(:from_hash) 109 | expect(client).to receive(:get).with(relative_path) 110 | described_class.from_url(full_url) 111 | end 112 | end 113 | end 114 | 115 | describe ".url_safe" do 116 | let(:string) { double(to_s: "string") } 117 | 118 | it "delegates to URI.escape" do 119 | expect(described_class.uri_parser).to receive(:escape).once 120 | described_class.url_safe(string) 121 | end 122 | 123 | it "converts the value to a string" do 124 | expect(string).to receive(:to_s).once 125 | described_class.url_safe(string) 126 | end 127 | end 128 | 129 | describe "#client" do 130 | it "defines a :client method" do 131 | expect(subject).to respond_to(:client) 132 | end 133 | 134 | it "defaults to the Artifactory.client" do 135 | client = double 136 | allow(Artifactory).to receive(:client).and_return(client) 137 | allow(client).to receive(:endpoint).and_return(endpoint) 138 | 139 | expect(subject.client).to be(client) 140 | end 141 | end 142 | 143 | describe "#extract_client!" do 144 | it "delegates to the class method" do 145 | expect(described_class).to receive(:extract_client!).once 146 | subject.extract_client!({}) 147 | end 148 | end 149 | 150 | describe "#format_repos!" do 151 | it "delegates to the class method" do 152 | expect(described_class).to receive(:format_repos!).once 153 | subject.format_repos!({}) 154 | end 155 | end 156 | 157 | describe "#url_safe" do 158 | it "delegates to the class method" do 159 | expect(described_class).to receive(:url_safe).once 160 | subject.url_safe("string") 161 | end 162 | end 163 | 164 | describe "#to_s" do 165 | it "returns the name of the class" do 166 | expect(subject.to_s).to eq("#") 167 | end 168 | end 169 | 170 | describe "#inspect" do 171 | it "includes all the attributes" do 172 | allow(subject).to receive(:attributes) do 173 | { foo: "bar" } 174 | end 175 | 176 | expect(subject.inspect).to eq(%q{#}) 177 | end 178 | end 179 | end 180 | end 181 | -------------------------------------------------------------------------------- /spec/unit/resources/build_component_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::BuildComponent do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | subject.name = "wicket" 11 | end 12 | 13 | describe ".all" do 14 | let(:response) do 15 | { 16 | "uri" => "#{Artifactory.endpoint}/api/build", 17 | "builds" => [ 18 | { 19 | "uri" => "/wicket", 20 | "lastStarted" => "2015-06-19T20:13:20.222Z", 21 | }, 22 | { 23 | "uri" => "/jackrabbit", 24 | "lastStarted" => "2015-06-20T20:13:20.333Z", 25 | }, 26 | ], 27 | } 28 | end 29 | 30 | before do 31 | allow(described_class).to receive(:from_hash).with(hash_including("uri" => "/wicket"), client: client).and_return("wicket") 32 | allow(described_class).to receive(:from_hash).with(hash_including("uri" => "/jackrabbit"), client: client).and_return("jackrabbit") 33 | end 34 | 35 | it "GETS /api/build" do 36 | expect(client).to receive(:get).with("/api/build").once 37 | described_class.all 38 | end 39 | 40 | context "when there are components with builds" do 41 | it "returns the components" do 42 | expect(described_class.all).to eq(%w{ wicket jackrabbit }) 43 | end 44 | end 45 | 46 | context "when the system has no components with builds" do 47 | it "returns an empty array" do 48 | allow(client).to receive(:get).and_raise(Error::HTTPError.new("status" => 404)) 49 | expect(described_class.all).to be_empty 50 | end 51 | end 52 | end 53 | 54 | describe ".find" do 55 | before do 56 | allow(described_class).to receive(:all).and_return([ 57 | described_class.new(name: "wicket"), 58 | described_class.new(name: "jackrabbit"), 59 | ]) 60 | end 61 | 62 | it "filters the full build component list by name" do 63 | expect(described_class.find("wicket")).to_not be_nil 64 | end 65 | 66 | context "when the build component does not exist" do 67 | it "returns nil" do 68 | expect(described_class.find("fricket")).to be_nil 69 | end 70 | end 71 | end 72 | 73 | describe ".from_hash" do 74 | let(:time) { Time.now.utc.round } 75 | let(:hash) do 76 | { 77 | "uri" => "/wicket", 78 | "lastStarted" => time.iso8601(3), 79 | } 80 | end 81 | 82 | it "creates a new instance" do 83 | instance = described_class.from_hash(hash) 84 | expect(instance.uri).to eq("/wicket") 85 | expect(instance.name).to eq("wicket") 86 | expect(instance.last_started).to eq(time) 87 | end 88 | end 89 | 90 | describe "#builds" do 91 | 92 | it "returns a build collection" do 93 | expect(subject.builds).to be_a(Collection::Build) 94 | end 95 | end 96 | 97 | describe "#delete" do 98 | 99 | it "sends DELETE to the client" do 100 | expect(client).to receive(:delete) 101 | subject.delete 102 | end 103 | 104 | it "adds the correct parameters to the request" do 105 | expect(client).to receive(:delete).with("/api/build/wicket?buildNumbers=51,52,55&artifacts=1&deleteAll=1", {}) 106 | subject.delete(build_numbers: %w{ 51 52 55 }, artifacts: true, delete_all: true) 107 | end 108 | end 109 | 110 | describe "#rename" do 111 | 112 | it "sends POST to the client with the correct param" do 113 | expect(client).to receive(:post).with("/api/build/rename/wicket?to=fricket", {}) 114 | subject.rename("fricket") 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /spec/unit/resources/certificate_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Certificate do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".all" do 13 | let(:response) do 14 | %w{a b c} 15 | end 16 | before do 17 | allow(described_class).to receive(:from_hash).with("a", client: client).and_return("a") 18 | allow(described_class).to receive(:from_hash).with("b", client: client).and_return("b") 19 | allow(described_class).to receive(:from_hash).with("c", client: client).and_return("c") 20 | end 21 | 22 | it "gets /api/system/security/certificates" do 23 | expect(client).to receive(:get).with("/api/system/security/certificates").once 24 | described_class.all 25 | end 26 | 27 | it "returns the certificates" do 28 | expect(described_class.all).to eq(%w{a b c}) 29 | end 30 | end 31 | 32 | describe ".from_hash" do 33 | let(:hash) do 34 | { 35 | "certificateAlias" => "test", 36 | "issuedTo" => "An end user", 37 | "issuedBy" => "An authority", 38 | "issuedOn" => "2014-01-01 10:00 UTC", 39 | "validUntil" => "2014-01-01 11:00 UTC", 40 | "fingerprint" => "00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F", 41 | } 42 | end 43 | 44 | it "creates a new instance" do 45 | instance = described_class.from_hash(hash) 46 | expect(instance.certificate_alias).to eq("test") 47 | expect(instance.issued_to).to eq("An end user") 48 | expect(instance.issued_by).to eq("An authority") 49 | expect(instance.issued_on).to eq(Time.parse("2014-01-01 10:00 UTC")) 50 | expect(instance.valid_until).to eq(Time.parse("2014-01-01 11:00 UTC")) 51 | expect(instance.fingerprint).to eq("00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F") 52 | end 53 | end 54 | 55 | describe "#upload" do 56 | let(:client) { double(put: {}) } 57 | let(:local_path) { "/local/path" } 58 | let(:file) { double(File) } 59 | 60 | subject { described_class.new(client: client, local_path: local_path, certificate_alias: "test") } 61 | 62 | before do 63 | allow(File).to receive(:new).with(/\A(\w:)?#{local_path}\z/).and_return(file) 64 | end 65 | 66 | context "when the certificate is a file path" do 67 | it "POSTs the file at the path to the server" do 68 | expect(client).to receive(:post).with("/api/system/security/certificates/test", file, { "Content-Type" => "application/text" }) 69 | subject.upload 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/unit/resources/defaults_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Defaults do 5 | describe "read_timeout" do 6 | before(:each) do 7 | ENV["ARTIFACTORY_READ_TIMEOUT"] = "60" 8 | end 9 | 10 | after(:each) do 11 | ENV.delete("ARTIFACTORY_READ_TIMEOUT") 12 | end 13 | 14 | it "returns Integers even when given strings" do 15 | expect(subject.read_timeout).to be_kind_of Integer 16 | end 17 | 18 | it "returns a non-zero value" do 19 | expect(subject.read_timeout).to be > 0 20 | end 21 | 22 | it "does not return a nil value" do 23 | expect(subject.read_timeout).not_to be nil 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/resources/group_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Group do 5 | let(:client) { double(:client) } 6 | let(:endpoint_host) { "http://33.33.33.11" } 7 | let(:endpoint) { "#{endpoint_host}/" } 8 | 9 | before(:each) do 10 | allow(Artifactory).to receive(:client).and_return(client) 11 | allow(client).to receive(:get).and_return(response) if defined?(response) 12 | end 13 | 14 | describe ".all" do 15 | let(:response) do 16 | [ 17 | { "uri" => "a" }, 18 | { "uri" => "b" }, 19 | { "uri" => "c" }, 20 | ] 21 | end 22 | before do 23 | allow(described_class).to receive(:from_url).with("a", client: client).and_return("a") 24 | allow(described_class).to receive(:from_url).with("b", client: client).and_return("b") 25 | allow(described_class).to receive(:from_url).with("c", client: client).and_return("c") 26 | end 27 | 28 | it "gets /api/security/groups" do 29 | expect(client).to receive(:get).with("/api/security/groups").once 30 | described_class.all 31 | end 32 | 33 | it "returns the groups" do 34 | expect(described_class.all).to eq(%w{a b c}) 35 | end 36 | end 37 | 38 | describe ".find" do 39 | let(:response) { {} } 40 | 41 | it 'gets /api/repositories/#{name}' do 42 | expect(client).to receive(:get).with("/api/security/groups/readers").once 43 | described_class.find("readers") 44 | end 45 | end 46 | 47 | describe ".from_url" do 48 | let(:response) { {} } 49 | 50 | it "constructs a new instance from the result" do 51 | expect(client).to receive(:endpoint).and_return(endpoint) 52 | expect(described_class).to receive(:from_hash).once 53 | described_class.from_url("/api/security/groups/readers") 54 | end 55 | end 56 | 57 | describe ".from_hash" do 58 | let(:hash) do 59 | { 60 | "name" => "readers", 61 | "description" => "This list of read-only users", 62 | "autoJoin" => true, 63 | "realm" => "artifactory", 64 | "realmAttributes" => nil, 65 | } 66 | end 67 | 68 | it "creates a new instance" do 69 | instance = described_class.from_hash(hash) 70 | expect(instance.name).to eq("readers") 71 | expect(instance.description).to eq("This list of read-only users") 72 | expect(instance.auto_join).to be_truthy 73 | expect(instance.realm).to eq("artifactory") 74 | expect(instance.realm_attributes).to be_nil 75 | end 76 | end 77 | 78 | describe "#save" do 79 | let(:client) { double } 80 | before do 81 | subject.client = client 82 | subject.name = "deployers" 83 | end 84 | 85 | context "when the group is new" do 86 | before do 87 | allow(described_class).to receive(:find).with(subject.name, client: client).and_return(nil) 88 | end 89 | 90 | it "PUTS the group to the server" do 91 | expect(client).to receive(:put).with("/api/security/groups/#{subject.name}", kind_of(String), kind_of(Hash)) 92 | subject.save 93 | end 94 | end 95 | 96 | context "when the group exists" do 97 | before do 98 | allow(described_class).to receive(:find).with(subject.name, client: client).and_return({ name: subject.name }) 99 | end 100 | 101 | it "POSTS the group to the server" do 102 | expect(client).to receive(:post).with("/api/security/groups/#{subject.name}", kind_of(String), kind_of(Hash)) 103 | subject.save 104 | end 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /spec/unit/resources/layout_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Layout do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".all" do 13 | doc = <<-XML 14 | 15 | 16 | 17 | fake-layout 18 | 19 | 20 | 21 | XML 22 | let(:xml) do 23 | REXML::Document.new(doc) 24 | end 25 | 26 | before do 27 | allow(Resource::System).to receive(:configuration).and_return(xml) 28 | end 29 | 30 | it "returns the layouts" do 31 | expect(described_class.all).to be_a(Array) 32 | expect(described_class.all.first).to be_a(described_class) 33 | expect(described_class.all.first.name).to eq("fake-layout") 34 | end 35 | end 36 | 37 | describe ".find" do 38 | doc = <<-XML 39 | 40 | 41 | 42 | found-layout 43 | 44 | 45 | 46 | XML 47 | let(:xml) do 48 | REXML::Document.new(doc) 49 | end 50 | 51 | before do 52 | allow(Resource::System).to receive(:configuration).and_return(xml) 53 | end 54 | 55 | it "returns the found layout" do 56 | expect(described_class.find("found-layout")).to be_a(described_class) 57 | expect(described_class.find("found-layout").name).to eq("found-layout") 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/unit/resources/ldap_setting_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::LDAPSetting do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".all" do 13 | doc = <<-XML 14 | 15 | 16 | 17 | 18 | example-ldap 19 | 20 | 21 | 22 | 23 | XML 24 | let(:xml) do 25 | REXML::Document.new(doc) 26 | end 27 | 28 | before do 29 | allow(Resource::System).to receive(:configuration).and_return(xml) 30 | end 31 | 32 | it "returns the ldap settings" do 33 | expect(described_class.all).to be_a(Array) 34 | expect(described_class.all.first).to be_a(described_class) 35 | expect(described_class.all.first.key).to eq("example-ldap") 36 | end 37 | end 38 | 39 | describe ".find" do 40 | doc = <<-XML 41 | 42 | 43 | 44 | 45 | viridian-ldap 46 | 47 | 48 | 49 | 50 | XML 51 | let(:xml) do 52 | REXML::Document.new(doc) 53 | end 54 | 55 | before do 56 | allow(Resource::System).to receive(:configuration).and_return(xml) 57 | end 58 | 59 | it "returns the found ldap setting" do 60 | expect(described_class.find("viridian-ldap")).to be_a(described_class) 61 | expect(described_class.find("viridian-ldap").key).to eq("viridian-ldap") 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/unit/resources/mail_server_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::MailServer do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".all" do 13 | doc = <<-XML 14 | 15 | 16 | smtp.gmail.com 17 | 18 | 19 | XML 20 | let(:xml) do 21 | REXML::Document.new(doc) 22 | end 23 | 24 | before do 25 | allow(Resource::System).to receive(:configuration).and_return(xml) 26 | end 27 | 28 | it "returns the mail server settings" do 29 | expect(described_class.all).to be_a(Array) 30 | expect(described_class.all.first).to be_a(described_class) 31 | expect(described_class.all.first.host).to eq("smtp.gmail.com") 32 | end 33 | end 34 | 35 | describe ".find" do 36 | doc = <<-XML 37 | 38 | 39 | mailserver.example.com 40 | 41 | 42 | XML 43 | let(:xml) do 44 | REXML::Document.new(doc) 45 | end 46 | 47 | before do 48 | allow(Resource::System).to receive(:configuration).and_return(xml) 49 | end 50 | 51 | it "returns the found mail server setting" do 52 | expect(described_class.find("mailserver.example.com")).to be_a(described_class) 53 | expect(described_class.find("mailserver.example.com").host).to eq("mailserver.example.com") 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/unit/resources/permission_target_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::PermissionTarget do 5 | let(:client) { double(:client) } 6 | let(:endpoint_host) { "http://33.33.33.11" } 7 | let(:endpoint) { "#{endpoint_host}/" } 8 | 9 | before(:each) do 10 | allow(Artifactory).to receive(:client).and_return(client) 11 | allow(client).to receive(:get).and_return(response) if defined?(response) 12 | end 13 | 14 | describe ".all" do 15 | let(:response) do 16 | [ 17 | { "uri" => "a" }, 18 | { "uri" => "b" }, 19 | { "uri" => "c" }, 20 | ] 21 | end 22 | before do 23 | allow(described_class).to receive(:from_url).with("a", client: client).and_return("a") 24 | allow(described_class).to receive(:from_url).with("b", client: client).and_return("b") 25 | allow(described_class).to receive(:from_url).with("c", client: client).and_return("c") 26 | end 27 | 28 | it "gets /api/security/permissions" do 29 | expect(client).to receive(:get).with("/api/security/permissions").once 30 | described_class.all 31 | end 32 | 33 | it "returns the permissions" do 34 | expect(described_class.all).to eq(%w{a b c}) 35 | end 36 | end 37 | 38 | describe ".find" do 39 | let(:response) { {} } 40 | 41 | it 'gets /api/security/permissions/#{name}' do 42 | expect(client).to receive(:get).with("/api/security/permissions/Any%20Remote").once 43 | described_class.find("Any Remote") 44 | end 45 | end 46 | 47 | describe ".from_url" do 48 | let(:response) { {} } 49 | 50 | it "constructs a new instance from the result" do 51 | expect(client).to receive(:endpoint).and_return(endpoint) 52 | expect(described_class).to receive(:from_hash).once 53 | described_class.from_url("/api/security/permissions/AnyRemote") 54 | end 55 | end 56 | 57 | describe ".from_hash" do 58 | let(:hash) do 59 | { 60 | "name" => "Test Remote", 61 | "includes_pattern" => "**", 62 | "excludes_pattern" => "", 63 | "repositories" => ["ANY REMOTE"], 64 | "principals" => { "users" => { "anonymous" => %w{w r} }, "groups" => {} }, 65 | } 66 | end 67 | 68 | it "creates a new instance" do 69 | instance = described_class.from_hash(hash) 70 | expect(instance.name).to eq("Test Remote") 71 | expect(instance.includes_pattern).to eq("**") 72 | expect(instance.excludes_pattern).to eq("") 73 | expect(instance.repositories).to eq(["ANY REMOTE"]) 74 | expect(instance.principals).to eq({ "users" => { "anonymous" => %w{deploy read} }, "groups" => {} }) 75 | end 76 | end 77 | 78 | describe Artifactory::Resource::PermissionTarget::Principal do 79 | context "principal object" do 80 | users = { "anonymous_users" => %w{admin deploy read} } 81 | groups = { "anonymous_groups" => %w{delete read} } 82 | instance = described_class.new(users, groups) 83 | 84 | it "has unabbreviated users" do 85 | expect(instance.users).to eq( { "anonymous_users" => %w{admin deploy read} } ) 86 | end 87 | 88 | it "has unabbreviated groups" do 89 | expect(instance.groups).to eq( { "anonymous_groups" => %w{delete read} } ) 90 | end 91 | 92 | it "abbreviates" do 93 | expect(instance.to_abbreviated).to eq( { "users" => { "anonymous_users" => %w{m r w} }, "groups" => { "anonymous_groups" => %w{d r} } } ) 94 | end 95 | end 96 | end 97 | 98 | describe "#save" do 99 | let(:client) { double } 100 | before do 101 | subject.client = client 102 | subject.name = "TestRemote" 103 | subject.includes_pattern = nil 104 | subject.excludes_pattern = "" 105 | subject.repositories = ["ANY"] 106 | subject.principals = { 107 | "users" => { 108 | "anonymous_users" => ["read"], 109 | }, 110 | "groups" => { 111 | "anonymous_readers" => ["read"], 112 | }, 113 | } 114 | allow(described_class).to receive(:find).with(subject.name, client: client).and_return(nil) 115 | end 116 | 117 | it "PUTS the permission target to the server" do 118 | expect(client).to receive(:put).with("/api/security/permissions/TestRemote", 119 | "{\"name\":\"TestRemote\",\"includesPattern\":\"**\",\"excludesPattern\":\"\",\"repositories\":[\"ANY\"],\"principals\":{\"users\":{\"anonymous_users\":[\"r\"]},\"groups\":{\"anonymous_readers\":[\"r\"]}}}", 120 | { "Content-Type" => "application/vnd.org.jfrog.artifactory.security.PermissionTarget+json" }) 121 | subject.save 122 | end 123 | end 124 | 125 | describe "#delete" do 126 | let(:client) { double } 127 | 128 | it "sends DELETE to the client" do 129 | subject.client = client 130 | subject.name = "My Permissions" 131 | 132 | expect(client).to receive(:delete).with("/api/security/permissions/My%20Permissions") 133 | subject.delete 134 | end 135 | end 136 | 137 | describe "getters" do 138 | let(:client) { double } 139 | before do 140 | subject.client = client 141 | subject.name = "TestGetters" 142 | subject.principals = { 143 | "users" => { 144 | "anonymous" => ["read"], 145 | }, 146 | "groups" => { 147 | "readers" => ["read"], 148 | }, 149 | } 150 | allow(described_class).to receive(:find).with(subject.name, client: client).and_return(nil) 151 | end 152 | 153 | it "#users returns the users hash" do 154 | expect(subject.users).to eq({ "anonymous" => ["read"] }) 155 | end 156 | 157 | it "#groups returns the groups hash" do 158 | expect(subject.groups).to eq({ "readers" => ["read"] }) 159 | end 160 | end 161 | 162 | describe "setters" do 163 | let(:client) { double } 164 | before do 165 | subject.client = client 166 | subject.name = "TestSetters" 167 | subject.principals = { 168 | "users" => { 169 | "anonymous" => ["read"], 170 | }, 171 | "groups" => { 172 | "readers" => ["read"], 173 | }, 174 | } 175 | allow(described_class).to receive(:find).with(subject.name, client: client).and_return(nil) 176 | end 177 | 178 | it "#users= sets the users hash" do 179 | subject.users = { "spiders" => %w{read admin} } 180 | expect(subject.users).to eq({ "spiders" => %w{admin read} }) 181 | end 182 | 183 | it "#groups= sets the groups hash" do 184 | subject.groups = { "beatles" => %w{deploy delete} } 185 | expect(subject.groups).to eq({ "beatles" => %w{delete deploy} }) 186 | end 187 | end 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /spec/unit/resources/plugin_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Plugin do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".all" do 13 | let(:response) { %w{a b c} } 14 | 15 | it "gets /api/plugins" do 16 | expect(client).to receive(:get).with("/api/plugins").once 17 | described_class.all 18 | end 19 | 20 | it "returns the plugins" do 21 | expect(described_class.all).to eq(%w{a b c}) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/unit/resources/repository_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::Repository do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".all" do 13 | let(:response) do 14 | [ 15 | { "key" => "a" }, 16 | { "key" => "b" }, 17 | { "key" => "c" }, 18 | ] 19 | end 20 | before do 21 | allow(described_class).to receive(:find).with("a", client: client).and_return("a") 22 | allow(described_class).to receive(:find).with("b", client: client).and_return("b") 23 | allow(described_class).to receive(:find).with("c", client: client).and_return("c") 24 | end 25 | 26 | it "gets /api/repositories" do 27 | expect(client).to receive(:get).with("/api/repositories").once 28 | described_class.all 29 | end 30 | 31 | it "returns the repositories" do 32 | expect(described_class.all).to eq(%w{a b c}) 33 | end 34 | end 35 | 36 | describe ".find" do 37 | let(:response) { {} } 38 | 39 | it 'gets /api/repositories/#{name}' do 40 | expect(client).to receive(:get).with("/api/repositories/libs-release-local").once 41 | described_class.find("libs-release-local") 42 | end 43 | end 44 | 45 | describe ".from_hash" do 46 | let(:hash) do 47 | { 48 | "key" => "libs-release-local", 49 | "description" => "Local repository for in-house libraries", 50 | "notes" => "", 51 | "includesPattern" => "**/*", 52 | "excludesPattern" => "", 53 | "enableNuGetSupport" => false, 54 | "enableGemsSupport" => false, 55 | "checksumPolicyType" => "server-generated-checksums", 56 | "handleReleases" => true, 57 | "handleSnapshots" => false, 58 | "maxUniqueSnapshots" => 10, 59 | "snapshotVersionBehavior" => "unique", 60 | "suppressPomConsistencyChecks" => true, 61 | "blackedOut" => false, 62 | "propertySets" => ["artifactory"], 63 | "archiveBrowsingEnabled" => false, 64 | "calculateYumMetadata" => false, 65 | "yumRootDepth" => 3, 66 | "rclass" => "local", 67 | "url" => "someurl", 68 | "respositories" => [], 69 | "externalDependenciesEnabled" => false, 70 | "clientTlsCertificate" => "test", 71 | } 72 | end 73 | 74 | it "creates a new instance" do 75 | instance = described_class.from_hash(hash) 76 | expect(instance.blacked_out).to be_falsey 77 | expect(instance.description).to eq("Local repository for in-house libraries") 78 | expect(instance.checksum_policy_type).to eq("server-generated-checksums") 79 | expect(instance.excludes_pattern).to eq("") 80 | expect(instance.handle_releases).to be_truthy 81 | expect(instance.handle_snapshots).to be_falsey 82 | expect(instance.includes_pattern).to eq("**/*") 83 | expect(instance.key).to eq("libs-release-local") 84 | expect(instance.max_unique_snapshots).to eq(10) 85 | expect(instance.notes).to eq("") 86 | expect(instance.package_type).to eq("generic") 87 | expect(instance.property_sets).to eq(["artifactory"]) 88 | expect(instance.rclass).to eq("local") 89 | expect(instance.repo_layout_ref).to eq("simple-default") 90 | expect(instance.snapshot_version_behavior).to eq("unique") 91 | expect(instance.suppress_pom_consistency_checks).to be_truthy 92 | expect(instance.url).to eq("someurl") 93 | expect(instance.yum_root_depth).to eq(3) 94 | expect(instance.calculate_yum_metadata).to eq(false) 95 | expect(instance.repositories).to eq([]) 96 | expect(instance.external_dependencies_enabled).to eq(false) 97 | expect(instance.client_tls_certificate).to eq("test") 98 | end 99 | end 100 | 101 | describe "#save" do 102 | let(:client) { double } 103 | before do 104 | subject.client = client 105 | subject.key = "libs-release-local" 106 | end 107 | 108 | context "when the repository is new" do 109 | before do 110 | allow(described_class).to receive(:find).with(subject.key, client: client).and_return(nil) 111 | end 112 | 113 | it "PUTS the file to the server" do 114 | expect(client).to receive(:put).with("/api/repositories/#{subject.key}", kind_of(String), kind_of(Hash)) 115 | subject.save 116 | end 117 | end 118 | 119 | context "when the repository exists" do 120 | before do 121 | allow(described_class).to receive(:find).with(subject.key, client: client).and_return({ key: subject.key }) 122 | end 123 | 124 | it "POSTS the file to the server" do 125 | expect(client).to receive(:post).with("/api/repositories/#{subject.key}", kind_of(String), kind_of(Hash)) 126 | subject.save 127 | end 128 | end 129 | end 130 | 131 | describe "#upload" do 132 | let(:client) { double(put: {}) } 133 | let(:file) { double(File) } 134 | let(:path) { "/fake/path" } 135 | before do 136 | subject.client = client 137 | subject.key = "libs-release-local" 138 | allow(File).to receive(:new).with(/\A(\w:)?#{path}\z/).and_return(file) 139 | end 140 | 141 | context "when the artifact is a file path" do 142 | it "PUTs the file at the path to the server" do 143 | expect(client).to receive(:put).with("libs-release-local/remote/path", file, {}) 144 | 145 | subject.upload(path, "/remote/path") 146 | end 147 | end 148 | 149 | context "when matrix properties are given" do 150 | it "converts the hash into matrix properties" do 151 | expect(client).to receive(:put).with("libs-release-local;branch=master;user=Seth+Vargo%2B1/remote/path", file, {}) 152 | 153 | subject.upload(path, "/remote/path", 154 | branch: "master", 155 | user: "Seth Vargo+1") 156 | end 157 | 158 | it "converts the hash into matrix properties" do 159 | expect(client).to receive(:put).with("libs-release-local;branch=master;user=Seth/remote/path", file, {}) 160 | 161 | subject.upload(path, "/remote/path", 162 | branch: "master", 163 | user: "Seth") 164 | end 165 | 166 | it 'converts spaces to "+" characters' do 167 | expect(client).to receive(:put).with("libs-release-local;user=Seth+Vargo/remote/path", file, {}) 168 | 169 | subject.upload(path, "/remote/path", 170 | user: "Seth Vargo") 171 | end 172 | 173 | it 'converts "+" to "%2B"' do 174 | expect(client).to receive(:put).with("libs-release-local;version=12.0.0-alpha.1%2B20140826080510.git.50.f5ff271/remote/path", file, {}) 175 | 176 | subject.upload(path, "/remote/path", 177 | version: "12.0.0-alpha.1+20140826080510.git.50.f5ff271") 178 | end 179 | end 180 | 181 | context "when custom headers are given" do 182 | it "passes the headers to the client" do 183 | headers = { "Content-Type" => "text/plain" } 184 | expect(client).to receive(:put).with("libs-release-local/remote/path", file, headers) 185 | 186 | subject.upload(path, "/remote/path", {}, headers) 187 | end 188 | end 189 | end 190 | 191 | describe "#artifacts" do 192 | before { subject.key = "libs-release-local" } 193 | 194 | it "returns an artifact collection" do 195 | expect(subject.artifacts).to be_a(Collection::Artifact) 196 | end 197 | end 198 | 199 | describe "#upload_with_checksum" do 200 | it "delecates to artifact" do 201 | artifact = double("Artifact") 202 | allow(Resource::Artifact).to receive(:new) { artifact } 203 | subject.key = "libs-release-local" 204 | expect(artifact).to receive(:upload_with_checksum).once 205 | subject.upload_with_checksum("/local/path", "/remote/path", "checksum", { properties: :foobar }) 206 | end 207 | end 208 | 209 | describe "#upload_from_archive" do 210 | it "delecates to artifact" do 211 | artifact = double("Artifact") 212 | allow(Resource::Artifact).to receive(:new) { artifact } 213 | subject.key = "libs-release-local" 214 | expect(artifact).to receive(:upload_from_archive).once 215 | subject.upload_from_archive("/local/path", "/remote/path", { properties: :foobar }) 216 | end 217 | end 218 | end 219 | end 220 | -------------------------------------------------------------------------------- /spec/unit/resources/system_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::System do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".info" do 13 | let(:response) { "This is the response..." } 14 | 15 | it "calls /api/system" do 16 | expect(client).to receive(:get).with("/api/system").once 17 | described_class.info 18 | end 19 | 20 | it "returns the plan-text body" do 21 | expect(described_class.info).to eq(response) 22 | end 23 | end 24 | 25 | describe ".ping" do 26 | let(:response) { "" } 27 | 28 | it "gets /api/system/ping" do 29 | expect(client).to receive(:get).with("/api/system/ping").once 30 | described_class.ping 31 | end 32 | 33 | context "when the system is ok" do 34 | it "returns true" do 35 | expect(described_class.ping).to be_truthy 36 | end 37 | end 38 | 39 | context "when the system is not running" do 40 | it "returns false" do 41 | allow(client).to receive(:get) 42 | .and_raise(Error::ConnectionError.new(Artifactory.endpoint)) 43 | expect(described_class.ping).to be_falsey 44 | end 45 | end 46 | end 47 | 48 | describe ".configuration" do 49 | let(:response) { "" } 50 | 51 | it "gets /api/system/configuration" do 52 | expect(client).to receive(:get).with("/api/system/configuration").once 53 | described_class.configuration 54 | end 55 | 56 | it "returns the xml" do 57 | expect(described_class.configuration).to be_a(REXML::Document) 58 | end 59 | end 60 | 61 | describe ".update_configuration" do 62 | let(:xml) { double(:xml) } 63 | let(:response) { double(body: "...") } 64 | before { allow(client).to receive(:post).and_return(response) } 65 | 66 | it "posts /api/system/configuration" do 67 | headers = { "Content-Type" => "application/xml" } 68 | expect(client).to receive(:post).with("/api/system/configuration", xml, headers).once 69 | described_class.update_configuration(xml) 70 | end 71 | 72 | it "returns the body of the response" do 73 | expect(described_class.update_configuration(xml)).to eq(response) 74 | end 75 | end 76 | 77 | describe ".version" do 78 | let(:response) { double(json: { "foo" => "bar" }) } 79 | 80 | it "gets /api/system/version" do 81 | expect(client).to receive(:get).with("/api/system/version").once 82 | described_class.version 83 | end 84 | 85 | it "returns the parsed JSON of the response" do 86 | expect(described_class.version).to eq(response) 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/unit/resources/url_base_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::URLBase do 5 | let(:client) { double(:client) } 6 | 7 | before(:each) do 8 | allow(Artifactory).to receive(:client).and_return(client) 9 | allow(client).to receive(:get).and_return(response) if defined?(response) 10 | end 11 | 12 | describe ".all" do 13 | doc = <<-XML 14 | 15 | http://33.33.33.20/artifactory 16 | 17 | XML 18 | let(:xml) do 19 | REXML::Document.new(doc) 20 | end 21 | 22 | before do 23 | allow(Resource::System).to receive(:configuration).and_return(xml) 24 | end 25 | 26 | it "returns the url bases" do 27 | expect(described_class.all).to be_a(Array) 28 | expect(described_class.all.first).to be_a(described_class) 29 | expect(described_class.all.first.url_base).to eq("http://33.33.33.20/artifactory") 30 | end 31 | end 32 | 33 | describe ".find" do 34 | doc = <<-XML 35 | 36 | http://proxyserver/artifactory 37 | 38 | XML 39 | let(:xml) do 40 | REXML::Document.new(doc) 41 | end 42 | 43 | before do 44 | allow(Resource::System).to receive(:configuration).and_return(xml) 45 | end 46 | 47 | it "returns the found urlBase" do 48 | expect(described_class.find("http://proxyserver/artifactory")).to be_a(described_class) 49 | expect(described_class.find("http://proxyserver/artifactory").url_base).to eq("http://proxyserver/artifactory") 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/unit/resources/user_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Artifactory 4 | describe Resource::User do 5 | let(:client) { double(:client) } 6 | let(:endpoint_host) { "http://33.33.33.11" } 7 | let(:endpoint) { "#{endpoint_host}/" } 8 | 9 | before(:each) do 10 | allow(Artifactory).to receive(:client).and_return(client) 11 | allow(client).to receive(:get).and_return(response) if defined?(response) 12 | end 13 | 14 | describe ".all" do 15 | let(:response) do 16 | [ 17 | { "uri" => "a" }, 18 | { "uri" => "b" }, 19 | { "uri" => "c" }, 20 | ] 21 | end 22 | before do 23 | allow(described_class).to receive(:from_url).with("a", client: client).and_return("a") 24 | allow(described_class).to receive(:from_url).with("b", client: client).and_return("b") 25 | allow(described_class).to receive(:from_url).with("c", client: client).and_return("c") 26 | end 27 | 28 | it "gets /api/security/users" do 29 | expect(client).to receive(:get).with("/api/security/users").once 30 | described_class.all 31 | end 32 | 33 | it "returns the users" do 34 | expect(described_class.all).to eq(%w{a b c}) 35 | end 36 | end 37 | 38 | describe ".find" do 39 | let(:response) { {} } 40 | 41 | it 'gets /api/repositories/#{name}' do 42 | expect(client).to receive(:get).with("/api/security/users/readers").once 43 | described_class.find("readers") 44 | end 45 | end 46 | 47 | describe ".from_url" do 48 | let(:response) { {} } 49 | 50 | it "constructs a new instance from the result" do 51 | expect(client).to receive(:endpoint).and_return(endpoint) 52 | expect(described_class).to receive(:from_hash).once 53 | described_class.from_url("/api/security/users/readers") 54 | end 55 | end 56 | 57 | describe ".from_hash" do 58 | let(:hash) do 59 | { 60 | "admin" => false, 61 | "email" => "admin@example.com", 62 | "groups" => ["admin"], 63 | "internalPasswordDisabled" => true, 64 | "lastLoggedIn" => nil, 65 | "name" => "admin", 66 | "profileUpdatable" => true, 67 | "realm" => "artifactory", 68 | } 69 | end 70 | 71 | it "creates a new instance" do 72 | instance = described_class.from_hash(hash) 73 | expect(instance.admin).to be_falsey 74 | expect(instance.email).to eq("admin@example.com") 75 | expect(instance.groups).to eq(["admin"]) 76 | expect(instance.internal_password_disabled).to be_truthy 77 | expect(instance.last_logged_in).to be_nil 78 | expect(instance.name).to eq("admin") 79 | expect(instance.password).to be_nil 80 | expect(instance.profile_updatable).to be_truthy 81 | expect(instance.realm).to eq("artifactory") 82 | end 83 | end 84 | 85 | describe "#save" do 86 | let(:client) { double } 87 | before do 88 | subject.client = client 89 | subject.name = "schisamo" 90 | end 91 | 92 | context "when the user is new" do 93 | before do 94 | allow(described_class).to receive(:find).with(subject.name, client: client).and_return(nil) 95 | end 96 | 97 | it "PUTS the user to the server" do 98 | expect(client).to receive(:put).with("/api/security/users/#{subject.name}", kind_of(String), kind_of(Hash)) 99 | subject.save 100 | end 101 | end 102 | 103 | context "when the user exists" do 104 | before do 105 | allow(described_class).to receive(:find).with(subject.name, client: client).and_return({ name: subject.name }) 106 | end 107 | 108 | it "POSTS the user to the server" do 109 | expect(client).to receive(:post).with("/api/security/users/#{subject.name}", kind_of(String), kind_of(Hash)) 110 | subject.save 111 | end 112 | end 113 | end 114 | end 115 | end 116 | --------------------------------------------------------------------------------