pax_global_header00006660000000000000000000000064140763740520014522gustar00rootroot0000000000000052 comment=4f6daba809245637e926458f3f62bfe556986215 vagrant_cloud-3.0.5/000077500000000000000000000000001407637405200143575ustar00rootroot00000000000000vagrant_cloud-3.0.5/.ci/000077500000000000000000000000001407637405200150305ustar00rootroot00000000000000vagrant_cloud-3.0.5/.ci/build.sh000077500000000000000000000011161407637405200164650ustar00rootroot00000000000000#!/usr/bin/env bash export SLACK_USERNAME="Vagrant Cloud RubyGem" export SLACK_ICON="https://avatars.slack-edge.com/2017-10-17/257000837696_070f98107cdacc0486f6_36.png" export SLACK_TITLE="💎 RubyGems Publishing" csource="${BASH_SOURCE[0]}" while [ -h "$csource" ] ; do csource="$(readlink "$csource")"; done root="$( cd -P "$( dirname "$csource" )/../" && pwd )" . "${root}/.ci/init.sh" pushd "${root}" > "${output}" slack "📢 New vagrant_cloud release has been triggered" # Build and publish our gem publish_to_rubygems slack -m "New version of vagrant_cloud published ${tag}" vagrant_cloud-3.0.5/.ci/init.sh000066400000000000000000000001731407637405200163300ustar00rootroot00000000000000#!/usr/bin/env bash . "${root}/.ci/load-ci.sh" export DEBIAN_FRONTEND="noninteractive" export PATH="${PATH}:${root}/.ci" vagrant_cloud-3.0.5/.ci/load-ci.sh000077500000000000000000000054461407637405200167100ustar00rootroot00000000000000#!/usr/bin/env bash echo "🤖 Loading VagrantCI 🤖" ldir="$(realpath ./.ci-utility-files)" # If utility files have not yet been pulled, fetch them if [ ! -e "${ldir}/.complete" ]; then # Validate that we have the AWS CLI available if ! command -v aws > /dev/null 2>&1; then echo "⚠ ERROR: Missing required aws executable ⚠" exit 1 fi # Validate that we have the jq tool available if ! command -v jq > /dev/null 2>&1; then echo "⚠ ERROR: Missing required jq executable ⚠" exit 1 fi # If we have a role defined, assume it so we can get access to files if [ "${AWS_ASSUME_ROLE_ARN}" != "" ] && [ "${AWS_SESSION_TOKEN}" = "" ]; then if output="$(aws sts assume-role --role-arn "${AWS_ASSUME_ROLE_ARN}" --role-session-name "CI-initializer")"; then export CORE_AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" export CORE_AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" id="$(printf '%s' "${output}" | jq -r .Credentials.AccessKeyId)" || failed=1 key="$(printf '%s' "${output}" | jq -r .Credentials.SecretAccessKey)" || failed=1 token="$(printf '%s' "${output}" | jq -r .Credentials.SessionToken)" || failed=1 expire="$(printf '%s' "${output}" | jq -r .Credentials.Expiration)" || failed=1 if [ "${failed}" = "1" ]; then echo "🛑 ERROR: Failed to extract role credentials 🛑" exit 1 fi export AWS_ACCESS_KEY_ID="${id}" export AWS_SECRET_ACCESS_KEY="${key}" export AWS_SESSION_TOKEN="${token}" export AWS_SESSION_EXPIRATION="${expire}" else echo "⛔ ERROR: Failed to assume configured AWS role ⛔" exit 1 fi fi # Create a local directory to stash our stuff in if ! mkdir -p "${ldir}"; then echo "⛔ ERROR: Failed to create utility file directory ⛔" exit 1 fi # Jump into local directory and grab files if ! pushd "${ldir}"; then echo "⁉ ERROR: Unexpected error, failed to relocate to expected directory ⁉" exit 1 fi if ! aws s3 sync "${VAGRANT_CI_LOADER_BUCKET}/ci-files/" ./; then echo "🛑 ERROR: Failed to retrieve utility files 🛑" exit 1 fi if ! chmod a+x ./*; then echo "⛔ ERROR: Failed to set permissions on CI files ⛔" exit 1 fi # Mark that we have pulled files touch .complete || echo "WARNING: Failed to mark CI files as fetched" # Time to load and configure if ! popd; then echo "⁉ ERROR: Unexpected error, failed to relocate to expected directory ⁉" exit 1 fi fi source "${ldir}/common.sh" export PATH="${PATH}:${ldir}" # And we are done! echo "🎉 VagrantCI Loaded! 🎉" vagrant_cloud-3.0.5/.ci/test.sh000077500000000000000000000006131407637405200163460ustar00rootroot00000000000000#!/usr/bin/env bash csource="${BASH_SOURCE[0]}" while [ -h "$csource" ] ; do csource="$(readlink "$csource")"; done root="$( cd -P "$( dirname "$csource" )/../" && pwd )" pushd "${root}" > /dev/null # Ensure bundler is installed gem install --no-document bundler || exit 1 # Install the bundle bundle install || exit 1 # Run tests bundle exec rake result=$? popd > /dev/null exit $result vagrant_cloud-3.0.5/.github/000077500000000000000000000000001407637405200157175ustar00rootroot00000000000000vagrant_cloud-3.0.5/.github/workflows/000077500000000000000000000000001407637405200177545ustar00rootroot00000000000000vagrant_cloud-3.0.5/.github/workflows/gempush.yml000066400000000000000000000023341407637405200221510ustar00rootroot00000000000000name: Ruby Gem on: push: branches: - 'release-*' tags: - 'v*' env: ASSETS_PRIVATE_BUCKET: ${{ secrets.ASSETS_PRIVATE_BUCKET }} ASSETS_PRIVATE_LONGTERM: ${{ secrets.ASSETS_PRIVATE_LONGTERM }} ASSETS_PRIVATE_SHORTTERM: ${{ secrets.ASSETS_PRIVATE_SHORTTERM }} ASSETS_PUBLIC_BUCKET: ${{ secrets.ASSETS_PUBLIC_BUCKET }} ASSETS_PUBLIC_SHORTTERM: ${{ secrets.ASSETS_PUBLIC_SHORTTERM }} ASSETS_PUBLIC_LONGTERM: ${{ secrets.ASSETS_PUBLIC_LONGTERM }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_ASSUME_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} HASHIBOT_EMAIL: ${{ secrets.HASHIBOT_EMAIL }} HASHIBOT_USERNAME: ${{ secrets.HASHIBOT_USERNAME }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} VAGRANT_CI_LOADER_BUCKET: ${{ secrets.VAGRANT_CI_LOADER_BUCKET }} jobs: build: name: Build + Publish runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - name: Set up Ruby 2.6 uses: actions/setup-ruby@v1 with: version: 2.6.x - name: Publish to RubyGems run: ./.ci/build.sh env: RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} working-directory: ${{ github.workspace }} vagrant_cloud-3.0.5/.github/workflows/testing.yml000066400000000000000000000012111407637405200221470ustar00rootroot00000000000000on: push: branches: - main - 'test-*' paths: - 'lib/**' - 'spec/**' pull_request: branches: - main paths: - 'lib/**' - 'spec/**' jobs: unit-tests: runs-on: ubuntu-18.04 strategy: matrix: ruby: [ '2.5.x', '2.6.x', '2.7.x', '3.0.x' ] name: Vagrant Cloud unit tests on Ruby ${{ matrix.ruby }} steps: - name: Code Checkout uses: actions/checkout@v1 - name: Setup Ruby uses: actions/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} architecture: 'x64' - name: Run Tests run: .ci/test.sh vagrant_cloud-3.0.5/.gitignore000066400000000000000000000000451407637405200163460ustar00rootroot00000000000000/Gemfile.lock /vendor /.bundle *.gem vagrant_cloud-3.0.5/.runner.sh000077500000000000000000000001401407637405200163000ustar00rootroot00000000000000#!/usr/bin/env bash set -e gem build *.gemspec mkdir -p assets mv vagrant_cloud-*.gem assets/ vagrant_cloud-3.0.5/CHANGELOG.md000066400000000000000000000042661407637405200162000ustar00rootroot00000000000000# v3.0.5 (July 22, 2021) * Updates for Ruby 3 compatibility [GH-76](https://github.com/hashicorp/vagrant_cloud/pull/76) # v3.0.4 (March 18, 2021) * Ensure URL is included when saving provider [GH-69](https://github.com/hashicorp/vagrant_cloud/pull/69) # v3.0.3 (February 19, 2021) * Save box before saving versions [GH-70](https://github.com/hashicorp/vagrant_cloud/pull/70) # v3.0.2 (October 30, 2020) * Raise custom exception on request error [GH-67](https://github.com/hashicorp/vagrant_cloud/pull/67) # v3.0.1 (October 27, 2020) * Fixes on authentication related client methods [GH-65](https://github.com/hashicorp/vagrant_cloud/pull/65) * Prevent frozen data modifications on deletions [GH-65](https://github.com/hashicorp/vagrant_cloud/pull/65) * Update direct upload callback behaviors [GH-65](https://github.com/hashicorp/vagrant_cloud/pull/65) # v3.0.0 (September 21, 2020) * Refactor library implementation [GH-59](https://github.com/hashicorp/vagrant_cloud/pull/59) * Add support for direct storage uploads [GH-62](https://github.com/hashicorp/vagrant_cloud/pull/62) _NOTE_: This release includes breaking changes and is not backwards compatible # v2.0.3 (October 8, 2019) * Pass access_token and base_url into legacy ensure methods [GH-50](https://github.com/hashicorp/vagrant_cloud/pull/50) * Support passing checksum and checksum type values [GH-51](https://github.com/hashicorp/vagrant_cloud/pull/51) # v2.0.2 (January 9, 2019) * Properly raise error if CLI is invoked [GH-40](https://github.com/hashicorp/vagrant_cloud/pull/40) * Only update Box attribute if non-empty hash is given [GH-44](https://github.com/hashicorp/vagrant_cloud/pull/44) * Raise InvalidVersion error if version number for Version attribute is invalid [GH-45](https://github.com/hashicorp/vagrant_cloud/pull/45) * Fix `ensure_box` when box does not exist [GH-43](https://github.com/hashicorp/vagrant_cloud/pull/43) # v2.0.1 * Remove JSON runtime dependency [GH-39](https://github.com/hashicorp/vagrant_cloud/pull/39) # v2.0.0 * Refactor with updated APIs [GH-35](https://github.com/hashicorp/vagrant_cloud/pull/35) * Use header for authentication token [GH-33](https://github.com/hashicorp/vagrant_cloud/pull/33) # v1.1.0 vagrant_cloud-3.0.5/Gemfile000066400000000000000000000000471407637405200156530ustar00rootroot00000000000000source 'https://rubygems.org' gemspec vagrant_cloud-3.0.5/LICENSE000066400000000000000000000021231407637405200153620ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Cargo Media Copyright (c) 2017 HashiCorp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vagrant_cloud-3.0.5/README.md000066400000000000000000000106241407637405200156410ustar00rootroot00000000000000# vagrant_cloud Ruby client for the [Vagrant Cloud API](https://www.vagrantup.com/docs/vagrant-cloud/api.html). [![Gem Version](https://img.shields.io/gem/v/vagrant_cloud.svg)](https://rubygems.org/gems/vagrant_cloud) This library provides the functionality to create, modify, and delete boxes, versions, and providers on Vagrant Cloud. ## Usage The Vagrant Cloud library provides two methods for interacting with the Vagrant Cloud API. The first is direct interaction using a `VagrantCloud::Client` instance. The second is a basic model based approach using a `VagrantCloud::Account` instance. ### Direct Client The `VagrantCloud::Client` class contains all the underlying functionality which with `vagrant_cloud` library uses for communicating with Vagrant Cloud. It can be used directly for quickly and easily sending requests to Vagrant Cloud. The `VagrantCloud::Client` class will automatically handle any configured authentication, request parameter structuring, and response validation. All API related methods in the `VagrantCloud::Client` class will return `Hash` results. Example usage (display box details): ```ruby require "vagrant_cloud" client = VagrantCloud::Client.new(access_token: "MY_TOKEN") box = client.box_get(username: "hashicorp", name: "bionic64") puts "Box: #{box[:tag]} Description: #{box[:description]}" ``` Example usage (creating box and releasing a new version): ```ruby require "vagrant_cloud" require "net/http" # Create a new client client = VagrantCloud::Client.new(access_token: "MY_TOKEN") # Create a new box client.box_create( username: "hashicorp", name: "test-bionic64", short_description: "Test Box", long_description: "Testing box for an example", is_private: false ) # Create a new version client.box_version_create( username: "hashicorp", name: "test-bionic64", version: "1.0.0", description: "Version 1.0.0 release" ) # Create a new provider client.box_version_provider_create( username: "hashicorp", name: "test-bionic64", version: "1.0.0", provider: "virtualbox" ) # Request box upload URL upload_url = client.box_version_provider_upload( username: "hashicorp", name: "test-bionic64", version: "1.0.0", provider: "virtualbox" ) # Upload box asset uri = URI.parse(upload_url[:upload_path]) request = Net::HTTP::Post.new(uri) box = File.open(BOX_PATH, "rb") request.set_form([["file", box]], "multipart/form-data") response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme.eql?("https")) do |http| http.request(request) end # Release the version client.box_version_release( username: "hashicorp", name: "test-bionic64", version: "1.0.0" ) ``` ### Simple Models The `VagrantCloud::Account` class is the entry point for using simple models to interact with Vagrant Cloud. Example usage (display box details): ```ruby require "vagrant_cloud" account = VagrantCloud::Account.new(access_token: "MY_TOKEN") org = account.organization(name: "hashicorp") box = org.boxes.detect { |b| b.name == "bionic64" } puts "Box: #{box[:tag]} Description: #{box[:description]}" ``` Example usage (creating box and releasing a new version): ```ruby require "vagrant_cloud" # Load our account account = VagrantCloud::Account.new(access_token: "MY_TOKEN") # Load organization org = account.organization(name: "hashicorp") # Create a new box box = org.add_box("test-bionic64") box.description = "Testing box for an example" box.short_description = "Test Box" # Create a new version version = box.add_version("1.0.0") version.description = "Version 1.0.0 release" # Create a new provider provider = version.add_provider("virtualbox") # Save the box, version, and provider box.save # Upload box asset provider.upload(path: BOX_PATH) # Release the version version.release ``` ## Development & Contributing Pull requests are very welcome! Install dependencies: ``` bundle install ``` Run the tests: ``` bundle exec rspec ``` ## Releasing Release a new version: 1. Update the version in the `version.txt` file 1. Commit the change to master 1. Create a new version tag in git: `git tag vX.X.X` 1. Push the new tag and master to GitHub `git push origin main --tags` The new release will be automatically built and published. ## History - This gem was developed and maintained by [Cargo Media](https://www.cargomedia.ch) from April 2014 until October 2017. - The `vagrant_cloud` CLI tool included in this RubyGem has been deprecated and removed. See `vagrant cloud` for a replacement. vagrant_cloud-3.0.5/Rakefile000066400000000000000000000002741407637405200160270ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path('lib', __dir__) require 'bundler/gem_tasks' Dir.glob(File.expand_path('tasks/*.rake', __dir__)).each do |task| load task end task default: [:spec] vagrant_cloud-3.0.5/lib/000077500000000000000000000000001407637405200151255ustar00rootroot00000000000000vagrant_cloud-3.0.5/lib/vagrant_cloud.rb000066400000000000000000000011771407637405200203100ustar00rootroot00000000000000require "excon" require "log4r" require "json" require "securerandom" require "set" require 'singleton' require "thread" module VagrantCloud autoload :Account, "vagrant_cloud/account" autoload :Box, "vagrant_cloud/box" autoload :Client, "vagrant_cloud/client" autoload :Data, "vagrant_cloud/data" autoload :Error, "vagrant_cloud/error" autoload :Instrumentor, "vagrant_cloud/instrumentor" autoload :Logger, "vagrant_cloud/logger" autoload :Organization, "vagrant_cloud/organization" autoload :Response, "vagrant_cloud/response" autoload :Search, "vagrant_cloud/search" autoload :VERSION, "vagrant_cloud/version" end vagrant_cloud-3.0.5/lib/vagrant_cloud/000077500000000000000000000000001407637405200177555ustar00rootroot00000000000000vagrant_cloud-3.0.5/lib/vagrant_cloud/account.rb000066400000000000000000000065341407637405200217460ustar00rootroot00000000000000module VagrantCloud # VagrantCloud account class Account # @return [Client] attr_reader :client # @return [String] username of this account attr_reader :username # @return [Instrumentor::Collection] Instrumentor in use attr_reader :instrumentor # Create a new Account instance # # @param [String] access_token Authentication token # @param [Client] client Client to use for account # @param [String] custom_server Custom server URL for client # @param [Integer] retry_count Number of retries on idempotent requests # @param [Integer] retry_interval Number of seconds to wait between requests # @param [Instrumentor::Core] instrumentor Instrumentor to use # @return [Account] def initialize(access_token: nil, client: nil, custom_server: nil, retry_count: nil, retry_interval: nil, instrumentor: nil) raise ArgumentError, "Account accepts `access_token` or `client` but not both" if client && access_token raise TypeError, "Expected `#{Client.name}` but received `#{client.class.name}`" if client && !client.is_a?(Client) if client @client = client else @client = Client.new( access_token: access_token, url_base: custom_server, retry_count: retry_count, retry_interval: retry_interval, instrumentor: instrumentor ) end setup! end # @return [Search] def searcher Search.new(account: self) end #--------------------------- # Authentication API Helpers #--------------------------- # Create a new access token # @param [String] password Remote password # @param [String] description Description of token # @param [String] code 2FA code # @return [Response::CreateToken] def create_token(password:, description: Data::Nil, code: Data::Nil) r = client.authentication_token_create(username: username, password: password, description: description, code: code) Response::CreateToken.new( token: r[:token], token_hash: r[:token_hash], created_at: r[:created_at], description: r[:description] ) end # Delete the current token # # @return [self] def delete_token client.authentication_token_delete self end # Validate the current token # # @return [self] def validate_token client.request(path: "authenticate") self end # Request a 2FA code is sent # # @param [String] delivery_method Delivery method of 2FA # @param [String] password Account password # @return [Response] def request_2fa_code(delivery_method:, password:) r = client.authentication_request_2fa_code(username: username, password: password, delivery_method: delivery_method) Response::Request2FA.new(destination: r.dig(:two_factor, :obfuscated_destination)) end # Fetch the requested organization # # @param [String] name Organization name # @return [Organization] def organization(name: nil) org_name = name || username r = client.organization_get(name: org_name) Organization.load(account: self, **r) end protected def setup! if client.access_token r = client.request(path: "authenticate") @username = r.dig(:user, :username) end end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/box.rb000066400000000000000000000074441407637405200211030ustar00rootroot00000000000000module VagrantCloud class Box < Data::Mutable autoload :Provider, "vagrant_cloud/box/provider" autoload :Version, "vagrant_cloud/box/version" attr_reader :organization attr_required :name attr_optional :created_at, :updated_at, :tag, :short_description, :description_html, :description_markdown, :private, :downloads, :current_version, :versions, :description, :username attr_mutable :short_description, :description, :private, :versions # Create a new instance # # @return [Box] def initialize(organization:, **opts) @organization = organization @versions_loaded = false opts[:username] = organization.username super(**opts) if opts[:versions] && !opts[:versions].empty? self.versions= Array(opts[:versions]).map do |version| Box::Version.load(box: self, **version) end end if opts[:current_version] clean(data: {current_version: Box::Version. load(box: self, **opts[:current_version])}) end clean! end # Delete this box # # @return [nil] # @note This will delete the box, and all versions def delete if exist? organization.account.client.box_delete( username: username, name: name ) b = organization.boxes.dup b.delete(self) organization.clean(data: {boxes: b}) end nil end # Add a new version of this box # # @param [String] version Version number # @return [Version] def add_version(version) if versions.any? { |v| v.version == version } raise Error::BoxError::VersionExistsError, "Version #{version} already exists for box #{tag}" end v = Version.new(box: self, version: version) clean(data: {versions: versions + [v]}) v end # Check if this instance is dirty # # @param [Boolean] deep Check nested instances # @return [Boolean] instance is dirty def dirty?(key=nil, deep: false) if key super(key) else d = super() || !exist? if deep && !d d = Array(plain_versions).any? { |v| v.dirty?(deep: true) } end d end end # @return [Boolean] box exists remotely def exist? !!created_at end # @return [Array] # @note This is used to allow versions information to be loaded # only when requested def versions_on_demand if !@versions_loaded if exist? r = self.organization.account.client.box_get(username: username, name: name) v = Array(r[:versions]).map do |version| Box::Version.load(box: self, **version) end clean(data: {versions: v + Array(plain_versions)}) else clean(data: {versions: []}) end @versions_loaded = true end plain_versions end alias_method :plain_versions, :versions alias_method :versions, :versions_on_demand # Save the box if any changes have been made # # @return [self] def save save_box if dirty? save_versions if dirty?(deep: true) self end protected # Save the box # # @return [self] def save_box req_args = { username: username, name: name, short_description: short_description, description: description, is_private: self.private } if exist? result = organization.account.client.box_update(**req_args) else result = organization.account.client.box_create(**req_args) end clean(data: result, ignores: [:current_version, :versions]) self end # Save the versions if any require saving # # @return [self] def save_versions versions.map(&:save) self end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/box/000077500000000000000000000000001407637405200205455ustar00rootroot00000000000000vagrant_cloud-3.0.5/lib/vagrant_cloud/box/provider.rb000066400000000000000000000133671407637405200227360ustar00rootroot00000000000000module VagrantCloud class Box class Provider < Data::Mutable # Result for upload requests to upload directly to the # storage backend. # # @param [String] upload_url URL for uploading file asset # @param [String] callback_url URL callback to PUT after successful upload # @param [Proc] callback Callable proc to perform callback via configured client DirectUpload = Struct.new(:upload_url, :callback_url, :callback, keyword_init: true) attr_reader :version attr_required :name attr_optional :hosted, :created_at, :updated_at, :checksum, :checksum_type, :original_url, :download_url, :url attr_mutable :url, :checksum, :checksum_type def initialize(version:, **opts) if !version.is_a?(Version) raise TypeError, "Expecting type `#{Version.name}` but received `#{version.class.name}`" end @version = version super(**opts) end # Delete this provider # # @return [nil] def delete if exist? version.box.organization.account.client.box_version_provider_delete( username: version.box.username, name: version.box.name, version: version.version, provider: name ) pv = version.providers.dup pv.delete(self) version.clean(data: {providers: pv}) end nil end # Upload box file to be hosted on VagrantCloud. This # method provides different behaviors based on the # parameters passed. When the `direct` option is enabled # the upload target will be directly to the backend # storage. However, when the `direct` option is used the # upload process becomes a two steps where a callback # must be called after the upload is complete. # # If the path is provided, the file will be uploaded # and the callback will be requested if the `direct` # option is enabled. # # If a block is provided, the upload URL will be yielded # to the block. If the `direct` option is set, the callback # will be automatically requested after the block execution # has completed. # # If no path or block is provided, the upload URL will # be returned. If the `direct` option is set, the # `DirectUpload` instance will be yielded and it is # the caller's responsibility to issue the callback # # @param [String] path Path to asset # @param [Boolean] direct Upload directly to backend storage # @yieldparam [String] url URL to upload asset # @return [self, Object, String, DirectUpload] self when path provided, result of yield when block provided, URL otherwise # @note The callback request uses PUT request method def upload(path: nil, direct: false) if !exist? raise Error::BoxError::ProviderNotFoundError, "Provider #{name} not found for box #{version.box.tag} version #{version.version}" end if path && block_given? raise ArgumentError, "Only path or block may be provided, not both" end if path && !File.exist?(path) raise Errno::ENOENT, path end req_args = { username: version.box.username, name: version.box.name, version: version.version, provider: name } if direct r = version.box.organization.account.client.box_version_provider_upload_direct(**req_args) else r = version.box.organization.account.client.box_version_provider_upload(**req_args) end result = DirectUpload.new( upload_url: r[:upload_path], callback_url: r[:callback], callback: proc { if r[:callback] version.box.organization.account.client. request(method: :put, path: URI.parse(r[:callback]).path) end } ) if block_given? block_r = yield result.upload_url result[:callback].call block_r elsif path File.open(path, "rb") do |file| chunks = lambda { file.read(Excon.defaults[:chunk_size]).to_s } Excon.put(result.upload_url, request_block: chunks) end result[:callback].call self else # When returning upload information for requester to complete, # return upload URL when `direct` option is false, otherwise # return the `DirectUpload` instance direct ? result : result.upload_url end end # @return [Boolean] provider exists remotely def exist? !!created_at end # Check if this instance is dirty # # @param [Boolean] deep Check nested instances # @return [Boolean] instance is dirty def dirty?(key=nil, **args) if key super(key) else super || !exist? end end # Save the provider if any changes have been made # # @return [self] def save save_provider if dirty? self end protected # Save the provider # # @return [self] def save_provider req_args = { username: version.box.username, name: version.box.name, version: version.version, provider: name, checksum: checksum, checksum_type: checksum_type, url: url } if exist? result = version.box.organization.account.client.box_version_provider_update(**req_args) else result = version.box.organization.account.client.box_version_provider_create(**req_args) end clean(data: result) self end end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/box/version.rb000066400000000000000000000103561407637405200225640ustar00rootroot00000000000000module VagrantCloud class Box class Version < Data::Mutable attr_reader :box attr_required :version attr_optional :status, :description_html, :description_markdown, :created_at, :updated_at, :number, :providers, :description attr_mutable :description def initialize(box:, **opts) if !box.is_a?(Box) raise TypeError, "Expecting type `#{Box.name}` but received `#{box.class.name}`" end @box = box opts[:providers] = Array(opts[:providers]).map do |provider| if provider.is_a?(Provider) provider else Provider.load(version: self, **provider) end end super(**opts) clean! end # Delete this version # # @return [nil] # @note This will delete the version, and all providers def delete if exist? box.organization.account.client.box_version_delete( username: box.username, name: box.name, version: version ) # Remove self from box v = box.versions.dup v.delete(self) box.clean(data: {versions: v}) end nil end # Release this version # # @return [self] def release if released? raise Error::BoxError::VersionStatusChangeError, "Version #{version} is already released for box #{box.tag}" end if !exist? raise Error::BoxError::VersionStatusChangeError, "Version #{version} for box #{box.tag} must be saved before release" end result = box.organization.account.client.box_version_release( username: box.username, name: box.name, version: version ) clean(data: result, only: :status) self end # Revoke this version # # @return [self] def revoke if !released? raise Error::BoxError::VersionStatusChangeError, "Version #{version} is not yet released for box #{box.tag}" end result = box.organization.account.client.box_version_revoke( username: box.username, name: box.name, version: version ) clean(data: result, only: :status) self end # @return [Boolean] def released? status == "active" end # Add a new provider for this version # # @param [String] pname Name of provider # @return [Provider] def add_provider(pname) if providers.any? { |p| p.name == pname } raise Error::BoxError::VersionProviderExistsError, "Provider #{pname} already exists for box #{box.tag} version #{version}" end pv = Provider.new(version: self, name: pname) clean(data: {providers: providers + [pv]}) pv end # Check if this instance is dirty # # @param [Boolean] deep Check nested instances # @return [Boolean] instance is dirty def dirty?(key=nil, deep: false) if key super(key) else d = super() || !exist? if deep && !d d = providers.any? { |p| p.dirty?(deep: true) } end d end end # @return [Boolean] version exists remotely def exist? !!created_at end # Save the version if any changes have been made # # @return [self] def save save_version if dirty? save_providers if dirty?(deep: true) self end protected # Save the version # # @return [self] def save_version params = { username: box.username, name: box.name, version: version, description: description } if exist? result = box.organization.account.client.box_version_update(**params) else result = box.organization.account.client.box_version_create(**params) end clean(data: result, ignores: :providers) self end # Save the providers if any require saving # # @return [self] def save_providers Array(providers).map(&:save) self end end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/client.rb000066400000000000000000000417111407637405200215640ustar00rootroot00000000000000module VagrantCloud class Client include Logger # Base Vagrant Cloud API URL DEFAULT_URL = 'https://vagrantcloud.com/api/v1'.freeze # Valid methods that can be retried IDEMPOTENT_METHODS = [:get, :head].freeze # Number or allowed retries IDEMPOTENT_RETRIES = 3 # Number of seconds to wait between retries IDEMPOTENT_RETRY_INTERVAL = 2 # Methods which require query parameters QUERY_PARAMS_METHODS = [:get, :head, :delete].freeze # Default instrumentor DEFAULT_INSTRUMENTOR = Instrumentor::Collection.new # @return [Instrumentor::Collection] def self.instrumentor DEFAULT_INSTRUMENTOR end # @return [String] Access token for Vagrant Cloud attr_reader :access_token # @return [String] Base request path attr_reader :path_base # @return [String] URL for initializing connection attr_reader :url_base # @return [Integer] Number of retries on idempotent requests attr_reader :retry_count # @return [Integer] Number of seconds to wait between requests attr_reader :retry_interval # @return [Instrumentor::Collection] Instrumentor in use attr_reader :instrumentor # Create a new Client instance # # @param [String] access_token Authentication token for API requests # @param [String] url_base URL used to make API requests # @param [Integer] retry_count Number of retries on idempotent requests # @param [Integer] retry_interval Number of seconds to wait between requests # @param [Instrumentor::Core] instrumentor Instrumentor to use # @return [Client] def initialize(access_token: nil, url_base: nil, retry_count: nil, retry_interval: nil, instrumentor: nil) url_base = DEFAULT_URL if url_base.nil? remote_url = URI.parse(url_base) @url_base = "#{remote_url.scheme}://#{remote_url.host}" @path_base = remote_url.path @access_token = access_token.dup.freeze if access_token if !@access_token && ENV["VAGRANT_CLOUD_TOKEN"] @access_token = ENV["VAGRANT_CLOUD_TOKEN"].dup.freeze end @retry_count = retry_count.nil? ? IDEMPOTENT_RETRIES : retry_count.to_i @retry_interval = retry_interval.nil? ? IDEMPOTENT_RETRY_INTERVAL : retry_interval.to_i @instrumentor = instrumentor.nil? ? Instrumentor::Collection.new : instrumentor headers = {}.tap do |h| h["Accept"] = "application/json" h["Authorization"] = "Bearer #{@access_token}" if @access_token h["Content-Type"] = "application/json" end @connection_lock = Mutex.new @connection = Excon.new(url_base, headers: headers, instrumentor: @instrumentor ) end # Use the remote connection # # @param [Boolean] wait Wait for the connection to be available # @yieldparam [Excon::Connection] # @return [Object] def with_connection(wait: true) raise ArgumentError, "Block expected but no block given" if !block_given? if !wait raise Error::ClientError::ConnectionLockedError, "Connection is currently locked" if !@connection_lock.try_lock begin yield @connection ensure @connection_lock.unlock end else @connection_lock.synchronize { yield @connection } end end # Send a request # @param [String, Symbol] method Request method # @param [String, URI] path Path of request # @param [Hash] params Parameters to send with request # @return [Hash] def request(path:, method: :get, params: {}) if !path.start_with?(path_base) # Build the full path for the request and clean it path = [path_base, path].compact.join("/").gsub(/\/{2,}/, "/") end method = method.to_s.downcase.to_sym # Build base request parameters request_params = { method: method, path: path, expects: [200, 201, 204] } # If this is an idempotent request allow it to retry on failure if IDEMPOTENT_METHODS.include?(method) request_params[:idempotent] = true request_params[:retry_limit] = retry_count request_params[:retry_interval] = retry_interval end # If parameters are provided, set them in the expected location if !params.empty? # Copy the parameters so we can freely modify them params = clean_parameters(params) if QUERY_PARAMS_METHODS.include?(method) request_params[:query] = params else request_params[:body] = JSON.dump(params) end end # Set a request ID so we can track request/responses request_params[:headers] = {"X-Request-Id" => SecureRandom.uuid} begin result = with_connection { |c| c.request(request_params) } rescue Excon::Error::HTTPStatus => err raise Error::ClientError::RequestError.new( "Vagrant Cloud request failed", err.response.body, err.response.status) rescue Excon::Error => err raise Error::ClientError, err.message end parse_json(result.body) end # Clone this client to create a new instance # # @param [String] access_token Authentication token for API requests # @return [Client] def clone(access_token: nil) self.class.new(access_token: access_token, url_base: url_base, retry_count: retry_count, retry_interval: retry_interval ) end # Submit a search on Vagrant Cloud # # @param [String] query Search query # @param [String] provider Limit results to only this provider # @param [String] sort Field to sort results ("downloads", "created", or "updated") # @param [String] order Order to return sorted result ("desc" or "asc") # @param [Integer] limit Number of results to return # @param [Integer] page Page number of results to return # @return [Hash] def search(query: Data::Nil, provider: Data::Nil, sort: Data::Nil, order: Data::Nil, limit: Data::Nil, page: Data::Nil) params = { q: query, provider: provider, sort: sort, order: order, limit: limit, page: page } request(method: :get, path: "search", params: params) end # Create a new access token # # @param [String] username Vagrant Cloud username # @param [String] password Vagrant Cloud password # @param [String] description Description of token # @param [String] code 2FA code # @return [Hash] def authentication_token_create(username:, password:, description: Data::Nil, code: Data::Nil) params = { user: { login: username, password: password }, token: { description: description }, two_factor: { code: code } } request(method: :post, path: "authenticate", params: params) end # Delete the token currently in use # # @return [Hash] empty def authentication_token_delete request(method: :delete, path: "authenticate") end # Request a 2FA code is sent # # @param [String] username Vagrant Cloud username # @param [String] password Vagrant Cloud password # @param [String] delivery_method Delivery method of 2FA # @param [String] password Account password # @return [Hash] def authentication_request_2fa_code(username:, password:, delivery_method:) params = { two_factor: { delivery_method: delivery_method }, user: { login: username, password: password } } request(method: :post, path: "two-factor/request-code", params: params) end # Validate the current token # # @return [Hash] emtpy def authentication_token_validate request(method: :get, path: "authenticate") end # Get an organization # # @param [String] name Name of organization # @return [Hash] organization information def organization_get(name:) request(method: :get, path: "user/#{name}") end # Get an existing box # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @return [Hash] box information def box_get(username:, name:) request(method: :get, path: "/box/#{username}/#{name}") end # Create a new box # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] short_description Short description of box # @param [String] description Long description of box (markdown supported) # @param [Boolean] is_private Set if box is private # @return [Hash] box information def box_create(username:, name:, short_description: Data::Nil, description: Data::Nil, is_private: Data::Nil) request(method: :post, path: '/boxes', params: { username: username, name: name, short_description: short_description, description: description, is_private: is_private }) end # Update an existing box # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] short_description Short description of box # @param [String] description Long description of box (markdown supported) # @param [Boolean] is_private Set if box is private # @return [Hash] box information def box_update(username:, name:, short_description: Data::Nil, description: Data::Nil, is_private: Data::Nil) params = { short_description: short_description, description: description, is_private: is_private } request(method: :put, path: "/box/#{username}/#{name}", params: params) end # Delete an existing box # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @return [Hash] box information def box_delete(username:, name:) request(method: :delete, path: "/box/#{username}/#{name}") end # Get an existing box version # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @return [Hash] box version information def box_version_get(username:, name:, version:) request(method: :get, path: "/box/#{username}/#{name}/version/#{version}") end # Create a new box version # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @param [String] description Box description # @return [Hash] box version information def box_version_create(username:, name:, version:, description: Data::Nil) request(method: :post, path: "/box/#{username}/#{name}/versions", params: { version: { version: version, description: description } }) end # Update an existing box version # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @param [String] description Box description # @return [Hash] box version information def box_version_update(username:, name:, version:, description: Data::Nil) params = { version: { version: version, description: description } } request(method: :put, path: "/box/#{username}/#{name}/version/#{version}", params: params) end # Delete an existing box version # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @return [Hash] box version information def box_version_delete(username:, name:, version:) request(method: :delete, path: "/box/#{username}/#{name}/version/#{version}") end # Release an existing box version # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @return [Hash] box version information def box_version_release(username:, name:, version:) request(method: :put, path: "/box/#{username}/#{name}/version/#{version}/release") end # Revoke an existing box version # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @return [Hash] box version information def box_version_revoke(username:, name:, version:) request(method: :put, path: "/box/#{username}/#{name}/version/#{version}/revoke") end # Get an existing box version provider # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @param [String] provider Provider name # @return [Hash] box version provider information def box_version_provider_get(username:, name:, version:, provider:) request(method: :get, path: "/box/#{username}/#{name}/version/#{version}/provider/#{provider}") end # Create a new box version provider # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @param [String] provider Provider name # @param [String] url Remote URL for box download # @return [Hash] box version provider information def box_version_provider_create(username:, name:, version:, provider:, url: Data::Nil, checksum: Data::Nil, checksum_type: Data::Nil) request(method: :post, path: "/box/#{username}/#{name}/version/#{version}/providers", params: { provider: { name: provider, url: url, checksum: checksum, checksum_type: checksum_type } }) end # Update an existing box version provider # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @param [String] provider Provider name # @param [String] url Remote URL for box download # @return [Hash] box version provider information def box_version_provider_update(username:, name:, version:, provider:, url: Data::Nil, checksum: Data::Nil, checksum_type: Data::Nil) params = { provider: { name: provider, url: url, checksum: checksum, checksum_type: checksum_type } } request(method: :put, path: "/box/#{username}/#{name}/version/#{version}/provider/#{provider}", params: params) end # Delete an existing box version provider # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @param [String] provider Provider name # @return [Hash] box version provider information def box_version_provider_delete(username:, name:, version:, provider:) request(method: :delete, path: "/box/#{username}/#{name}/version/#{version}/provider/#{provider}") end # Upload a box asset for an existing box version provider # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @param [String] provider Provider name # @return [Hash] box version provider upload information (contains upload_path entry) def box_version_provider_upload(username:, name:, version:, provider:) request(method: :get, path: "/box/#{username}/#{name}/version/#{version}/provider/#{provider}/upload") end # Upload a box asset directly to the backend storage for an existing box version provider # # @param [String] username Username/organization name to create box under # @param [String] name Box name # @param [String] version Box version # @param [String] provider Provider name # @return [Hash] box version provider upload information (contains upload_path and callback entries) def box_version_provider_upload_direct(username:, name:, version:, provider:) request(method: :get, path: "/box/#{username}/#{name}/version/#{version}/provider/#{provider}/upload/direct") end protected # Parse a string of JSON # # @param [String] string String of JSON data # @return [Object] # @note All keys are symbolized when parsed def parse_json(string) return {} if string.empty? JSON.parse(string, symbolize_names: true) end # Remove any values that have a default value set # # @param [Object] item Item to clean # @return [Object] cleaned item def clean_parameters(item) case item when Array item = item.find_all { |i| i != Data::Nil } item.map! { |i| clean_parameters(i) } when Hash item = item.dup item.delete_if{ |_,v| v == Data::Nil } item.keys.each do |k| item[k] = clean_parameters(item[k]) end end item end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/data.rb000066400000000000000000000176761407637405200212340ustar00rootroot00000000000000module VagrantCloud # Generic data class which provides simple attribute # data storage using a Hash like interface class Data # Custom nil class which is used for signifying # a nil value that was not set by the user. This # makes it easy to filter out values which are # unset vs. those that are set to nil. class NilClass < BasicObject include ::Singleton def nil?; true; end def ==(v); v.nil? || super(v); end def ===(v); equal?(v); end def equal?(v); v.nil? || super(v); end def to_i; 0; end def to_f; 0.0; end def to_a; []; end def to_h; {}; end def to_s; ""; end def &(_); false; end def |(_); false; end def ^(_); false; end def !; true; end def inspect; 'nil'; end end # Easy to use constant to access general # use instance of our custom nil class Nil = NilClass.instance # Create a new instance # # @return [Data] def initialize(**opts) @data = opts end # Fetch value from data # # @param [String, Symbol] k Name of value # @return [Object] def [](k) @data.key?(k.to_sym) ? @data[k.to_sym] : Nil end # @return [String] def inspect "<#{self.class.name}:#{sprintf("%#x", object_id)}>" end protected def data; @data; end # Immutable data class. This class adds extra functionality to the Data # class like providing attribute methods which can be defined using the # `attr_required` and `attr_optional` methods. Once an instance is created # the data is immutable. For example: # # class MyData < Immutable # attr_required :name # attr_optional :version # end # # When creating a new instance, a name parameter _must_ be provided, # but a version parameter is optional, so both are valid: # # instance = MyData.new(name: "testing", version: "new-version") # # and # # instance = MyData.new(name: "testing") # # but only providing the version is invalid: # # instance = MyData.new(version: "new-version") # -> Exception class Immutable < Data @@lock = Mutex.new # Define attributes which are required def self.attr_required(*args) return @required || [] if args.empty? sync do @required ||= [] if !args.empty? # Create any accessor methods which do not yet exist args = args.map(&:to_sym) - @required args.each do |argument_name| if !method_defined?(argument_name) define_method(argument_name) { send(:[], argument_name.to_sym) } end end @required += args end @required end end # Define attributes which are optional def self.attr_optional(*args) return @optional || [] if args.empty? sync do @optional ||= [] if !args.empty? # Create any accessor method which do not yet exist args = args.map(&:to_sym) - @optional args.each do |argument_name| if !method_defined?(argument_name) define_method(argument_name) { send(:[], argument_name.to_sym) } end end @optional += args end @optional end end # If inherited, set attribute information def self.inherited(klass) klass.attr_required(*attr_required) klass.attr_optional(*attr_optional) klass.class_variable_set(:@@lock, Mutex.new) end # Synchronize action def self.sync @@lock.synchronize do yield end end # Create a new instance # # @return [Immutable] def initialize(**opts) super() self.class.attr_required.each do |attr| if !opts.key?(attr) raise ArgumentError, "Missing required parameter `#{attr}`" end data[attr.to_sym] = opts[attr].dup end self.class.attr_optional.each do |attr| if opts.key?(attr) data[attr.to_sym] = opts[attr].dup end end extras = opts.keys - (self.class.attr_required + self.class.attr_optional) if !extras.empty? raise ArgumentError, "Unknown parameters provided: #{extras.join(",")}" end freezer(@data) end # @return [String] def inspect vars = (self.class.attr_required + self.class.attr_optional).map do |k| val = self.send(:[], k) next if val.nil? || val.to_s.empty? "#{k}=#{val.inspect}" end.compact.join(", ") "<#{self.class.name}:#{sprintf("%#x", object_id)} #{vars}>" end protected # Freeze the given object and all nested # objects that can be found # # @return [Object] def freezer(obj) if obj.is_a?(Enumerable) obj.each do |item| freezer(item) item.freeze end end obj.freeze end end # Mutable data class class Mutable < Immutable # Define attributes which are mutable def self.attr_mutable(*args) sync do args.each do |attr| if !attr_required.include?(attr.to_sym) && !attr_optional.include?(attr.to_sym) raise ArgumentError, "Unknown attribute name provided `#{attr}`" end define_method("#{attr}=") { |v| dirty[attr.to_sym] = v } end end end # Load data and create a new instance # # @param [Hash] options Value to initialize instance # @return [Mutable] def self.load(options={}) opts = {}.tap do |o| (attr_required + attr_optional + self.instance_method(:initialize).parameters.find_all { |i| i.first == :key || i.first == :keyreq }.map(&:last)).each do |k| o[k.to_sym] = options[k.to_sym] end end self.new(**opts) end # Create a new instance # # @return [Mutable] def initialize(**opts) super @dirty = {} end # Fetch value from data # # @param [String, Symbol] k Name of value # @return [Object] def [](k) if dirty?(k) @dirty[k.to_sym] else super end end # Check if instance is dirty or specific # attribute if key is provided # # @param [Symbol] key Key to check # @return [Boolean] instance is dirty def dirty?(key=nil, **opts) if key.nil? !@dirty.empty? else @dirty.key?(key.to_sym) end end # Load given data and ignore any fields # that are provided. Flush dirty state. # # @param [Hash] data Attribute data to load # @param [Array] ignores Fields to skip # @param [Array] only Fields to update # @return [self] def clean(data:, ignores: [], only: []) raise TypeError, "Expected type `Hash` but received `#{data.inspect}`" if !data.is_a?(Hash) new_data = @data.dup ignores = Array(ignores).map(&:to_sym) only = Array(only).map(&:to_sym) data.each do |k, v| k = k.to_sym next if ignores.include?(k) next if !only.empty? && !only.include?(k) if self.respond_to?(k) new_data[k] = v @dirty.delete(k) end end @data = freezer(new_data) self end # Merge values from dirty cache into data # # @return [self] def clean! @data = freezer(@data.merge(@dirty)) @dirty.clear self end # @return [self] disable freezing def freeze self end # @return [Hash] updated attributes protected def dirty; @dirty; end end end Nil = Data::Nil end vagrant_cloud-3.0.5/lib/vagrant_cloud/error.rb000066400000000000000000000026771407637405200214470ustar00rootroot00000000000000module VagrantCloud class Error < StandardError class ClientError < Error class RequestError < ClientError attr_accessor :error_code attr_accessor :error_arr def initialize(msg, http_body, http_code) message = msg begin errors = JSON.parse(http_body) if errors.is_a?(Hash) vagrant_cloud_msg = errors['errors'] if vagrant_cloud_msg.is_a?(Array) message = msg + ' - ' + vagrant_cloud_msg.map(&:to_s).join(', ').to_s elsif !vagrant_cloud_msg.to_s.empty? message = msg + ' - ' + vagrant_cloud_msg.to_s end end rescue JSON::ParserError => err vagrant_cloud_msg = err.message end @error_arr = Array(vagrant_cloud_msg) @error_code = http_code.to_i super(message) end end class ConnectionLockedError < ClientError; end end class BoxError < Error class InvalidVersionError < BoxError def initialize(version_number) message = 'Invalid version given: ' + version_number super(message) end end class BoxExistsError < BoxError; end class ProviderNotFoundError < BoxError; end class VersionExistsError < BoxError; end class VersionStatusChangeError < BoxError; end class VersionProviderExistsError < BoxError; end end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/instrumentor.rb000066400000000000000000000003461407637405200230560ustar00rootroot00000000000000module VagrantCloud module Instrumentor autoload :Collection, "vagrant_cloud/instrumentor/collection" autoload :Core, "vagrant_cloud/instrumentor/core" autoload :Logger, "vagrant_cloud/instrumentor/logger" end end vagrant_cloud-3.0.5/lib/vagrant_cloud/instrumentor/000077500000000000000000000000001407637405200225265ustar00rootroot00000000000000vagrant_cloud-3.0.5/lib/vagrant_cloud/instrumentor/collection.rb000066400000000000000000000067411407637405200252160ustar00rootroot00000000000000module VagrantCloud module Instrumentor class Collection < Core # @return [Set] attr_reader :instrumentors # @return [Set] attr_reader :subscriptions # Create a new instance # # @param [Array] instrumentors Instrumentors to add to collection def initialize(instrumentors: []) @lock = Mutex.new @subscriptions = Set.new @instrumentors = Set.new # Add our default @instrumentors << Logger.new Array(instrumentors).each do |i| if !i.is_a?(Core) && !i.respond_to?(:instrument) raise TypeError, "Instrumentors must implement `#instrument`" end @instrumentors << i end @instrumentors.freeze end # Add a new instrumentor # # @param [Core] instrumentor New instrumentor to add # @return [self] def add(instrumentor) @lock.synchronize do if !instrumentor.is_a?(Core) && !instrumentor.respond_to?(:instrument) raise TypeError, "Instrumentors must implement `#instrument`" end @instrumentors = (instrumentors + [instrumentor]).freeze end self end # Remove instrumentor # # @param [Core] instrumentor Remove instrumentor from collection # @return [self] def remove(instrumentor) @lock.synchronize do @instrumentors = instrumentors.dup.tap{|i| i.delete(instrumentor)}.freeze end self end # Add a subscription for events # # @param [Regexp, String] event Event to match def subscribe(event, callable=nil, &block) if callable && block raise ArgumentError, "Callable argument or block expected, not both" end c = callable || block if !c.respond_to?(:call) raise TypeError, "Callable action is required for subscription" end entry = [event, c] @lock.synchronize do @subscriptions = (@subscriptions + [entry]).freeze end self end def unsubscribe(callable) @lock.synchronize do subscriptions = @subscriptions.dup subscriptions.delete_if { |entry| entry.last == callable } @subscriptions = subscriptions.freeze end self end # Call all instrumentors in collection with given parameters def instrument(name, params = {}) # Log the start time timing = {start_time: Time.now} # Run the action result = yield if block_given? # Log the completion time and calculate duration timing[:complete_time] = Time.now timing[:duration] = timing[:complete_time] - timing[:start_time] # Insert timing into params params[:timing] = timing # Call any instrumentors we know about @lock.synchronize do # Call our instrumentors first instrumentors.each do |i| i.instrument(name, params) end # Now call any matching subscriptions subscriptions.each do |event, callable| if event.is_a?(Regexp) next if !event.match(name) else next if event != name end args = [name, params] if callable.arity > -1 args = args[0, callable.arity] end callable.call(*args) end end result end end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/instrumentor/core.rb000066400000000000000000000002201407637405200237750ustar00rootroot00000000000000module VagrantCloud module Instrumentor class Core def instrument(*_) raise NotImplementedError end end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/instrumentor/logger.rb000066400000000000000000000060331407637405200243340ustar00rootroot00000000000000module VagrantCloud module Instrumentor class Logger < Core REDACTED = "REDACTED".freeze include VagrantCloud::Logger # Perform event logging # # @param [String] name Name of event "namespace.event" # @param [Hash] params Data available with event def instrument(name, params = {}) namespace, event = name.split(".", 2) if event == "error" logger.error { "#{namespace} #{event.upcase} #{params[:error]}" } return end logger.info do case namespace when "excon" # Make a copy so we can modify params = params.dup info = excon(event, params) else info = params.dup end "#{namespace} #{event.upcase} #{format_output(info)}" end logger.debug do "#{namespace} #{event.upcase} #{format_output(params)}" end end # Format output to make it look nicer # # @param [Hash] info Output information # @return [String] def format_output(info) info.map do |key, value| if value.is_a?(Enumerable) value = value.map{ |k,v| [k, v].compact.join(": ") }.join(", ") end "#{key}=#{value.inspect}" end.join(" ") end # Generate information based on excon event # # @param [String] event Event name # @param [Hash] params Event data # @return [Hash] data to be printed def excon(event, params) # Remove noisy stuff that may be present from excon params.delete(:connection) params.delete(:stack) # Remove any credential information params[:password] = REDACTED if params.key?(:password) params[:access_token] = REDACTED if params[:access_token] if params.dig(:headers, "Authorization") || params.dig(:headers, "Proxy-Authorization") params[:headers] = params[:headers].dup.tap do |h| h["Authorization"] = REDACTED if h["Authorization"] h["Proxy-Authorization"] = REDACTED if h["Proxy-Authorization"] end end if params.dig(:proxy, :password) params[:proxy] = params[:proxy].dup.tap do |proxy| proxy[:password] = REDACTED end end info = {} case event when "request", "retry" info[:method] = params[:method] info[:identifier] = params.dig(:headers, 'X-Request-Id') info[:url] = "#{params[:scheme]}://#{File.join(params[:host], params[:path])}" info[:query] = params[:query] if params[:query] info[:headers] = params[:headers] if params[:headers] when "response" info[:status] = params[:status] info[:identifier] = params.dig(:headers, 'X-Request-Id') info[:body] = params[:body] else info = params.dup end duration = (params.dig(:timing, :duration).to_f * 1000).to_i info[:duration] = "#{duration}ms" info end end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/logger.rb000066400000000000000000000037331407637405200215670ustar00rootroot00000000000000module VagrantCloud module Logger @@lock = Mutex.new # @return [Log4r::Logger] default logger def self.default @@lock.synchronize do if !@logger # Require Log4r and define the levels we'll be using require 'log4r/config' Log4r.define_levels(*Log4r::Log4rConfig::LogLevels) level = nil begin level = Log4r.const_get(ENV.fetch("VAGRANT_CLOUD_LOG", "FATAL").upcase) rescue NameError # This means that the logging constant wasn't found, # which is fine. We just keep `level` as `nil`. But # we tell the user. level = nil end # Some constants, such as "true" resolve to booleans, so the # above error checking doesn't catch it. This will check to make # sure that the log level is an integer, as Log4r requires. level = nil if !level.is_a?(Integer) # Only override the log output format if the default is set if Log4r::Outputter.stderr.formatter.is_a?(Log4r::DefaultFormatter) base_formatter = Log4r::PatternFormatter.new( pattern: "%d [%5l] %m", date_pattern: "%F %T" ) Log4r::Outputter.stderr.formatter = base_formatter end logger = Log4r::Logger.new("vagrantcloud") logger.outputters = Log4r::Outputter.stderr logger.level = level @logger = logger end end @logger end def self.included(klass) klass.class_variable_set(:@@logger, Log4r::Logger.new(klass.name.downcase)) klass.class_eval { define_method(:logger) { self.class.class_variable_get(:@@logger) } } end # @return [Log4r::Logger] logger instance for current context def logger @@lock.synchronize do if !@logger @logger = Log4r::Logger.new(self.class.name.downcase) end @logger end end end Logger.default end vagrant_cloud-3.0.5/lib/vagrant_cloud/organization.rb000066400000000000000000000026001407637405200230040ustar00rootroot00000000000000module VagrantCloud class Organization < Data::Mutable attr_reader :account attr_required :username attr_optional :boxes, :avatar_url, :profile_html, :profile_markdown attr_mutable :boxes def initialize(account:, **opts) @account = account opts[:boxes] ||= [] super(**opts) bxs = boxes.map do |b| if !b.is_a?(Box) b = Box.load(organization: self, **b) end b end clean(data: {boxes: bxs}) end # Add a new box to the organization # # @param [String] name Name of the box # @return [Box] def add_box(name) if boxes.any? { |b| b.name == name } raise Error::BoxError::BoxExistsError, "Box with name #{name} already exists" end b = Box.new(organization: self, name: name) clean(data: {boxes: boxes + [b]}) b end # Check if this instance is dirty # # @param [Boolean] deep Check nested instances # @return [Boolean] instance is dirty def dirty?(key=nil, deep: false) if key super(key) else d = super() if deep && !d d = boxes.any? { |b| b.dirty?(deep: true) } end d end end # Save the organization # # @return [self] # @note This only saves boxes within organization def save boxes.map(&:save) self end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/response.rb000066400000000000000000000003671407637405200221460ustar00rootroot00000000000000module VagrantCloud class Response < Data::Immutable autoload :CreateToken, "vagrant_cloud/response/create_token" autoload :Request2FA, "vagrant_cloud/response/request_2fa" autoload :Search, "vagrant_cloud/response/search" end end vagrant_cloud-3.0.5/lib/vagrant_cloud/response/000077500000000000000000000000001407637405200216135ustar00rootroot00000000000000vagrant_cloud-3.0.5/lib/vagrant_cloud/response/create_token.rb000066400000000000000000000002331407637405200246010ustar00rootroot00000000000000module VagrantCloud class Response class CreateToken < Response attr_required :token, :token_hash, :created_at, :description end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/response/request_2fa.rb000066400000000000000000000001701407637405200243560ustar00rootroot00000000000000module VagrantCloud class Response class Request2FA < Response attr_required :destination end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/response/search.rb000066400000000000000000000031761407637405200234140ustar00rootroot00000000000000module VagrantCloud class Response class Search < Response # @return [Account] attr_reader :account # @return [Hash] search parameters attr_reader :search_parameters attr_optional :boxes def initialize(account:, params:, **opts) if !account.is_a?(Account) raise TypeError, "Expected type `#{Account.name}` but received `#{account.class.name}`" end @account = account @search_parameters = params opts[:boxes] = reload_boxes(opts[:boxes]) super(**opts) end # @return [Integer] def page pg = @search_parameters.fetch(:page, 0).to_i pg > 0 ? pg : 1 end # @return [Search] previous page of search results def previous if page <= 1 raise ArgumentError, "Cannot request page results less than one" end account.searcher.from_response(self) do |s| s.prev_page end end # @return [Search] next page of search results def next account.searcher.from_response(self) do |s| s.next_page end end protected # Load all the box data into proper instances def reload_boxes(boxes) org_cache = {} boxes.map do |b| org_name = b[:username] if !org_cache[org_name] org_cache[org_name] = account.organization(name: org_name) end org = org_cache[org_name] box = Box.new(organization: org, **b) org.boxes = org.boxes + [box] org.clean! box end end end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/search.rb000066400000000000000000000066441407637405200215610ustar00rootroot00000000000000module VagrantCloud class Search # @return [Account] attr_reader :account # Create a new search instance # # @param [String] access_token Authentication token # @param [Account] account Account instance # @param [Client] client Client instance # @return [Search] def initialize(access_token: nil, account: nil, client: nil) args = {access_token: access_token, account: account, client: client}.compact if args.size > 1 raise ArgumentError, "Search accepts `access_token`, `account`, or `client` but received multiple (#{args.keys.join(", ")})" end if client if !client.is_a?(Client) raise TypeError, "Expecting type `#{Client.name}` but received `#{client.class.name}`" end @account = Account.new(client: client) elsif account if !account.is_a?(Account) raise TypeError, "Expecting type `#{Account.name}` but received `#{account.class.name}`" end @account = account else @account = Account.new(access_token: access_token) end @params = {} @lock = Mutex.new end # Requests a search based on the given parameters # # @param [String] query # @param [String] provider # @param [String] sort # @param [String] order # @param [String] limit # @param [String] page # @return [Response::Search] def search(query: Data::Nil, provider: Data::Nil, sort: Data::Nil, order: Data::Nil, limit: Data::Nil, page: Data::Nil) @lock.synchronize do @params = { query: query, provider: provider, sort: sort, order: order, limit: limit, page: page } execute end end # Request the next page of the search results # # @param [Response::Search] def next_page @lock.synchronize do if @params.empty? raise ArgumentError, "No active search currently cached" end page = @params[:page].to_i page = 1 if page < 1 @params[:page] = page + 1 execute end end # Request the previous page of the search results # # @param [Response::Search] def prev_page @lock.synchronize do if @params.empty? raise ArgumentError, "No active search currently cached" end page = @params[:page].to_i - 1 @params[:page] = page < 1 ? 1 : page execute end end # @return [Boolean] Search terms are stored def active? !@params.empty? end # Clear the currently cached search parameters # # @return [self] def clear! @lock.synchronize { @params.clear } self end # Seed the parameters # # @return [self] def seed(**params) @lock.synchronize { @params = params } self end # Generate a new instance seeded with search # parameters from given response # # @param [Response::Search] response Search response # @yieldparam [Search] Seeded search instance # @return [Object] result of given block def from_response(response) s = self.class.new(account: account) yield s.seed(**response.search_parameters) end protected # @return [Response::Search] def execute r = account.client.search(**@params) Response::Search.new(account: account, params: @params, **r) end end end vagrant_cloud-3.0.5/lib/vagrant_cloud/version.rb000066400000000000000000000001641407637405200217700ustar00rootroot00000000000000module VagrantCloud VERSION = Gem::Version.new(File.read(File.expand_path("../../../version.txt", __FILE__))) end vagrant_cloud-3.0.5/spec/000077500000000000000000000000001407637405200153115ustar00rootroot00000000000000vagrant_cloud-3.0.5/spec/spec_helper.rb000066400000000000000000000003211407637405200201230ustar00rootroot00000000000000require 'webmock/rspec' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end end WebMock.disable_net_connect!(allow_localhost: true) ENV.delete("VAGRANT_CLOUD_TOKEN") vagrant_cloud-3.0.5/spec/unit/000077500000000000000000000000001407637405200162705ustar00rootroot00000000000000vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/000077500000000000000000000000001407637405200211205ustar00rootroot00000000000000vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/account_spec.rb000066400000000000000000000163201407637405200241150ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Account do let(:access_token) { double("access_token") } let(:client) { double("client", access_token: access_token) } let(:username) { double("username") } let(:subject) { described_class.new(access_token: access_token) } before do allow(VagrantCloud::Client).to receive(:new).with(hash_including(access_token: access_token)).and_return(client) expect(client).to receive(:request).with(path: "authenticate"). and_return(user: {username: username}) end describe "#initialize" do it "should support a custom server" do expect(VagrantCloud::Client).to receive(:new).with(hash_including(url_base: "example.com")) described_class.new(access_token: access_token, custom_server: "example.com") end it "should support retry count" do expect(VagrantCloud::Client).to receive(:new).with(hash_including(retry_count: 1)) described_class.new(access_token: access_token, retry_count: 1) end it "should support retry interval" do expect(VagrantCloud::Client).to receive(:new).with(hash_including(retry_interval: 1)) described_class.new(access_token: access_token, retry_interval: 1) end it "should support custom instrumentor" do i = double("instrumentor") expect(VagrantCloud::Client).to receive(:new).with(hash_including(instrumentor: i)) described_class.new(access_token: access_token, instrumentor: i) end it "should set the username during initialization" do expect(subject.username).to eq(username) end end describe "#searcher" do it "should create a new Searcher instance" do expect(subject.searcher).to be_a(VagrantCloud::Search) end it "should be attached to the account" do expect(subject.searcher.account).to eq(subject) end end describe "#create_token" do let(:password) { double("password") } let(:response) { {token: token, token_hash: token_hash, created_at: created_at, description: description} } let(:token) { "TOKEN" } let(:token_hash) { "TOKEN_HASH" } let(:created_at) { "CREATED_AT" } let(:description) { "DESCRIPTION" } before { allow(client).to receive(:authentication_token_create).and_return(response) } it "should require a password" do expect { subject.create_token }.to raise_error(ArgumentError) end it "should return a create token response" do expect(subject.create_token(password: password)). to be_an_instance_of(VagrantCloud::Response::CreateToken) end it "should send username and password" do expect(client).to receive(:authentication_token_create).with(hash_including(username: username, password: password)). and_return(response) subject.create_token(password: password) end it "should send description and two factor code if provided" do expect(client).to receive(:authentication_token_create).with(hash_including(description: description, code: "CODE")). and_return(response) subject.create_token(password: password, description: description, code: "CODE") end end describe "#delete_token" do it "should send DELETE request to authenticate" do expect(client).to receive(:authentication_token_delete) subject.delete_token end it "should return itself" do allow(client).to receive(:authentication_token_delete) expect(subject.delete_token).to eq(subject) end end describe "#validate_token" do it "should call authenticate" do expect(client).to receive(:request).with(path: "authenticate") subject.validate_token end it "should return self" do allow(client).to receive(:request) expect(subject.validate_token).to eq(subject) end end describe "#request_2fa_code" do let(:delivery_method) { double("delivery_method") } let(:password) { double("password") } let(:response) { {two_factor: {obfuscated_destination: "2fa-dst"}} } before { allow(client).to receive(:authentication_request_2fa_code).and_return(response) } it "should require delivery method" do expect { subject.request_2fa_code(password: password)}. to raise_error(ArgumentError) end it "should require password" do expect { subject.request_2fa_code(delivery_method: delivery_method) }. to raise_error(ArgumentError) end it "should return a 2FA request response" do expect(subject.request_2fa_code(delivery_method: delivery_method, password: password)). to be_an_instance_of(VagrantCloud::Response::Request2FA) end it "should include 2FA request information" do expect(client).to receive(:authentication_request_2fa_code).with(hash_including(username: username, password: password, delivery_method: delivery_method)). and_return(response) subject.request_2fa_code(delivery_method: delivery_method, password: password) end it "should make a post request to the request code path" do expect(client).to receive(:authentication_request_2fa_code).with(hash_including(username: username, password: password, delivery_method: delivery_method)). and_return(response) subject.request_2fa_code(delivery_method: delivery_method, password: password) end end describe "#organization" do let(:response) { {username: r_username} } let(:r_username) { "R_USERNAME" } let(:username) { "username" } before { allow(client).to receive(:organization_get).and_return(response) } it "should request account username organization by default" do expect(client).to receive(:organization_get).with(name: username). and_return(response) subject.organization end it "should request organization with given name" do expect(client).to receive(:organization_get).with(name: r_username). and_return(response) subject.organization(name: r_username) end it "should return an organization instance" do expect(subject.organization).to be_an_instance_of(VagrantCloud::Organization) end it "should set the account into the organization instance" do expect(subject.organization.account).to eq(subject) end end describe "#setup!" do let(:response) { {user: {username: different_username}} } let(:different_username) { double("different_username") } before { allow(client).to receive(:request).with(path: "authenticate"). and_return(response) } it "should make a request to authenticate" do expect(client).to receive(:request).with(path: "authenticate").and_return(response) subject.send(:setup!) end it "should extract the username" do expect(subject.send(:setup!)).to eq(different_username) end context "when client is built without access token" do let(:c) { double("empty_client", access_token: nil) } let(:instance) { described_class.new(access_token: nil) } before do subject allow(VagrantCloud::Client).to receive(:new).with(hash_including(access_token: nil)). and_return(c) end it "should not fetch the token username" do expect(c).not_to receive(:request).with(path: "authenticate") expect(instance.send(:setup!)).to be_nil end end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/box/000077500000000000000000000000001407637405200217105ustar00rootroot00000000000000vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/box/provider_spec.rb000066400000000000000000000425111407637405200251040ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Box::Provider do let(:version) { double("version") } let(:provider_name) { "PROVIDER_NAME" } let(:box_username) { double("box_username") } let(:box_name) { double("box_name") } let(:version_version) { double("version_version") } let(:box_url) { double("box_url") } let(:subject) { described_class.new(version: version, name: provider_name) } before do allow(version).to receive(:is_a?).with(VagrantCloud::Box::Version).and_return(true) allow(version).to receive_message_chain(:box, :username).and_return(box_username) allow(version).to receive_message_chain(:box, :name).and_return(box_name) allow(version).to receive(:version).and_return(version_version) end describe "#initialize" do it "should require a version" do expect { described_class.new(name: provider_name) }.to raise_error(ArgumentError) end it "should require a name" do expect { described_class.new(version: version) }.to raise_error(ArgumentError) end end describe "#delete" do context "when provdier does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should not request deletion" do expect(version).not_to receive(:box) subject.delete end it "should return nil" do expect(subject.delete).to be_nil end end context "when provider does exist" do before do allow(subject).to receive(:exist?).and_return(true) allow(version).to receive_message_chain(:providers, :dup).and_return([]) allow(version).to receive(:clean).with(data: {providers: []}) end it "should request deletion" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_delete) subject.delete end it "should send box username" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_delete). with(hash_including(username: box_username)) subject.delete end it "should send box name" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_delete). with(hash_including(name: box_name)) subject.delete end it "should send version number" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_delete). with(hash_including(version: version_version)) subject.delete end it "should send provider_name" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_delete). with(hash_including(provider: provider_name)) subject.delete end it "should return nil" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_delete) expect(subject.delete).to be_nil end it "should remove itself from the versions provider collection" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_delete) subject.delete end end end describe "#upload" do let(:response) { {upload_path: upload_path} } let(:response_direct) { {upload_path: upload_path, callback: callback_url} } let(:upload_path) { double("upload_path") } let(:callback_url) { double("callback-url", to_str: callback) } let(:callback) { "callback_destination" } let(:callback_proc) { double("callback-proc", call: nil) } before do allow(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_upload). and_return(response) allow(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_upload_direct). and_return(response_direct) allow(version).to receive_message_chain(:box, :tag).and_return("org/box") allow(version).to receive(:version).and_return("1.0") end context "when provider does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should error" do expect { subject.upload }.to raise_error(VagrantCloud::Error::BoxError::ProviderNotFoundError) end context "when direct upload is enabled" do it "should error" do expect { subject.upload(direct: true) }. to raise_error(VagrantCloud::Error::BoxError::ProviderNotFoundError) end end end context "when provider exists" do before { allow(subject).to receive(:exist?).and_return(true) } it "should error if path and block are both provided" do expect { subject.upload(path: "/") {} }.to raise_error(ArgumentError) end it "should return the upload path" do expect(subject.upload).to eq(upload_path) end context "with path provided" do let(:path) { "PATH" } before { allow(File).to receive(:open).with(path, any_args) } context "when path does not exist" do before { allow(File).to receive(:exist?).with(path).and_return(false) } it "should error" do expect { subject.upload(path: path) }.to raise_error(Errno::ENOENT) end end context "when path does exist" do before { allow(File).to receive(:exist?).with(path).and_return(true) } it "should make request for upload" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_upload). and_return(response) subject.upload(path: path) end it "should upload the path" do expect(File).to receive(:open).with(path, any_args).and_yield(double("file")) expect(Excon).to receive(:put).with(upload_path, any_args) subject.upload(path: path) end it "should return self" do expect(subject.upload(path: path)).to eq(subject) end end end context "with block provided" do it "should make request for upload" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_upload). and_return(response) subject.upload { |url| } end it "should yield the upload path" do subject.upload do |url| expect(url).to eq(upload_path) end end it "should return result of block" do expect(subject.upload { |u| :test }).to eq(:test) end end end context "with direct option set" do before do allow(subject).to receive(:exist?).and_return(true) allow(Excon).to receive(:put) allow(Excon).to receive(:post) end it "should error if path and block are both provided" do expect { subject.upload(path: "/", direct: true) {} }.to raise_error(ArgumentError) end it "should return a DirectUpload" do expect(subject.upload(direct: true)).to be_a(VagrantCloud::Box::Provider::DirectUpload) end it "should include an upload_url and callback_url in result" do result = subject.upload(direct: true) expect(result.upload_url).to eq(upload_path) expect(result.callback_url).to eq(callback_url) expect(result.callback).to be_a(Proc) end context "with path provided" do let(:path) { "PATH" } before do allow(File).to receive(:open).with(path, any_args) allow(version).to receive_message_chain(:box, :organization, :account, :client, :request). with(method: :put, path: callback) end context "when path does not exist" do before { allow(File).to receive(:exist?).with(path).and_return(false) } it "should error" do expect { subject.upload(path: path, direct: true) }.to raise_error(Errno::ENOENT) end end context "when path does exist" do before { allow(File).to receive(:exist?).with(path).and_return(true) } it "should make request for direct upload" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_upload_direct). and_return(response_direct) subject.upload(path: path, direct: true) end it "should upload the path" do expect(File).to receive(:open).with(path, any_args).and_yield(double("file")) expect(version).to receive_message_chain(:box, :organization, :account, :client, :request). with(method: :put, path: callback) subject.upload(path: path, direct: true) end it "should request the callback" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :request). with(method: :put, path: callback) subject.upload(path: path, direct: true) end it "should return self" do expect(subject.upload(path: path, direct: true)).to eq(subject) end end end context "with block provided" do before do allow(version).to receive_message_chain(:box, :organization, :account, :client, :request). with(method: :put, path: callback) end it "should make request for upload" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_upload_direct). and_return(response_direct) subject.upload(direct: true) { |du| } end it "should yield the upload path" do subject.upload(direct: true) do |du| expect(du).to eq(upload_path) end end it "should request the callback" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :request). with(method: :put, path: callback) subject.upload(direct: true) {|_|} end it "should return result of block" do expect(subject.upload(direct: true) { |du| :test }).to eq(:test) end end end end describe "#exist?" do let(:subject) { described_class.new(version: version, name: provider_name, created_at: created_at) } context "with created_at attribute set" do let(:created_at) { Time.now.to_s } it "should be true" do expect(subject.exist?).to be_truthy end end context "with created_at attribute unset" do let(:created_at) { nil } it "should be false" do expect(subject.exist?).to be_falsey end end end describe "#dirty?" do context "when provider does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should be true" do expect(subject.dirty?).to be_truthy end end context "when provider does exist" do before { allow(subject).to receive(:exist?).and_return(true) } it "should be false" do expect(subject.dirty?).to be_falsey end context "with modified attribute" do before { subject.url = "test" } it "should be true" do expect(subject.dirty?).to be_truthy end end end end describe "#save" do before { allow(subject).to receive(:save_provider) } context "when provider is not dirty" do before { allow(subject).to receive(:dirty?).and_return(false) } it "should not save provider" do expect(subject).not_to receive(:save_provider) subject.save end it "should return self" do expect(subject.save).to eq(subject) end end context "when provider is dirty" do before { allow(subject).to receive(:dirty?).and_return(true) } it "should save the provider" do expect(subject).to receive(:save_provider) subject.save end it "should return self" do expect(subject.save).to eq(subject) end end end describe "#save_provider" do let(:checksum) { double("checksum") } let(:checksum_type) { double("checksum_type") } before do allow(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). and_return({}) allow(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). and_return({}) end context "when provider exists" do before { allow(subject).to receive(:exist?).and_return(true) } it "should request an update" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). and_return({}) subject.send(:save_provider) end it "should return self" do expect(subject.send(:save_provider)).to eq(subject) end it "should include box organization" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). with(hash_including(username: box_username)).and_return({}) subject.send(:save_provider) end it "should include box name" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). with(hash_including(name: box_name)).and_return({}) subject.send(:save_provider) end it "should include version" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). with(hash_including(version: version_version)).and_return({}) subject.send(:save_provider) end it "should include provider" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). with(hash_including(provider: provider_name)).and_return({}) subject.send(:save_provider) end it "should include checksum" do subject.checksum = checksum expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). with(hash_including(checksum: checksum)).and_return({}) subject.send(:save_provider) end it "should include checksum_type" do subject.checksum_type = checksum_type expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). with(hash_including(checksum_type: checksum_type)).and_return({}) subject.send(:save_provider) end it "should include URL" do subject.url = box_url expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_update). with(hash_including(url: box_url)).and_return({}) subject.send(:save_provider) end end context "when provider does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should request a creation" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). and_return({}) subject.send(:save_provider) end it "should return self" do expect(subject.send(:save_provider)).to eq(subject) end it "should include box organization" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). with(hash_including(username: box_username)).and_return({}) subject.send(:save_provider) end it "should include box name" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). with(hash_including(name: box_name)).and_return({}) subject.send(:save_provider) end it "should include version" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). with(hash_including(version: version_version)).and_return({}) subject.send(:save_provider) end it "should include provider" do expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). with(hash_including(provider: provider_name)).and_return({}) subject.send(:save_provider) end it "should include checksum" do subject.checksum = checksum expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). with(hash_including(checksum: checksum)).and_return({}) subject.send(:save_provider) end it "should include checksum_type" do subject.checksum_type = checksum_type expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). with(hash_including(checksum_type: checksum_type)).and_return({}) subject.send(:save_provider) end it "should include URL" do subject.url = box_url expect(version).to receive_message_chain(:box, :organization, :account, :client, :box_version_provider_create). with(hash_including(url: box_url)).and_return({}) subject.send(:save_provider) end end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/box/version_spec.rb000066400000000000000000000263141407637405200247420ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Box::Version do let(:box) { double("box", username: box_username, name: box_name, tag: "#{box_username}/#{box_name}") } let(:box_username) { double("box_username") } let(:box_name) { double("box_name") } let(:version) { "1.0.0" } let(:subject) { described_class.new(box: box, version: version) } before { allow(box).to receive(:is_a?).with(VagrantCloud::Box).and_return(true) } describe "#initialize" do it "should require a box" do expect { described_class.new }.to raise_error(ArgumentError) end it "should require box argument be box type" do expect { described_class.new(box: nil) }.to raise_error(TypeError) end it "should load providers" do instance = described_class.new(box: box, version: version, providers: [{name: "test"}]) expect(instance.providers).not_to be_empty expect(instance.providers.first).to be_a(VagrantCloud::Box::Provider) end end describe "#delete" do before do allow(box).to receive(:versions).and_return([]) allow(box).to receive_message_chain(:organization, :account, :client, :box_version_delete) end it "should not delete if version does not exist" do expect(box).not_to receive(:organization) subject.delete end it "should return nil" do expect(subject.delete).to be_nil end context "when version exists" do before do allow(subject).to receive(:exist?).and_return(true) allow(box).to receive(:clean) end it "should make a version deletion request" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_delete) subject.delete end it "should include box username and name" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_delete). with(hash_including(username: box_username, name: box_name)) subject.delete end it "should include the version" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_delete). with(hash_including(version: version)) subject.delete end it "should delete the version from the box versions" do versions = double("versions") expect(versions).to receive(:dup).and_return(versions) expect(box).to receive(:versions).and_return(versions) expect(versions).to receive(:delete).with(subject).and_return(versions) expect(box).to receive(:clean).with(data: {versions: versions}) subject.delete end end end describe "#release" do context "when version is released" do before { allow(subject).to receive(:released?).and_return(true) } it "should error" do expect { subject.release }.to raise_error(VagrantCloud::Error::BoxError::VersionStatusChangeError) end end context "when version has not been saved" do before { allow(subject).to receive(:exist?).and_return(false) } it "should error" do expect { subject.release }.to raise_error(VagrantCloud::Error::BoxError::VersionStatusChangeError) end end context "when version is saved and not released" do before do allow(subject).to receive(:exist?).and_return(true) allow(subject).to receive(:released?).and_return(false) end it "should send request to release version" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_release). and_return({}) subject.release end it "should include box username, box name, and version" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_release). with(hash_including(username: box_username, name: box_name, version: version)).and_return({}) subject.release end it "should update status with value provided in result" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_release). and_return({status: "active"}) subject.release expect(subject.status).to eq("active") end it "should return self" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_release). and_return({}) expect(subject.release).to eq(subject) end end end describe "#revoke" do context "when version is not released" do before { allow(subject).to receive(:released?).and_return(false) } it "should error" do expect { subject.revoke }.to raise_error(VagrantCloud::Error::BoxError::VersionStatusChangeError) end end context "when version is released" do before { allow(subject).to receive(:released?).and_return(true) } it "should send request to revoke release" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_revoke). and_return({}) subject.revoke end it "should include the box username, box name, and version" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_revoke). with(hash_including(username: box_username, name: box_name, version: version)).and_return({}) subject.revoke end it "should update status with value provided in result" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_revoke). and_return({status: "inactive"}) subject.revoke expect(subject.status).to eq("inactive") end it "should return self" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_revoke). and_return({}) expect(subject.revoke).to eq(subject) end end end describe "#add_provider" do it "should create a new provider" do expect(subject.add_provider("test")).to be_a(VagrantCloud::Box::Provider) end it "should add provider to providers collection" do pv = subject.add_provider("test") expect(subject.providers).to include(pv) end it "should raise error when provider exists" do subject.add_provider("test") expect { subject.add_provider("test") }. to raise_error(VagrantCloud::Error::BoxError::VersionProviderExistsError) end end describe "#dirty?" do context "when version does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should be true" do expect(subject.dirty?).to be_truthy end end context "when version does exist" do before { allow(subject).to receive(:exist?).and_return(true) } it "should be false" do expect(subject.dirty?).to be_falsey end context "with modified attribute" do before { subject.description = "test" } it "should be true" do expect(subject.dirty?).to be_truthy end end context "with deep check" do it "should be false" do expect(subject.dirty?(deep: true)).to be_falsey end context "with modified attribute" do before { subject.description = "test" } it "should be true" do expect(subject.dirty?(deep: true)).to be_truthy end end context "with dirty provider in providers collection" do before { subject.add_provider("test") } it "should be true" do expect(subject.dirty?(deep: true)).to be_truthy end end end end end describe "#exist?" do let(:subject) { described_class.new(box: box, version: version, created_at: created_at) } context "with created_at attribute set" do let(:created_at) { Time.now.to_s } it "should be true" do expect(subject.exist?).to be_truthy end end context "with created_at attribute unset" do let(:created_at) { nil } it "should be false" do expect(subject.exist?).to be_falsey end end end describe "#save" do before do allow(subject).to receive(:save_version) allow(subject).to receive(:save_provdiers) end it "should return self" do expect(subject.save).to eq(subject) end context "when version is dirty" do before do allow(subject).to receive(:dirty?).and_return(true) allow(subject).to receive(:dirty?).with(deep: true).and_return(false) end it "should save the version" do expect(subject).to receive(:save_version) subject.save end end context "when version is clean" do before { allow(subject).to receive(:dirty?).and_return(false) } it "should not save the version" do expect(subject).not_to receive(:save_version) subject.save end end context "when dirty provider in providers collection" do before { subject.add_provider("test") } it "should save the providers" do expect(subject).to receive(:save_providers) subject.save end end end describe "#save_version" do context "when version exists" do before { allow(subject).to receive(:exist?).and_return(true) } it "should request a version update" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_update). and_return({}) subject.send(:save_version) end it "should include the box username, box name, version, and description" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_update). with(hash_including(username: box_username, name: box_name, version: version, description: subject.description)). and_return({}) subject.send(:save_version) end it "should return self" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_update). and_return({}) expect(subject.send(:save_version)).to eq(subject) end end context "when version does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should request a version create" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_create). and_return({}) subject.send(:save_version) end it "should include the box username, box name, version, and description" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_create). with(hash_including(username: box_username, name: box_name, version: version, description: subject.description)). and_return({}) subject.send(:save_version) end it "should return self" do expect(box).to receive_message_chain(:organization, :account, :client, :box_version_create). and_return({}) expect(subject.send(:save_version)).to eq(subject) end end end describe "#save_providers" do it "should return self" do expect(subject.send(:save_providers)).to eq(subject) end it "should save the providers" do pv = subject.add_provider("test") expect(pv).to receive(:save) subject.send(:save_providers) end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/box_spec.rb000066400000000000000000000241351407637405200232540ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Box do let(:organization) { VagrantCloud::Organization.new(account: account, username: organization_name) } let(:organization_name) { "ORG_NAME" } let(:account) { double("account") } let(:name) { "BOX_NAME" } let(:subject) { described_class.new(organization: organization, name: name) } before do allow(account).to receive_message_chain(:client, :box_get).and_return(versions: []) end describe "#initialize" do it "should require a name" do expect { described_class.new(organization: organization) }. to raise_error(ArgumentError) end it "should require an organization" do expect { described_class.new(name: name) }. to raise_error(ArgumentError) end it "should create new instance with organization and name" do expect { described_class.new(name: name, organization: organization) }. not_to raise_error end end describe "#short_description" do it "should be mutable" do expect(subject.short_description).to be_nil subject.short_description = "test" expect(subject.short_description).to eq("test") end end describe "#description" do it "should be mutable" do expect(subject.description).to be_nil subject.description = "test" expect(subject.description).to eq("test") end end describe "#private" do it "should be mutable" do expect(subject.private).to be_nil subject.private = true expect(subject.private).to be_truthy end end describe "#delete" do it "should return nil" do expect(subject.delete).to be_nil end it "should not request to delete box that does not exist" do expect(organization).not_to receive(:account) subject.delete end context "when box exists" do before { allow(subject).to receive(:exist?).and_return(true) } it "should request box deletion" do expect(account).to receive_message_chain(:client, :box_delete) subject.delete end end end describe "#add_version" do it "should create a new version" do expect(subject.add_version("1.0.0")).to be_a(VagrantCloud::Box::Version) end it "should add new version to the versions collection" do v = subject.add_version("1.0.0") expect(subject.versions).to include(v) end it "should error when adding an existing version" do subject.add_version("1.0.0") expect { subject.add_version("1.0.0") }. to raise_error(VagrantCloud::Error::BoxError::VersionExistsError) end end describe "#dirty?" do it "should be true when box does not exist" do expect(subject.exist?).to be_falsey expect(subject.dirty?).to be_truthy end context "when box exists" do before { allow(subject).to receive(:exist?).and_return(true) } it "should be false" do expect(subject.dirty?).to be_falsey end context "when attribute is modified" do before { subject.description = "test" } it "should be true" do expect(subject.dirty?).to be_truthy end it "should be true on attribute name check" do expect(subject.dirty?(:description)).to be_truthy end end context "deep check" do it "should be false" do expect(subject.dirty?(deep: true)).to be_falsey end context "when a version is added" do before { subject.add_version("1.0.0") } it "should be true" do expect(subject.dirty?(deep: true)).to be_truthy end end end end end describe "#exist?" do it "should be false when created_at is unset" do expect(subject.created_at).to be_falsey expect(subject.exist?).to be_falsey end context "when created_at is set" do before { subject.clean(data: {created_at: Time.now.to_s}) } it "should be true" do expect(subject.exist?).to be_truthy end end end describe "#versions_on_demand" do context "when box exists" do before { allow(subject).to receive(:exist?).and_return(true) } it "should load versions when called" do expect(account).to receive_message_chain(:client, :box_get).and_return(versions: []) subject.versions_on_demand end it "should not load versions after initial load" do expect(subject.dirty?(:versions)).to be_falsey expect(account).to receive_message_chain(:client, :box_get).and_return(versions: []) subject.versions_on_demand expect(account).not_to receive(:client) subject.versions_on_demand end end context "when box does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should not load versions when called" do expect(account).not_to receive(:client) subject.versions_on_demand end it "should not load versions after initial load" do expect(subject.dirty?(:versions)).to be_falsey expect(account).not_to receive(:client) subject.versions_on_demand expect(account).not_to receive(:client) subject.versions_on_demand end end end describe "#save" do before do allow(subject).to receive(:save_versions) allow(subject).to receive(:save_box) end it "should return self" do expect(subject.save).to eq(subject) end context "when box does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should save the box" do expect(subject).to receive(:save_box).ordered expect(subject).to receive(:save_versions).ordered subject.save end end context "when box includes unsaved versions" do before { subject.add_version("1.0.0") } it "should save the versions" do expect(subject).to receive(:save_versions) subject.save end end context "when box exists" do before { allow(subject).to receive(:exist?).and_return(true) } it "should not save anything" do expect(subject).not_to receive(:save_box) expect(subject).not_to receive(:save_versions) subject.save end context "when box includes unsaved versions" do before { subject.add_version("1.0.0") } it "should save the versions" do expect(subject).to receive(:save_versions) subject.save end end context "when box attribute is updated" do before { subject.description = "test" } it "should save the box" do expect(subject).to receive(:save_box) subject.save end end end end describe "#save_box" do context "when box exists" do before { allow(subject).to receive(:exist?).and_return(true) } it "should return self" do expect(account).to receive_message_chain(:client, :box_update).and_return({}) expect(subject.send(:save_box)).to eq(subject) end it "should request a box update" do expect(account).to receive_message_chain(:client, :box_update).and_return({}) subject.send(:save_box) end it "should include the organization name" do expect(account).to receive_message_chain(:client, :box_update). with(hash_including(username: organization_name)).and_return({}) subject.send(:save_box) end it "should include the name" do expect(account).to receive_message_chain(:client, :box_update). with(hash_including(name: name)).and_return({}) subject.send(:save_box) end it "should include the short description" do expect(account).to receive_message_chain(:client, :box_update). with(hash_including(short_description: subject.short_description)).and_return({}) subject.send(:save_box) end it "should include the description" do expect(account).to receive_message_chain(:client, :box_update). with(hash_including(description: subject.description)).and_return({}) subject.send(:save_box) end it "should include the box privacy" do expect(account).to receive_message_chain(:client, :box_update). with(hash_including(is_private: subject.private)).and_return({}) subject.send(:save_box) end end context "when box does not exist" do before { allow(subject).to receive(:exist?).and_return(false) } it "should request a box create" do expect(account).to receive_message_chain(:client, :box_create).and_return({}) subject.send(:save_box) end it "should include the organization name" do expect(account).to receive_message_chain(:client, :box_create). with(hash_including(username: organization_name)).and_return({}) subject.send(:save_box) end it "should include the name" do expect(account).to receive_message_chain(:client, :box_create). with(hash_including(name: name)).and_return({}) subject.send(:save_box) end it "should include the short description" do expect(account).to receive_message_chain(:client, :box_create). with(hash_including(short_description: subject.short_description)).and_return({}) subject.send(:save_box) end it "should include the description" do expect(account).to receive_message_chain(:client, :box_create). with(hash_including(description: subject.description)).and_return({}) subject.send(:save_box) end it "should include the box privacy" do expect(account).to receive_message_chain(:client, :box_create). with(hash_including(is_private: subject.private)).and_return({}) subject.send(:save_box) end end end describe "#save_versions" do it "should return self" do expect(subject.send(:save_versions)).to eq(subject) end it "should call save on any versions" do subject.add_version("1.0.0") expect(account).to receive_message_chain(:client, :box_version_create). and_return({}) subject.send(:save_versions) end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/client_spec.rb000066400000000000000000001077771407637405200237600ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Client do let(:connection) { double("connection", request: nil) } describe "#intialize" do context "with no arguments" do it "should have #url_base set" do expect(subject.url_base).not_to be_nil end it "should have #retry_count set" do expect(subject.retry_count).not_to be_nil end it "should have #retry_interval set" do expect(subject.retry_interval).not_to be_nil end it "should have #instrumentor set" do expect(subject.instrumentor).not_to be_nil end it "should not have #access_token set" do expect(subject.access_token).to be_nil end end context "with arguments" do it "should set #url_base" do subject = described_class.new(url_base: "http://example.com") expect(subject.url_base).to eq("http://example.com") end it "should set #retry_count" do subject = described_class.new(retry_count: 1) expect(subject.retry_count).to eq(1) end it "should set #retry_interval" do subject = described_class.new(retry_interval: 1) expect(subject.retry_interval).to eq(1) end it "should set #instrumentor" do i = double("instrumentor") subject = described_class.new(instrumentor: i) expect(subject.instrumentor).to eq(i) end it "should set #access_token" do subject = described_class.new(access_token: "token") expect(subject.access_token).to eq("token") end end end describe "#parse_json" do it "should return result with symbolized keys" do expect(subject.send(:parse_json, {"test" => :val}.to_json)).to eq({test: "val"}) end end describe "#clean_parameters" do it "should remove Data::Nil values from Array" do val = [1, 2, VagrantCloud::Data::Nil, 3] result = subject.send(:clean_parameters, val) expect(result).to eq([1, 2, 3]) end it "should remove Data::Nil values from nested Arrays" do val = [1, 2, VagrantCloud::Data::Nil, [1, 2, VagrantCloud::Data::Nil], 3] result = subject.send(:clean_parameters, val) expect(result).to eq([1, 2, [1, 2], 3]) end it "should remove Data::Nil values from Hash" do val = {a: 1, b: 2, c: VagrantCloud::Data::Nil, d: 3} result = subject.send(:clean_parameters, val) expect(result).to eq({a: 1, b: 2, d: 3}) end it "should remove Data::Nil values from nested Hashes" do val = {a: 1, b: 2, c: VagrantCloud::Data::Nil, d: {a: 1, b: VagrantCloud::Data::Nil}, e: 3} result = subject.send(:clean_parameters, val) expect(result).to eq({a: 1, b: 2, d: {a: 1}, e: 3}) end it "should remove Data::Nil values from nested Arrays and Hashes" do val = {a: 1, b: [1, 2, VagrantCloud::Data::Nil, {a: 1, b: VagrantCloud::Data::Nil}], c: 2} result = subject.send(:clean_parameters, val) expect(result).to eq({a: 1, b: [1, 2, {a: 1}], c: 2}) end end describe "#with_connection" do it "should provide the connection to the block" do subject.with_connection do |c| expect(c).to be_a(Excon::Connection) end end it "should gate access to the connection" do fiber = Fiber.new do subject.with_connection { Fiber.yield } end fiber.resume expect { subject.with_connection(wait: false) {} }. to raise_error(VagrantCloud::Error::ClientError::ConnectionLockedError) fiber.resume expect { subject.with_connection {} }.not_to raise_error end end describe "#request" do let(:response) { double("response", body: body, status: status) } let(:body) { "" } let(:status) { 200 } before do allow(subject).to receive(:with_connection). and_yield(connection) allow(connection).to receive(:request). and_return(response) end it "should require path to be set" do expect { subject.request }.to raise_error(ArgumentError) end it "should default to GET method" do expect(connection).to receive(:request). with(hash_including(method: :get)). and_return(response) subject.request(path: "/") end it "should use method provided" do expect(connection).to receive(:request). with(hash_including(method: :post)). and_return(response) subject.request(path: "/", method: :post) end it "should set a request ID header" do expect(connection).to receive(:request) do |args| expect(args.dig(:headers, "X-Request-Id")).not_to be_nil response end subject.request(path: "/") end context "when response body is valid json" do let(:body) { {result: true}.to_json } it "should parse the return the JSON value" do expect(subject.request(path: "/")).to eq({result: true}) end end context "with parameters" do [:get, :head, :delete].each do |request_method| it "should use query parameters for #{request_method.to_s.upcase} request method" do expect(connection).to receive(:request).with(hash_including(query: anything)). and_return(response) subject.request(path: "/", method: request_method, params: {testing: true}) end end it "should use JSON body parameters for other request methods" do expect(connection).to receive(:request).with(hash_including(body: anything)). and_return(response) subject.request(path: "/", method: :post, params: {testing: true}) end it "should pass parameter hash through in request" do expect(connection).to receive(:request).with(hash_including(query: {testing: true})). and_return(response) subject.request(path: "/", params: {testing: true}) end it "should remove parameters that were not explicitly set" do expect(connection).to receive(:request).with(hash_including(query: {testing: true})). and_return(response) subject.request(path: "/", params: {testing: true, invalid: VagrantCloud::Data::Nil}) end end context "idempotent information" do [:get, :head].each do |request_method| it "should set idempotent options for #{request_method.to_s.upcase} request method" do expect(connection).to receive(:request). with(hash_including(idempotent: anything, retry_limit: anything, retry_interval: anything)). and_return(response) subject.request(path: "/", method: request_method) end end it "should not set idempotent options for other request methods" do expect(connection).to receive(:request) do |args| expect(args.keys).not_to include(:idempotent) expect(args.keys).not_to include(:retry_limit) expect(args.keys).not_to include(:retry_interval) response end subject.request(path: "/", method: :post) end end context "with errors" do context "with request errors" do let(:response) { double("response", status: 403, body: '{"errors": ["forbidden request"]}') } before { expect(connection).to receive(:request).and_raise(Excon::Error::Forbidden.new("forbidden", nil, response)) } it "should raise a wrapped error" do expect { subject.request(path: "/") }.to raise_error(VagrantCloud::Error::ClientError::RequestError) end it "should set the error message from the content" do err = nil subject.request(path: "/") rescue => err expect(err.error_arr).to eq(["forbidden request"]) end it "should set the error status code" do err = nil subject.request(path: "/") rescue => err expect(err.error_code).to eq(403) end end end end describe "#clone" do it "should create a new clone" do expect(subject.clone).to be_a(described_class) end it "should be a new instance" do expect(subject.clone).not_to be(subject) end it "should clone custom settings" do subject = described_class.new(url_base: "http://example.com") expect(subject.clone.url_base).to eq("http://example.com") end it "should override the access_token when provided" do subject = described_class.new(access_token: "token") expect(subject.clone(access_token: "new-token").access_token).to eq("new-token") end end describe "#authentication_token_create" do let(:username) { double("username") } let(:password) { double("password") } let(:description) { double("description") } let(:code) { double("code") } it "should require a username" do expect { subject.authentication_token_create(password: password) }. to raise_error(ArgumentError) end it "should require a password" do expect { subject.authentication_token_create(username: username) }. to raise_error(ArgumentError) end it "should send remote request and include username and password" do expect(subject).to receive(:request) do |args| expect(args[:path]).to include("authenticate") expect(args[:method]).to eq(:post) expect(args.dig(:params, :user, :login)).to eq(username) expect(args.dig(:params, :user, :password)).to eq(password) end subject.authentication_token_create(username: username, password: password) end it "should include description and code if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :token, :description)).to eq(description) expect(args.dig(:params, :two_factor, :code)).to eq(code) end subject.authentication_token_create(username: username, password: password, description: description, code: code) end end describe "#authentication_token_delete" do it "should send delete request" do expect(subject).to receive(:request).with(hash_including(method: :delete, path: "authenticate")) subject.authentication_token_delete end end describe "#authentication_request_2fa_code" do let(:username) { double("username") } let(:password) { double("password") } let(:delivery_method) { method("delivery_method") } let(:args) { {username: username, password: password, delivery_method: delivery_method} } it "should require a username" do args.delete(:username) expect { subject.authentication_request_2fa_code(**args) }. to raise_error(ArgumentError) end it "should require a password" do args.delete(:password) expect { subject.authentication_request_2fa_code(**args) }. to raise_error(ArgumentError) end it "should require a delivery method" do args.delete(:delivery_method) expect { subject.authentication_request_2fa_code(**args) }. to raise_error(ArgumentError) end it "should include username, password, and delivery method in request" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :two_factor, :delivery_method)).to eq(delivery_method) expect(args.dig(:params, :user, :login)).to eq(username) expect(args.dig(:params, :user, :password)).to eq(password) end subject.authentication_request_2fa_code(**args) end it "should post to the two factor request path" do expect(subject).to receive(:request).with(hash_including(method: :post, path: "two-factor/request-code")) subject.authentication_request_2fa_code(**args) end end describe "#search" do let(:query) { double("query") } let(:provider) { double("provider") } let(:sort) { double("sort") } let(:order) { "asc" } let(:limit) { 53 } let(:page) { 101 } let(:args) { {query: query, provider: provider, sort: sort, order: order, limit: limit, page: page} } it "should sent request for search" do expect(subject).to receive(:request).with(hash_including(method: :get, path: "search")) subject.search(**args) end it "should include given values within request parameters" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :q)).to eq(query) expect(args.dig(:params, :provider)).to eq(provider) expect(args.dig(:params, :sort)).to eq(sort) expect(args.dig(:params, :order)).to eq(order) expect(args.dig(:params, :limit)).to eq(limit) expect(args.dig(:params, :page)).to eq(page) end subject.search(**args) end end describe "#box_get" do before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_get(name: "mybox") }.to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_get(username: "myname") }.to raise_error(ArgumentError) end it "should send the remote request with username and name" do expect(subject).to receive(:request) do |args| expect(args[:path]).to include("myname") expect(args[:path]).to include("mybox") end subject.box_get(username: "myname", name: "mybox") end end describe "#box_create" do let(:name) { double("name") } let(:username) { double("username") } let(:description) { double("description") } let(:short_description) { double("short_description") } let(:is_private) { double("is_private") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_create(name: name) }.to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_create(username: username) }.to raise_error(ArgumentError) end it "should only require username and name" do expect(subject).to receive(:request) subject.box_create(username: username, name: name) end it "should include description" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :description)).to eq(description) end subject.box_create(username: username, name: name, description: description) end it "should include short_description" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :short_description)).to eq(short_description) end subject.box_create(username: username, name: name, short_description: short_description) end it "should include is_private" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :is_private)).to eq(is_private) end subject.box_create(username: username, name: name, is_private: is_private) end end describe "#box_update" do let(:username) { double("username") } let(:name) { double("name") } let(:short_description) { double("short_description") } let(:description) { double("description") } let(:is_private) { double("is_private") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_update(name: name) }.to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_update(username: username) }.to raise_error(ArgumentError) end it "should only require username and name" do expect(subject).to receive(:request) subject.box_update(username: username, name: name) end it "should include description" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :description)).to eq(description) end subject.box_update(username: username, name: name, description: description) end it "should include short_description" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :short_description)).to eq(short_description) end subject.box_update(username: username, name: name, short_description: short_description) end it "should include is_private" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :is_private)).to eq(is_private) end subject.box_update(username: username, name: name, is_private: is_private) end end describe "#box_delete" do let(:username) { double("username") } let(:name) { double("name") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_delete(name: name) }.to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_delete(username: username) }.to raise_error(ArgumentError) end it "should send deletion request" do expect(subject).to receive(:request) subject.box_delete(username: username, name: name) end end describe "#box_version_get" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_get(name: name, version: version) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_get(username: username, version: version) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_get(username: username, name: name) }. to raise_error(ArgumentError) end it "should request the box version" do expect(subject).to receive(:request) subject.box_version_get(username: username, name: name, version: version) end end describe "#box_version_create" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } let(:description) { double("description") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_create(name: name, version: version) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_create(username: username, version: version) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_create(username: username, name: name) }. to raise_error(ArgumentError) end it "should request the version creation" do expect(subject).to receive(:request) subject.box_version_create(username: username, name: name, version: version) end it "should make request using POST method" do expect(subject).to receive(:request).with(hash_including(method: :post)) subject.box_version_create(username: username, name: name, version: version) end it "should include description if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :version, :description)).to eq(description) end subject.box_version_create(username: username, name: name, version: version, description: description) end it "should include the version in the parameters" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :version, :version)).to eq(version) end subject.box_version_create(username: username, name: name, version: version) end end describe "#box_version_update" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } let(:description) { double("description") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_update(name: name, version: version) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_update(username: username, version: version) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_update(username: username, name: name) }. to raise_error(ArgumentError) end it "should request the version update" do expect(subject).to receive(:request) subject.box_version_update(username: username, name: name, version: version) end it "should make request using PUT method" do expect(subject).to receive(:request).with(hash_including(method: :put)) subject.box_version_update(username: username, name: name, version: version) end it "should include description if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :version, :description)).to eq(description) end subject.box_version_update(username: username, name: name, version: version, description: description) end it "should include the version in the parameters" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :version, :version)).to eq(version) end subject.box_version_update(username: username, name: name, version: version) end end describe "#box_version_delete" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_delete(name: name, version: version) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_delete(username: username, version: version) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_delete(username: username, name: name) }. to raise_error(ArgumentError) end it "should request the version delete" do expect(subject).to receive(:request) subject.box_version_delete(username: username, name: name, version: version) end it "should make request using DELETE method" do expect(subject).to receive(:request).with(hash_including(method: :delete)) subject.box_version_delete(username: username, name: name, version: version) end end describe "#box_version_release" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_release(name: name, version: version) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_release(username: username, version: version) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_release(username: username, name: name) }. to raise_error(ArgumentError) end it "should request the version release" do expect(subject).to receive(:request) do |args| expect(args[:path]).to include("release") end subject.box_version_release(username: username, name: name, version: version) end it "should make request using PUT method" do expect(subject).to receive(:request).with(hash_including(method: :put)) subject.box_version_release(username: username, name: name, version: version) end end describe "#box_version_revoke" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_revoke(name: name, version: version) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_revoke(username: username, version: version) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_revoke(username: username, name: name) }. to raise_error(ArgumentError) end it "should request the version revoke" do expect(subject).to receive(:request) do |args| expect(args[:path]).to include("revoke") end subject.box_version_revoke(username: username, name: name, version: version) end it "should make request using PUT method" do expect(subject).to receive(:request).with(hash_including(method: :put)) subject.box_version_revoke(username: username, name: name, version: version) end end describe "#box_version_provider_get" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } let(:provider) { double("provider") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_provider_get(name: name, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_provider_get(username: username, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_provider_get(username: username, name: name, provider: provider) }. to raise_error(ArgumentError) end it "should require provider is provided" do expect { subject.box_version_provider_get(username: username, name: name, version: version) }. to raise_error(ArgumentError) end it "should request the box version provider" do expect(subject).to receive(:request) subject.box_version_provider_get(username: username, name: name, version: version, provider: provider) end end describe "#box_version_provider_create" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } let(:provider) { double("provider") } let(:url) { double("url") } let(:checksum) { double("checksum") } let(:checksum_type) { double("checksum_type") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_provider_create(name: name, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_provider_create(username: username, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_provider_create(username: username, name: name, provider: provider) }. to raise_error(ArgumentError) end it "should require provider is provided" do expect { subject.box_version_provider_create(username: username, name: name, version: version) }. to raise_error(ArgumentError) end it "should create the box version provider" do expect(subject).to receive(:request) subject.box_version_provider_create(username: username, name: name, version: version, provider: provider) end it "should create the box version provider with POST method" do expect(subject).to receive(:request).with(hash_including(method: :post)) subject.box_version_provider_create(username: username, name: name, version: version, provider: provider) end it "should include url if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :provider, :url)).to eq(url) end subject.box_version_provider_create(username: username, name: name, version: version, provider: provider, url: url) end it "should include checksum if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :provider, :checksum)).to eq(checksum) end subject.box_version_provider_create(username: username, name: name, version: version, provider: provider, checksum: checksum) end it "should include checksum_type if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :provider, :checksum_type)).to eq(checksum_type) end subject.box_version_provider_create(username: username, name: name, version: version, provider: provider, checksum_type: checksum_type) end end describe "#box_version_provider_update" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } let(:provider) { double("provider") } let(:url) { double("url") } let(:checksum) { double("checksum") } let(:checksum_type) { double("checksum_type") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_provider_update(name: name, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_provider_update(username: username, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_provider_update(username: username, name: name, provider: provider) }. to raise_error(ArgumentError) end it "should require provider is provided" do expect { subject.box_version_provider_update(username: username, name: name, version: version) }. to raise_error(ArgumentError) end it "should update the box version provider" do expect(subject).to receive(:request) subject.box_version_provider_update(username: username, name: name, version: version, provider: provider) end it "should update the box version provider with PUT method" do expect(subject).to receive(:request).with(hash_including(method: :put)) subject.box_version_provider_update(username: username, name: name, version: version, provider: provider) end it "should include url if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :provider, :url)).to eq(url) end subject.box_version_provider_update(username: username, name: name, version: version, provider: provider, url: url) end it "should include checksum if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :provider, :checksum)).to eq(checksum) end subject.box_version_provider_update(username: username, name: name, version: version, provider: provider, checksum: checksum) end it "should include checksum_type if provided" do expect(subject).to receive(:request) do |args| expect(args.dig(:params, :provider, :checksum_type)).to eq(checksum_type) end subject.box_version_provider_update(username: username, name: name, version: version, provider: provider, checksum_type: checksum_type) end end describe "#box_version_provider_delete" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } let(:provider) { double("provider") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_provider_delete(name: name, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_provider_delete(username: username, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_provider_delete(username: username, name: name, provider: provider) }. to raise_error(ArgumentError) end it "should require provider is provided" do expect { subject.box_version_provider_delete(username: username, name: name, version: version) }. to raise_error(ArgumentError) end it "should delete the box version provider" do expect(subject).to receive(:request) subject.box_version_provider_delete(username: username, name: name, version: version, provider: provider) end it "should delete the box version provider with DELETE method" do expect(subject).to receive(:request).with(hash_including(method: :delete)) subject.box_version_provider_delete(username: username, name: name, version: version, provider: provider) end end describe "#box_version_provider_upload" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } let(:provider) { double("provider") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_provider_upload(name: name, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_provider_upload(username: username, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_provider_upload(username: username, name: name, provider: provider) }. to raise_error(ArgumentError) end it "should require provider is provided" do expect { subject.box_version_provider_upload(username: username, name: name, version: version) }. to raise_error(ArgumentError) end it "should send upload request for the box version provider" do expect(subject).to receive(:request) subject.box_version_provider_upload(username: username, name: name, version: version, provider: provider) end it "should request the box version provider upload with GET method" do expect(subject).to receive(:request).with(hash_including(method: :get)) subject.box_version_provider_upload(username: username, name: name, version: version, provider: provider) end it "should request the upload path" do expect(subject).to receive(:request) do |args| expect(args[:path]).to include("upload") end subject.box_version_provider_upload(username: username, name: name, version: version, provider: provider) end end describe "#box_version_provider_upload_direct" do let(:username) { double("username") } let(:name) { double("name") } let(:version) { double("version") } let(:provider) { double("provider") } before { allow(subject).to receive(:request) } it "should require username is provided" do expect { subject.box_version_provider_upload_direct(name: name, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require name is provided" do expect { subject.box_version_provider_upload_direct(username: username, version: version, provider: provider) }. to raise_error(ArgumentError) end it "should require version is provided" do expect { subject.box_version_provider_upload_direct(username: username, name: name, provider: provider) }. to raise_error(ArgumentError) end it "should require provider is provided" do expect { subject.box_version_provider_upload_direct(username: username, name: name, version: version) }. to raise_error(ArgumentError) end it "should send upload request for the box version provider" do expect(subject).to receive(:request) subject.box_version_provider_upload_direct(username: username, name: name, version: version, provider: provider) end it "should request the box version provider upload with GET method" do expect(subject).to receive(:request).with(hash_including(method: :get)) subject.box_version_provider_upload_direct(username: username, name: name, version: version, provider: provider) end it "should request the upload path" do expect(subject).to receive(:request) do |args| expect(args[:path]).to include("upload") end subject.box_version_provider_upload_direct(username: username, name: name, version: version, provider: provider) end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/data_spec.rb000066400000000000000000000255451407637405200234030ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Data::NilClass do let(:subject) { described_class.instance } it "should be a singleton" do expect(described_class.ancestors).to include(Singleton) end it "should be nil?" do expect(subject.nil?).to be_truthy end it "should == nil" do expect(subject == nil).to be_truthy end it "should === nil" do expect(subject === nil).to be_truthy end it "should equal? nil" do expect(subject.equal?(nil)).to be_truthy end it "should convert to 0" do expect(subject.to_i).to eq(0) end it "should convert to 0.0" do expect(subject.to_f).to eq(0.0) end it "should convert to empty array" do expect(subject.to_a).to eq([]) end it "should convert to empty hash" do expect(subject.to_h).to eq({}) end it "should convert to empty string" do expect(subject.to_s).to eq("") end it "should & to false" do expect(subject & :value).to be_falsey end it "should | to false" do expect(subject | :value).to be_falsey end it "should ^ to false" do expect(subject ^ :value).to be_falsey end it "should inspect to nil string" do expect(subject.inspect).to eq("nil") end end describe VagrantCloud::Data do describe "#initialize" do it "should accept no arguments when creating" do expect { described_class.new }.not_to raise_error end it "should accept arguments when creating" do expect { described_class.new(value: true) }.not_to raise_error end end describe "#[]" do it "should provide access to arguments" do instance = described_class.new(value: 1) expect(instance[:value]).to eq(1) end it "should return custom nil when argument is not defined" do instance = described_class.new(value: 1) expect(instance[:other_value]).to eq(VagrantCloud::Data::Nil) end end end describe VagrantCloud::Data::Immutable do context "with required attributes" do let(:described_class) do @c ||= Class.new(VagrantCloud::Data::Immutable) do attr_required :password end end it "should error if required argument is not provided" do expect { described_class.new }.to raise_error(ArgumentError) end it "should not error if required argument is provided" do expect { described_class.new(password: "value") }.not_to raise_error end it "should add an accessor to retrieve the value" do instance = described_class.new(password: "value") expect(instance.password).to eq("value") end it "should retrieve value via [] using a symbol" do instance = described_class.new(password: "value") expect(instance[:password]).to eq("value") end it "should retrieve value via [] using a string" do instance = described_class.new(password: "value") expect(instance["password"]).to eq("value") end it "should not allow value to be modified" do instance = described_class.new(password: "value") expect { instance.password.replace("new-value") }.to raise_error(FrozenError) end end context "with optional attributes" do let(:described_class) do @c ||= Class.new(VagrantCloud::Data::Immutable) do attr_optional :username end end it "should not error if argument is not provided" do expect { described_class.new(username: "value") }.not_to raise_error end it "should add an accessor to retrieve the value" do instance = described_class.new(username: "value") expect(instance.username).to eq("value") end it "should retrieve value via [] using a symbol" do instance = described_class.new(username: "value") expect(instance[:username]).to eq("value") end it "should retrieve value via [] using a string" do instance = described_class.new(username: "value") expect(instance["username"]).to eq("value") end it "should not allow value to be modified" do instance = described_class.new(username: "value") expect { instance.username.replace("new-value") }.to raise_error(FrozenError) end it "should return custom nil via accessor when unset" do instance = described_class.new expect(instance.username).to eq(VagrantCloud::Data::Nil) end it "should return custom nil via []" do instance = described_class.new expect(instance[:username]).to eq(VagrantCloud::Data::Nil) end end context "with optional and required attributes" do let(:described_class) do @c ||= Class.new(VagrantCloud::Data::Immutable) do attr_required :password attr_optional :username end end it "should error if invalid argument is provided" do expect { described_class.new(password: "pass", other: true) }.to raise_error(ArgumentError) end it "should error if no arguments are provided" do expect { described_class.new }.to raise_error(ArgumentError) end it "should error if only optional argument is provided" do expect { described_class.new(username: "user") }.to raise_error(ArgumentError) end it "should not error if required argument is provided" do expect { described_class.new(password: "pass") }.not_to raise_error end it "should not error if both required and optional arguments are provided" do expect { described_class.new(password: "pass", username: "user") }.not_to raise_error end end end describe VagrantCloud::Data::Mutable do let(:username) { "U" } let(:password) { "P" } context ".load" do let(:described_class) do @c ||= Class.new(VagrantCloud::Data::Mutable) do attr_reader :key attr_required :password attr_optional :username def initialize(key:, **opts) super(**opts) @key = key end end end it "should create a new instance" do data = {password: "pass", key: "key"} instance = described_class.load(data) expect(instance).to be_a(described_class) end it "should set the information provided in the data hash" do data = {password: "pass", key: "key"} instance = described_class.load(data) expect(instance.password).to eq("pass") expect(instance.key).to eq("key") end it "should ignore extra information in the hash" do data = {password: "pass", key: "key", invalid: true} instance = described_class.load(data) expect(instance.password).to eq("pass") expect(instance.key).to eq("key") end end describe "#freeze" do it "should return self" do expect(subject.freeze).to eq(subject) end it "should not freeze" do expect(subject.freeze).not_to be_frozen end end context "with no mutables defined" do let(:described_class) do @c ||= Class.new(VagrantCloud::Data::Mutable) do attr_required :password attr_optional :username end end let(:subject) { described_class.new(username: username, password: password) } it "should not allow setting optional value" do expect { subject.username = "new-value" }.to raise_error(NoMethodError) end it "should not allow setting required value" do expect { subject.password = "new-value" }.to raise_error(NoMethodError) end end context "with mutables defined" do let(:described_class) do @c ||= Class.new(VagrantCloud::Data::Mutable) do attr_required :password attr_optional :username attr_mutable :username, :password end end let(:subject) { described_class.new(username: username, password: password) } it "should allow setting optional value" do expect { subject.username = "new-value" }.not_to raise_error end it "should allow setting required value" do expect { subject.password = "new-value" }.not_to raise_error end it "should return updated optional value" do expect(subject.username).to eq(username) subject.username = "new-value" expect(subject.username).to eq("new-value") end it "should return updated required value" do expect(subject.password).to eq(password) subject.password = "new-value" expect(subject.password).to eq("new-value") end context "#dirty?" do it "should mark the instance as dirty when value is updated" do subject.username = "new-value" expect(subject.dirty?).to be_truthy end it "should mark the field as dirty with value is updated" do subject.username = "new-value" expect(subject.dirty?(:username)).to be_truthy end it "should not mark other fields dirty when not updated" do subject.username = "new-value" expect(subject.dirty?(:username)).to be_truthy expect(subject.dirty?(:password)).to be_falsey end end context "#clean!" do it "should make a dirty instance clean" do subject.username = "new-value" expect(subject.dirty?).to be_truthy subject.clean! expect(subject.dirty?).to be_falsey end it "should make clean values non-modifyable" do subject.username = "new-value" subject.clean! expect { subject.username.replace("testing") }.to raise_error(FrozenError) end end context "#clean" do it "should update values with provided data" do expect(subject.username).to eq(username) expect(subject.password).to eq(password) subject.clean(data: {username: "new-user", password: "new-pass"}) expect(subject.username).to eq("new-user") expect(subject.password).to eq("new-pass") end it "should update instance so it is non-dirty" do expect(subject.dirty?).to be_falsey subject.clean(data: {username: "new-user", password: "new-pass"}) expect(subject.dirty?).to be_falsey end it "should ignore fields of data provided" do expect(subject.username).to eq(username) expect(subject.password).to eq(password) subject.clean(data: {username: "new-user", password: "new-pass"}, ignores: :password) expect(subject.username).to eq("new-user") expect(subject.password).to eq(password) end it "should only update requested fields of data provided" do expect(subject.username).to eq(username) expect(subject.password).to eq(password) subject.clean(data: {username: "new-user", password: "new-pass"}, only: :password) expect(subject.username).to eq(username) expect(subject.password).to eq("new-pass") end it "should make clean values non-modifyable" do subject.clean(data: {username: "new-user", password: "new-pass"}, only: :password) subject.clean! expect { subject.username.replace("testing") }.to raise_error(FrozenError) end it "should error if data provided is not a hash" do expect { subject.clean(data: nil) }.to raise_error(TypeError) end end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/instrumentor/000077500000000000000000000000001407637405200236715ustar00rootroot00000000000000vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/instrumentor/collection_spec.rb000066400000000000000000000152701407637405200273700ustar00rootroot00000000000000require "spec_helper" require "vagrant_cloud" describe VagrantCloud::Instrumentor::Collection do describe "#initialize" do it "should be a Core type" do expect(subject).to be_a(VagrantCloud::Instrumentor::Core) end it "should have a logger instrumentor by default" do expect(subject.instrumentors.first).to be_a(VagrantCloud::Instrumentor::Logger) end it "should accept additional instrumentors" do addition = VagrantCloud::Instrumentor::Core.new instance = described_class.new(instrumentors: [addition]) expect(instance.instrumentors).to include(addition) end it "should accept single additional instrumentor" do addition = VagrantCloud::Instrumentor::Core.new instance = described_class.new(instrumentors: addition) expect(instance.instrumentors).to include(addition) end end describe "#add" do it "should add a new instrumentor" do addition = VagrantCloud::Instrumentor::Core.new subject.add(addition) expect(subject.instrumentors).to include(addition) end it "should return self" do addition = VagrantCloud::Instrumentor::Core.new expect(subject.add(addition)).to eq(subject) end it "should only add an instrumentor instance once" do addition = VagrantCloud::Instrumentor::Core.new subject.add(addition).add(addition) expect(subject.instrumentors.count{|i| i == addition}).to eq(1) end it "should error if instance is not an instrumentor" do expect { subject.add("string") }.to raise_error(TypeError) end it "should freeze instrumentors after adding" do addition = VagrantCloud::Instrumentor::Core.new subject.add(addition).add(addition) expect(subject.instrumentors).to be_frozen end end describe "#remove" do it "should remove an instrumentor" do addition = VagrantCloud::Instrumentor::Core.new subject.add(addition).add(addition) expect(subject.instrumentors).to include(addition) subject.remove(addition) expect(subject.instrumentors).not_to include(addition) end it "should return self" do expect(subject.remove(nil)).to eq(subject) end it "should freeze instrumentors after removing" do addition = VagrantCloud::Instrumentor::Core.new subject.add(addition).add(addition) subject.remove(addition) expect(subject.instrumentors).to be_frozen end end describe "#subscribe" do it "should add a new subscription entry" do expect(subject.subscriptions.size).to eq(0) subject.subscribe("event", proc{}) expect(subject.subscriptions.size).to eq(1) end it "should freeze subscriptions after subscribing" do subject.subscribe("event", proc{}) expect(subject.subscriptions).to be_frozen end it "should return self" do expect(subject.subscribe("event", proc{})).to eq(subject) end it "should error if callable is not provided" do expect { subject.subscribe("event") }.to raise_error(TypeError) end it "should error if non-callable is provided" do expect { subject.subscribe("event", :thing) }.to raise_error(TypeError) end it "should error if callable and block are provided" do expect { subject.subscribe("event", proc{}){} }.to raise_error(ArgumentError) end it "should add with callable argument" do expect { subject.subscribe("event", proc{}) }.not_to raise_error end it "should add with block" do expect { subject.subscribe("event"){} }.not_to raise_error end end describe "#unsubscribe" do it "should remove entry using callable instance" do callable = proc{} subject.subscribe("event", callable) expect(subject.subscriptions.count).to eq(1) subject.unsubscribe(callable) expect(subject.subscriptions).to be_empty end it "should freeze subscriptions after remove" do callable = proc{} subject.subscribe("event", callable) expect(subject.subscriptions.count).to eq(1) subject.unsubscribe(callable) expect(subject.subscriptions).to be_frozen end it "should return self" do callable = proc{} subject.subscribe("event", callable) expect(subject.subscriptions.count).to eq(1) expect(subject.unsubscribe(callable)).to eq(subject) end end describe "#instrument" do let(:logger) { double("logger") } let(:event) { "event" } let(:params) { {} } before do allow(VagrantCloud::Instrumentor::Logger). to receive(:new).and_return(logger) allow(logger).to receive(:instrument) end it "should call the logger instrumentor" do expect(logger).to receive(:instrument).with(event, params) subject.instrument(event, params) end it "should yield when a block is provided" do run = false subject.instrument(event, params) do run = true end expect(run).to be_truthy end it "should return the result of the block when provided" do expect(subject.instrument(event, params){ :result }).to eq(:result) end it "should add timing information to params" do subject.instrument(event, params) expect(params).to have_key(:timing) end it "should provide duration timing" do expect(Time).to receive(:now).and_return(Time.now - 5) expect(Time).to receive(:now).and_call_original subject.instrument(event, params) expect(params.dig(:timing, :duration)).to be_within(0.01).of(5) end context "when a subscription is added with exact name match" do it "should call the subscription" do run = false callable = proc { run = true } subject.subscribe(event, callable) subject.instrument(event, params) expect(run).to be_truthy end end context "when a subscription is added with regex name match" do it "should call the subscription" do run = false callable = proc { run = true } subject.subscribe(/ev/, callable) subject.instrument(event, params) expect(run).to be_truthy end end context "when a subscription is added with exact name not matching" do it "should not call the subscription" do run = false callable = proc { run = true } subject.subscribe("other", callable) subject.instrument(event, params) expect(run).to be_falsey end end context "when a subscription is added with regex name not matching" do it "should not call the subscription" do run = false callable = proc { run = true } subject.subscribe(/ot/, callable) subject.instrument(event, params) expect(run).to be_falsey end end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/instrumentor/core_spec.rb000066400000000000000000000003761407637405200261660ustar00rootroot00000000000000require "spec_helper" require "vagrant_cloud" describe VagrantCloud::Instrumentor::Core do context "#instrument" do it "should raise NotImplementedError" do expect { subject.instrument }.to raise_error(NotImplementedError) end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/instrumentor/logger_spec.rb000066400000000000000000000126631407637405200265170ustar00rootroot00000000000000require "spec_helper" require "vagrant_cloud" describe VagrantCloud::Instrumentor::Logger do let(:logger) { double("logger") } before { allow(subject).to receive(:logger).and_return(logger) } it "should be a subclass of Core" do expect(described_class.ancestors).to include(VagrantCloud::Instrumentor::Core) end describe "#instrument" do before do allow(logger).to receive(:debug).and_yield allow(logger).to receive(:info).and_yield allow(logger).to receive(:error).and_yield end context "errors" do let(:name) { "test.error" } it "should output error message when event type is error" do expect(logger).to receive(:error) subject.instrument(name) end it "should include namespace and event type in output" do expect(logger).to receive(:error) do |&b| expect(b.call).to match(/test ERROR/) end subject.instrument(name) end it "should include optional error message if provided" do expect(logger).to receive(:error) do |&b| expect(b.call).to match(/test ERROR custom message/) end subject.instrument(name, error: "custom message") end it "should not proceed any farther" do expect(logger).not_to receive(:info) expect(logger).not_to receive(:debug) subject.instrument(name) end end context "non-excon namespaced events" do let(:name) { "test.action" } let(:params) { {} } after { subject.instrument(name, params) } it "should include namespace in info output" do expect(logger).to receive(:info) do |&b| expect(b.call).to include("test") end end it "should include namespace in debug output" do expect(logger).to receive(:debug) do |&b| expect(b.call).to include("test") end end it "should include the event name in the info output upcased" do expect(logger).to receive(:info) do |&b| expect(b.call).to include("ACTION") end end it "should include the event name in the debug output upcased" do expect(logger).to receive(:debug) do |&b| expect(b.call).to include("ACTION") end end it "should format output to the logger" do # debug format expect(subject).to receive(:format_output).with(params) # info format expect(subject).to receive(:format_output).with(anything) end context "when params include content" do let(:params) { {value: true, testing: "a-value"} } it "should include params in the info output" do expect(logger).to receive(:info) do |&b| result = b.call expect(result).to include("testing=\"a-value\"") expect(result).to include("value=true") end end it "should include params in the debug output" do expect(logger).to receive(:debug) do |&b| result = b.call expect(result).to include("testing=\"a-value\"") expect(result).to include("value=true") end end end end context "excon namespaced events" do let(:name) { "excon.action" } let(:params) { {data: nil} } after { subject.instrument(name, params) } it "should call #excon to filter parameters" do expect(subject).to receive(:excon).with(anything, params).and_return({}) end it "should send event type to #excon" do expect(subject).to receive(:excon).with("action", anything).and_return({}) end it "should output all parameters via debug" do allow(subject).to receive(:format_output) expect(subject).to receive(:format_output).with(params) end end end describe "excon" do let(:action) { double("action") } let(:params) { {} } let(:redacted) { described_class.const_get(:REDACTED) } it "should return hash with duration" do expect(subject.excon(action, params)).to have_key(:duration) end context "when parameters include password" do let(:params) { {password: "my-password"} } it "should redact the password value" do subject.excon(action, params) expect(params[:password]).to eq(redacted) end end context "when parameters include proxy password" do let(:params) { {proxy: {password: "my-password"}} } it "should redact the password value" do subject.excon(action, params) expect(params.dig(:proxy, :password)).to eq(redacted) end end context "when parameters include access token" do let(:params) { {access_token: "my-token"} } it "should redact the access token value" do subject.excon(action, params) expect(params[:access_token]).to eq(redacted) end end context "when parameters include authorization header" do let(:params) { {headers: {"Authorization" => "value"}} } it "should redact the authorization header value" do subject.excon(action, params) expect(params.dig(:headers, "Authorization")).to eq(redacted) end end context "when parameters include proxy authorization header" do let(:params) { {headers: {"Proxy-Authorization" => "value"}} } it "should redact the authorization header value" do subject.excon(action, params) expect(params.dig(:headers, "Proxy-Authorization")).to eq(redacted) end end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/organization_spec.rb000066400000000000000000000043651407637405200251730ustar00rootroot00000000000000require "spec_helper" require "vagrant_cloud" describe VagrantCloud::Organization do let(:account) { double("account") } let(:username) { "USERNAME" } let(:subject) { described_class.new(account: account, username: username) } describe "#initialize" do it "should error if account is not provided" do expect { described_class.new(username: username) }.to raise_error(ArgumentError) end it "should error if username is not provided" do expect { described_class.new(account: account) }.to raise_error(ArgumentError) end end describe "#add_box" do it "should create a new box" do expect(subject.add_box("test")).to be_a(VagrantCloud::Box) end it "should add box to the collection" do expect(subject.boxes).to be_empty subject.add_box("test") expect(subject.boxes).not_to be_empty end it "should error if box name already exists" do subject.add_box("test") expect { subject.add_box("test") }. to raise_error(VagrantCloud::Error::BoxError::BoxExistsError) end end describe "#dirty?" do it "should return false by default" do expect(subject.dirty?).to be_falsey end it "should check dirtiness based on attribute" do expect(subject.dirty?(:username)).to be_falsey end context "deep check" do it "should return false by default" do expect(subject.dirty?(deep: true)).to be_falsey end context "with box collection of one clean box" do before do b = subject.add_box("test") b.clean(data: {created_at: Time.now.to_s}) subject.clean! end it "should return false" do expect(subject.dirty?(deep: true)).to be_falsey end context "with a dirty box in collection" do before { subject.add_box("test2") } it "should return true" do expect(subject.dirty?(deep: true)).to be_truthy end end end end end describe "#save" do it "should return self" do expect(subject.save).to eq(subject) end context "with boxes" do it "should save boxes" do b = subject.add_box("test") expect(b).to receive(:save) subject.save end end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/response/000077500000000000000000000000001407637405200227565ustar00rootroot00000000000000vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/response/create_token_spec.rb000066400000000000000000000035641407637405200267700ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Response::CreateToken do let(:token) { "token" } let(:token_hash) { "token_hash" } let(:created_at) { Time.now.to_s } let(:description) { "description" } let(:args) { {token: token, token_hash: token_hash, created_at: created_at, description: description } } let(:subject) { described_class.new(**args) } describe "#initialize" do it "should create a new instance" do expect { subject }.not_to raise_error end it "should require token" do args.delete(:token) expect { subject }.to raise_error(ArgumentError) end it "should require token_hash" do args.delete(:token_hash) expect { subject }.to raise_error(ArgumentError) end it "should require created_at" do args.delete(:created_at) expect { subject }.to raise_error(ArgumentError) end it "should require description" do args.delete(:description) expect { subject }.to raise_error(ArgumentError) end end describe "#token" do it "should return a value" do expect(subject.token).to eq(token) end it "should freeze the value" do expect(subject.token).to be_frozen end end describe "#token_hash" do it "should return a value" do expect(subject.token_hash).to eq(token_hash) end it "should freeze the value" do expect(subject.token_hash).to be_frozen end end describe "#created_at" do it "should return a value" do expect(subject.created_at).to eq(created_at) end it "should freeze the value" do expect(subject.created_at).to be_frozen end end describe "#description" do it "should return a value" do expect(subject.description).to eq(description) end it "should freeze the value" do expect(subject.description).to be_frozen end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/response/request_2fa_spec.rb000066400000000000000000000012471407637405200265410ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Response::Request2FA do let(:subject) { described_class.new(destination: destination) } let(:destination) { "value" } describe "#initialize" do it "should create a new instance" do expect { subject }.not_to raise_error end it "should error if destination is not provided" do expect { described_class.new }.to raise_error(ArgumentError) end end describe "#destination" do it "should return a value" do expect(subject.destination).to eq(destination) end it "should freeze the value" do expect(subject.destination).to be_frozen end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/response/search_spec.rb000066400000000000000000000057211407637405200255670ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Response::Search do let(:client) { double("client", access_token: nil) } let(:account) { VagrantCloud::Account.new(client: client) } let(:params) { {} } let(:result) { {boxes: boxes} } let(:boxes) { [] } let(:searcher) { VagrantCloud::Search.new(account: account) } let(:subject) { described_class.new(account: account, params: params, **result) } before do allow(client).to receive(:is_a?).with(VagrantCloud::Client).and_return(true) allow(VagrantCloud::Search).to receive(:new).and_return(searcher) end describe "#initialize" do it "should error when account is not provided" do expect { described_class.new(params: params, **result) }. to raise_error(ArgumentError) end it "should error when params are not provided" do expect { described_class.new(account: account, **result) }. to raise_error(ArgumentError) end it "should error when account is not the right type" do expect { described_class.new(account: "value", params: params, **result) }. to raise_error(TypeError) end it "should load boxes" do expect_any_instance_of(described_class).to receive(:reload_boxes) subject end end describe "#page" do it "defaults to page 1" do expect(subject.page).to eq(1) end context "when page is set in params" do let(:params) { {page: 5} } it "should return the page value" do expect(subject.page).to eq(5) end end end describe "#previous" do it "should raise error when no previous page is available" do expect { subject.previous }.to raise_error(ArgumentError) end context "with previous pages available" do let(:params) { {page: 5} } let(:response) { double("response") } before { allow(searcher).to receive(:execute).and_return(response) } it "should request previous page through a searcher" do expect(searcher).to receive(:prev_page) subject.previous end end end describe "#next" do let(:response) { double("response") } before { allow(searcher).to receive(:execute).and_return(response) } it "should request next page through a searcher" do expect(searcher).to receive(:next_page) subject.next end end describe "#boxes" do let(:boxes) { [{tag: "org/box", name: "box", username: "org"}] } let(:organization) { VagrantCloud::Organization. new(account: account, username: "org") } before { allow(account).to receive(:organization). with(name: "org").and_return(organization) } it "should have a boxes count of 1" do expect(subject.boxes.count).to eq(1) end it "should contain a Box instance" do expect(subject.boxes.first).to be_a(VagrantCloud::Box) end it "should population the organization" do expect(subject.boxes.first.organization.boxes.first).to eq(subject.boxes.first) end end end vagrant_cloud-3.0.5/spec/unit/vagrant_cloud/search_spec.rb000066400000000000000000000143201407637405200237240ustar00rootroot00000000000000require 'spec_helper' require 'vagrant_cloud' describe VagrantCloud::Search do let(:account) { double("account", client: client_account) } let(:client) { double("client", access_token: nil) } let(:client_account) { double("client_account") } let(:client_with_token) { double("client_with_token", access_token: access_token) } let(:access_token) { double("access_token") } let(:response) { {boxes: boxes} } let(:boxes) { [] } before do allow(VagrantCloud::Client).to receive(:new). with(hash_including(access_token: nil)).and_return(client) allow(VagrantCloud::Client).to receive(:new). with(hash_including(access_token: access_token)).and_return(client_with_token) allow(account).to receive(:is_a?).with(VagrantCloud::Account).and_return(true) allow(client).to receive(:is_a?).with(VagrantCloud::Client).and_return(true) allow(client_with_token).to receive(:request).and_return({}) allow(client).to receive(:search).and_return(response) end describe "#initialize" do it "should create a new instance without an access token" do expect(subject.account.client).to eq(client) end it "should create a new instance with a custom access token" do instance = described_class.new(access_token: access_token) expect(instance.account.client).to eq(client_with_token) end it "should create a new instance with an account" do instance = described_class.new(account: account) expect(instance.account.client).to eq(client_account) end it "should create a new instance with a client" do instance = described_class.new(client: client) expect(instance.account.client).to eq(client) end it "should error when more than one argument is provided" do expect { described_class.new(client: client, access_token: access_token) }. to raise_error(ArgumentError) end it "should error when client is not a client instance" do expect { described_class.new(client: "value") }. to raise_error(TypeError) end it "should error when account is not an account instance" do expect { described_class.new(account: "value") }. to raise_error(TypeError) end end describe "#search" do it "should execute the search request" do expect(subject).to receive(:execute) subject.search end it "should set instance to active after a search" do expect(subject.active?).to be_falsey subject.search expect(subject.active?).to be_truthy end it "should return a search response" do expect(subject.search).to be_a(VagrantCloud::Response::Search) end end describe "#next_page" do it "should error without active search" do expect { subject.next_page }.to raise_error(ArgumentError) end context "with active search" do before { subject.search } it "should not produce an error" do expect { subject.next_page }.not_to raise_error end it "should increment the page requested" do expect(subject).to receive(:execute).and_call_original expect(client).to receive(:search).with(hash_including(page: 2)). and_return(response) subject.next_page end it "should return a search response" do expect(subject.next_page).to be_a(VagrantCloud::Response::Search) end it "should persist the page number" do subject.next_page expect(client).to receive(:search).with(hash_including(page: 3)). and_return(response) subject.next_page end end end describe "#prev_page" do it "should error without active search" do expect { subject.prev_page }.to raise_error(ArgumentError) end context "with active search" do before { subject.search } it "should not produce an error" do expect { subject.prev_page }.not_to raise_error end it "should maintain page 1 when decrementing page is less than 1" do subject.prev_page expect(client).to receive(:search).with(hash_including(page: 1)). and_return(response) subject.prev_page end it "should return a search response" do expect(subject.prev_page).to be_a(VagrantCloud::Response::Search) end context "with active search on page 10" do before { subject.search(page: 10) } it "should request results from page 9" do expect(client).to receive(:search).with(hash_including(page: 9)). and_return(response) subject.prev_page end it "should persist page value and request page 8" do subject.prev_page expect(client).to receive(:search).with(hash_including(page: 8)). and_return(response) subject.prev_page end end end end describe "#active?" do context "without active search" do it "should be false" do expect(subject.active?).to be_falsey end end context "with active search" do before { subject.search } it "should be true" do expect(subject.active?).to be_truthy end end end describe "#clear!" do it "should return self" do expect(subject.clear!).to eq(subject) end it "should not be active after clearing" do subject.clear! expect(subject.active?).to be_falsey end context "with active search" do before { subject.search } it "should not be active after clearing" do expect(subject.active?).to be_truthy subject.clear! expect(subject.active?).to be_falsey end end end describe "#seed" do it "should return self" do expect(subject.seed(query: "test")).to eq(subject) end it "should make search instance active" do expect(subject.active?).to be_falsey subject.seed(query: "test") expect(subject.active?).to be_truthy end it "should not execute a request" do expect(subject).not_to receive(:execute) subject.seed(query: "test") end end describe "#from_response" do it "should yield a new search instance" do subject.from_response(subject.search) do |s| expect(s).not_to eq(subject) expect(s).to be_a(described_class) end end end end vagrant_cloud-3.0.5/tasks/000077500000000000000000000000001407637405200155045ustar00rootroot00000000000000vagrant_cloud-3.0.5/tasks/rspec.rake000066400000000000000000000002031407637405200174570ustar00rootroot00000000000000require 'rspec/core/rake_task' desc 'Run all specs' RSpec::Core::RakeTask.new(:spec) do |t| t.pattern = 'spec/**/*_spec.rb' end vagrant_cloud-3.0.5/vagrant_cloud.gemspec000066400000000000000000000015071407637405200205570ustar00rootroot00000000000000require_relative "lib/vagrant_cloud/version" Gem::Specification.new do |s| s.name = 'vagrant_cloud' s.version = VagrantCloud::VERSION.to_s s.summary = 'Vagrant Cloud API Library' s.description = 'Ruby library for the HashiCorp Vagrant Cloud API' s.authors = ['HashiCorp', 'Cargo Media'] s.email = 'vagrant@hashicorp.com' s.files = Dir['LICENSE*', 'README*', '{lib}/**/*'].reject { |f| f.end_with?('~') } s.homepage = 'https://github.com/hashicorp/vagrant_cloud' s.license = 'MIT' s.add_runtime_dependency 'excon', '~> 0.73' s.add_runtime_dependency 'log4r', '~> 1.1.10' s.add_runtime_dependency 'rexml', '~> 3.2.5' s.add_development_dependency 'rake', '~> 12.3' s.add_development_dependency 'rspec', '~> 3.0' s.add_development_dependency 'webmock', '~> 3.0' end vagrant_cloud-3.0.5/version.txt000066400000000000000000000000061407637405200166010ustar00rootroot000000000000003.0.5