fragrant 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +6 -0
  3. data/bin/fragrant +14 -0
  4. data/lib/fragrant/address_manager.rb +80 -0
  5. data/lib/fragrant/vagrantfile_generator.rb +64 -0
  6. data/lib/fragrant.rb +323 -0
  7. data/spec/fragrant/environments_spec.rb +18 -0
  8. data/spec/fragrant/vagrantfile_generator_spec.rb +40 -0
  9. data/spec/fragrant/vms_spec.rb +26 -0
  10. data/spec/spec_helper.rb +15 -0
  11. data/vendor/grape/LICENSE +20 -0
  12. data/vendor/grape/lib/grape/api.rb +420 -0
  13. data/vendor/grape/lib/grape/cookies.rb +41 -0
  14. data/vendor/grape/lib/grape/endpoint.rb +377 -0
  15. data/vendor/grape/lib/grape/entity.rb +378 -0
  16. data/vendor/grape/lib/grape/exceptions/base.rb +17 -0
  17. data/vendor/grape/lib/grape/exceptions/validation_error.rb +10 -0
  18. data/vendor/grape/lib/grape/middleware/auth/basic.rb +30 -0
  19. data/vendor/grape/lib/grape/middleware/auth/digest.rb +30 -0
  20. data/vendor/grape/lib/grape/middleware/auth/oauth2.rb +72 -0
  21. data/vendor/grape/lib/grape/middleware/base.rb +154 -0
  22. data/vendor/grape/lib/grape/middleware/error.rb +87 -0
  23. data/vendor/grape/lib/grape/middleware/filter.rb +17 -0
  24. data/vendor/grape/lib/grape/middleware/formatter.rb +81 -0
  25. data/vendor/grape/lib/grape/middleware/prefixer.rb +21 -0
  26. data/vendor/grape/lib/grape/middleware/versioner/header.rb +59 -0
  27. data/vendor/grape/lib/grape/middleware/versioner/param.rb +44 -0
  28. data/vendor/grape/lib/grape/middleware/versioner/path.rb +42 -0
  29. data/vendor/grape/lib/grape/middleware/versioner.rb +29 -0
  30. data/vendor/grape/lib/grape/route.rb +23 -0
  31. data/vendor/grape/lib/grape/util/deep_merge.rb +23 -0
  32. data/vendor/grape/lib/grape/util/hash_stack.rb +100 -0
  33. data/vendor/grape/lib/grape/validations/coerce.rb +61 -0
  34. data/vendor/grape/lib/grape/validations/presence.rb +11 -0
  35. data/vendor/grape/lib/grape/validations/regexp.rb +13 -0
  36. data/vendor/grape/lib/grape/validations.rb +192 -0
  37. data/vendor/grape/lib/grape/version.rb +3 -0
  38. data/vendor/grape/lib/grape.rb +44 -0
  39. metadata +216 -0
@@ -0,0 +1,87 @@
1
+ require 'grape/middleware/base'
2
+ require 'multi_json'
3
+
4
+ module Grape
5
+ module Middleware
6
+ class Error < Base
7
+ include Formats
8
+
9
+ def default_options
10
+ {
11
+ :default_status => 403, # default status returned on error
12
+ :default_message => "",
13
+ :format => :txt,
14
+ :formatters => {},
15
+ :rescue_all => false, # true to rescue all exceptions
16
+ :rescue_options => { :backtrace => false }, # true to display backtrace
17
+ :rescue_handlers => {}, # rescue handler blocks
18
+ :rescued_errors => []
19
+ }
20
+ end
21
+
22
+ def encode_json(message, backtrace)
23
+ result = message.is_a?(Hash) ? message : { :error => message }
24
+ if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
25
+ result = result.merge({ :backtrace => backtrace })
26
+ end
27
+ MultiJson.dump(result)
28
+ end
29
+
30
+ def encode_txt(message, backtrace)
31
+ result = message.is_a?(Hash) ? MultiJson.dump(message) : message
32
+ if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
33
+ result += "\r\n "
34
+ result += backtrace.join("\r\n ")
35
+ end
36
+ result
37
+ end
38
+
39
+ def call!(env)
40
+ @env = env
41
+
42
+ begin
43
+ error_response(catch(:error){
44
+ return @app.call(@env)
45
+ })
46
+ rescue Exception => e
47
+ is_rescuable = rescuable?(e.class)
48
+ if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
49
+ handler = lambda {|e| error_response(e) }
50
+ else
51
+ raise unless is_rescuable
52
+ handler = options[:rescue_handlers][e.class] || options[:rescue_handlers][:all]
53
+ end
54
+ handler.nil? ? handle_error(e) : self.instance_exec(e, &handler)
55
+ end
56
+ end
57
+
58
+ def rescuable?(klass)
59
+ options[:rescue_all] || (options[:rescued_errors] || []).include?(klass)
60
+ end
61
+
62
+ def handle_error(e)
63
+ error_response({ :message => e.message, :backtrace => e.backtrace })
64
+ end
65
+
66
+ def error_response(error = {})
67
+ status = error[:status] || options[:default_status]
68
+ message = error[:message] || options[:default_message]
69
+ headers = {'Content-Type' => content_type}
70
+ headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
71
+ backtrace = error[:backtrace] || []
72
+ rack_response(format_message(message, backtrace, status), status, headers)
73
+ end
74
+
75
+ def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
76
+ Rack::Response.new([ message ], status, headers).finish
77
+ end
78
+
79
+ def format_message(message, backtrace, status)
80
+ formatter = formatter_for(options[:format])
81
+ throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
82
+ formatter.call(message, backtrace)
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,17 @@
1
+ module Grape
2
+ module Middleware
3
+ # This is a simple middleware for adding before and after filters
4
+ # to Grape APIs. It is used like so:
5
+ #
6
+ # use Grape::Middleware::Filter, :before => lambda{ do_something }, after: => lambda{ do_something }
7
+ class Filter < Base
8
+ def before
9
+ app.instance_eval &options[:before] if options[:before]
10
+ end
11
+
12
+ def after
13
+ app.instance_eval &options[:after] if options[:after]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,81 @@
1
+ require 'grape/middleware/base'
2
+
3
+ module Grape
4
+ module Middleware
5
+ class Formatter < Base
6
+ include Formats
7
+
8
+ def default_options
9
+ {
10
+ :default_format => :txt,
11
+ :formatters => {},
12
+ :content_types => {},
13
+ :parsers => {}
14
+ }
15
+ end
16
+
17
+ def headers
18
+ env.dup.inject({}){|h,(k,v)| h[k.to_s.downcase[5..-1]] = v if k.to_s.downcase.start_with?('http_'); h}
19
+ end
20
+
21
+ def before
22
+ fmt = format_from_extension || options[:format] || format_from_header || options[:default_format]
23
+ if content_types.key?(fmt)
24
+ if !env['rack.input'].nil? and (body = env['rack.input'].read).strip.length != 0
25
+ parser = parser_for fmt
26
+ unless parser.nil?
27
+ begin
28
+ body = parser.call(body)
29
+ env['rack.request.form_hash'] = !env['rack.request.form_hash'].nil? ? env['rack.request.form_hash'].merge(body) : body
30
+ env['rack.request.form_input'] = env['rack.input']
31
+ rescue
32
+ # It's possible that it's just regular POST content -- just back off
33
+ end
34
+ end
35
+ env['rack.input'].rewind
36
+ end
37
+ env['api.format'] = fmt
38
+ else
39
+ throw :error, :status => 406, :message => 'The requested format is not supported.'
40
+ end
41
+ end
42
+
43
+ def format_from_extension
44
+ parts = request.path.split('.')
45
+ extension = parts.last.to_sym
46
+
47
+ if parts.size > 1 && content_types.key?(extension)
48
+ return extension
49
+ end
50
+ nil
51
+ end
52
+
53
+ def format_from_header
54
+ mime_array.each do |t|
55
+ if mime_types.key?(t)
56
+ return mime_types[t]
57
+ end
58
+ end
59
+ nil
60
+ end
61
+
62
+ def mime_array
63
+ accept = headers['accept'] or return []
64
+
65
+ accept.gsub(/\b/,'').scan(%r((\w+/[\w+.-]+)(?:(?:;[^,]*?)?;\s*q=([\d.]+))?)).sort_by { |_, q| -q.to_f }.map {|mime, _|
66
+ mime.sub(%r(vnd\.[^+]+\+), '')
67
+ }
68
+ end
69
+
70
+ def after
71
+ status, headers, bodies = *@app_response
72
+ formatter = formatter_for env['api.format']
73
+ bodymap = bodies.collect do |body|
74
+ formatter.call(body)
75
+ end
76
+ headers['Content-Type'] = content_types[env['api.format']] unless headers['Content-Type']
77
+ Rack::Response.new(bodymap, status, headers).to_a
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,21 @@
1
+ require 'rack/mount/utils'
2
+ require 'grape'
3
+
4
+ module Grape
5
+ module Middleware
6
+ class Prefixer < Base
7
+ def prefix
8
+ prefix = options[:prefix] || ""
9
+ prefix = Rack::Mount::Utils.normalize_path(prefix)
10
+ prefix
11
+ end
12
+
13
+ def before
14
+ if env['PATH_INFO'].index(prefix) == 0
15
+ env['PATH_INFO'].sub!(prefix, '')
16
+ env['PATH_INFO'] = Rack::Mount::Utils.normalize_path(env['PATH_INFO'])
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ require 'grape/middleware/base'
2
+
3
+ module Grape
4
+ module Middleware
5
+ module Versioner
6
+ # This middleware sets various version related rack environment variables
7
+ # based on the HTTP Accept header with the pattern:
8
+ # application/vnd.:vendor-:version+:format
9
+ #
10
+ # Example: For request header
11
+ # Accept: application/vnd.mycompany-v1+json
12
+ #
13
+ # The following rack env variables are set:
14
+ #
15
+ # env['api.type'] => 'application'
16
+ # env['api.subtype'] => 'vnd.mycompany-v1+json'
17
+ # env['api.vendor] => 'mycompany'
18
+ # env['api.version] => 'v1'
19
+ # env['api.format] => 'format'
20
+ #
21
+ # If version does not match this route, then a 406 is throw with
22
+ # X-Cascade header to alert Rack::Mount to attempt the next matched
23
+ # route.
24
+ class Header < Base
25
+ def before
26
+ accept = env['HTTP_ACCEPT'] || ""
27
+
28
+ if options[:version_options] && options[:version_options].keys.include?(:strict) && options[:version_options][:strict]
29
+ if (is_accept_header_valid?(accept)) && options[:version_options][:using] == :header
30
+ throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found"
31
+ end
32
+ end
33
+ accept.strip.scan(/^(.+?)\/(.+?)$/) do |type, subtype|
34
+ env['api.type'] = type
35
+ env['api.subtype'] = subtype
36
+
37
+ subtype.scan(/vnd\.(.+)?-(.+)?\+(.*)?/) do |vendor, version, format|
38
+ is_vendored = options[:version_options] && options[:version_options][:vendor]
39
+ is_vendored_match = is_vendored ? options[:version_options][:vendor] == vendor : true
40
+
41
+ if (options[:versions] && !options[:versions].include?(version)) || !is_vendored_match
42
+ throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found"
43
+ end
44
+
45
+ env['api.version'] = version
46
+ env['api.vendor'] = vendor
47
+ env['api.format'] = format # weird that Grape::Middleware::Formatter also does this
48
+ end
49
+ end
50
+ end
51
+
52
+ protected
53
+ def is_accept_header_valid?(header)
54
+ (header.strip =~ /application\/vnd\.(.+?)-(.+?)\+(.+?)/).nil?
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,44 @@
1
+ require 'grape/middleware/base'
2
+
3
+ module Grape
4
+ module Middleware
5
+ module Versioner
6
+ # This middleware sets various version related rack environment variables
7
+ # based on the request parameters and removes that parameter from the
8
+ # request parameters for subsequent middleware and API.
9
+ # If the version substring does not match any potential initialized
10
+ # versions, a 404 error is thrown.
11
+ # If the version substring is not passed the version (highest mounted)
12
+ # version will be used.
13
+ #
14
+ # Example: For a uri path
15
+ # /resource?apiver=v1
16
+ #
17
+ # The following rack env variables are set and path is rewritten to
18
+ # '/resource':
19
+ #
20
+ # env['api.version'] => 'v1'
21
+ class Param < Base
22
+ def default_options
23
+ {
24
+ :parameter => "apiver"
25
+ }
26
+ end
27
+
28
+ def before
29
+ paramkey = options[:parameter]
30
+ potential_version = request.params[paramkey]
31
+
32
+ unless potential_version.nil?
33
+ if options[:versions] && !options[:versions].include?(potential_version)
34
+ throw :error, :status => 404, :message => "404 API Version Not Found", :headers => {'X-Cascade' => 'pass'}
35
+ end
36
+ env['api.version'] = potential_version
37
+ env['rack.request.query_hash'].delete(paramkey)
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ require 'grape/middleware/base'
2
+
3
+ module Grape
4
+ module Middleware
5
+ module Versioner
6
+ # This middleware sets various version related rack environment variables
7
+ # based on the uri path and removes the version substring from the uri
8
+ # path. If the version substring does not match any potential initialized
9
+ # versions, a 404 error is thrown.
10
+ #
11
+ # Example: For a uri path
12
+ # /v1/resource
13
+ #
14
+ # The following rack env variables are set and path is rewritten to
15
+ # '/resource':
16
+ #
17
+ # env['api.version'] => 'v1'
18
+ #
19
+ class Path < Base
20
+ def default_options
21
+ {
22
+ :pattern => /.*/i
23
+ }
24
+ end
25
+
26
+ def before
27
+ pieces = env['PATH_INFO'].split('/')
28
+ potential_version = pieces[1]
29
+ if potential_version =~ options[:pattern]
30
+ if options[:versions] && !options[:versions].include?(potential_version)
31
+ throw :error, :status => 404, :message => "404 API Version Not Found"
32
+ end
33
+
34
+ truncated_path = "/#{pieces[2..-1].join('/')}"
35
+ env['api.version'] = potential_version
36
+ env['PATH_INFO'] = truncated_path
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ # Versioners set env['api.version'] when a version is defined on an API and
2
+ # on the requests. The current methods for determining version are:
3
+ #
4
+ # :header - version from HTTP Accept header.
5
+ # :path - version from uri. e.g. /v1/resource
6
+ #
7
+ # See individual classes for details.
8
+ module Grape
9
+ module Middleware
10
+ module Versioner
11
+ extend self
12
+
13
+ # @param strategy [Symbol] :path or :header
14
+ # @return a middleware class based on strategy
15
+ def using(strategy)
16
+ case strategy
17
+ when :path
18
+ Path
19
+ when :header
20
+ Header
21
+ when :param
22
+ Param
23
+ else
24
+ raise ArgumentError.new("Unknown :using for versioner: #{strategy}")
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module Grape
2
+
3
+ # A compiled route for inspection.
4
+ class Route
5
+
6
+ def initialize(options = {})
7
+ @options = options || {}
8
+ end
9
+
10
+ def method_missing(method_id, *arguments)
11
+ if match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
12
+ @options[match.captures.last.to_sym]
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def to_s
19
+ "version=#{route_version}, method=#{route_method}, path=#{route_path}"
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ class Hash
2
+ # deep_merge from rails
3
+ # activesupport/lib/active_support/core_ext/hash/deep_merge.rb
4
+ # Returns a new hash with +self+ and +other_hash+ merged recursively.
5
+ #
6
+ # h1 = {:x => {:y => [4,5,6]}, :z => [7,8,9]}
7
+ # h2 = {:x => {:y => [7,8,9]}, :z => "xyz"}
8
+ #
9
+ # h1.deep_merge(h2) #=> { :x => {:y => [7, 8, 9]}, :z => "xyz" }
10
+ # h2.deep_merge(h1) #=> { :x => {:y => [4, 5, 6]}, :z => [7, 8, 9] }
11
+ def deep_merge(other_hash)
12
+ dup.deep_merge!(other_hash)
13
+ end
14
+
15
+ # Same as +deep_merge+, but modifies +self+.
16
+ def deep_merge!(other_hash)
17
+ other_hash.each_pair do |k,v|
18
+ tv = self[k]
19
+ self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
20
+ end
21
+ self
22
+ end
23
+ end
@@ -0,0 +1,100 @@
1
+ module Grape
2
+ module Util
3
+ # HashStack is a stack of hashes. When retrieving a value, keys of the top
4
+ # hash on the stack take precendent over the lower keys.
5
+ class HashStack
6
+ # Unmerged array of hashes to represent the stack.
7
+ # The top of the stack is the last element.
8
+ attr_reader :stack
9
+
10
+ # TODO: handle aggregates
11
+ def initialize
12
+ @stack = [{}]
13
+ end
14
+
15
+ # Returns the top hash on the stack
16
+ def peek
17
+ @stack.last
18
+ end
19
+
20
+ # Add a new hash to the top of the stack.
21
+ #
22
+ # @param hash [Hash] optional hash to be pushed. Defaults to empty hash
23
+ # @return [HashStack]
24
+ def push(hash = {})
25
+ @stack.push(hash)
26
+ self
27
+ end
28
+
29
+ def pop
30
+ @stack.pop
31
+ end
32
+
33
+ # Looks through the stack for the first frame that matches :key
34
+ #
35
+ # @param key [Symbol] key to look for in hash frames
36
+ # @return value of given key after merging the stack
37
+ def get(key)
38
+ (@stack.length - 1).downto(0).each do |i|
39
+ return @stack[i][key] if @stack[i].key? key
40
+ end
41
+ nil
42
+ end
43
+ alias_method :[], :get
44
+
45
+ # Replace a value on the top hash of the stack.
46
+ #
47
+ # @param key [Symbol] The key to set.
48
+ # @param value [Object] The value to set.
49
+ def set(key, value)
50
+ peek[key.to_sym] = value
51
+ end
52
+ alias_method :[]=, :set
53
+
54
+ # Replace multiple values on the top hash of the stack.
55
+ #
56
+ # @param hash [Hash] Hash of values to be merged in.
57
+ def update(hash)
58
+ peek.merge!(hash)
59
+ self
60
+ end
61
+
62
+ # Adds addition value into the top hash of the stack
63
+ def imbue(key, value)
64
+ current = peek[key.to_sym]
65
+ if current.is_a?(Array)
66
+ current.concat(value)
67
+ elsif current.is_a?(Hash)
68
+ current.merge!(value)
69
+ else
70
+ set(key, value)
71
+ end
72
+ end
73
+
74
+ # Prepend another HashStack's to self
75
+ def prepend(hash_stack)
76
+ @stack.unshift *hash_stack.stack
77
+ self
78
+ end
79
+
80
+ # Concatenate another HashStack's to self
81
+ def concat(hash_stack)
82
+ @stack.concat hash_stack.stack
83
+ self
84
+ end
85
+
86
+ def to_s
87
+ @stack.to_s
88
+ end
89
+
90
+ def clone
91
+ new_stack = HashStack.new
92
+ stack.each do |frame|
93
+ new_stack.push frame.clone
94
+ end
95
+ new_stack.stack.shift
96
+ new_stack
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,61 @@
1
+ module Grape
2
+
3
+ class API
4
+ Boolean = Virtus::Attribute::Boolean
5
+ end
6
+
7
+ module Validations
8
+
9
+ class CoerceValidator < SingleOptionValidator
10
+ def validate_param!(attr_name, params)
11
+ new_value = coerce_value(@option, params[attr_name])
12
+ if valid_type?(new_value)
13
+ params[attr_name] = new_value
14
+ else
15
+ raise ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
16
+ end
17
+ end
18
+
19
+ class InvalidValue; end
20
+ private
21
+
22
+ def _valid_array_type?(type, values)
23
+ values.all? do |val|
24
+ _valid_single_type?(type, val)
25
+ end
26
+ end
27
+
28
+ def _valid_single_type?(klass, val)
29
+ # allow nil, to ignore when a parameter is absent
30
+ return true if val.nil?
31
+ if klass == Virtus::Attribute::Boolean
32
+ val.is_a?(TrueClass) || val.is_a?(FalseClass)
33
+ elsif klass == Rack::Multipart::UploadedFile
34
+ val.is_a?(Hashie::Mash) && val.key?(:tempfile)
35
+ else
36
+ val.is_a?(klass)
37
+ end
38
+ end
39
+
40
+ def valid_type?(val)
41
+ if @option.is_a?(Array)
42
+ _valid_array_type?(@option[0], val)
43
+ else
44
+ _valid_single_type?(@option, val)
45
+ end
46
+ end
47
+
48
+ def coerce_value(type, val)
49
+ converter = Virtus::Attribute.build(:a, type)
50
+ converter.coerce(val)
51
+
52
+ # not the prettiest but some invalid coercion can currently trigger
53
+ # errors in Virtus (see coerce_spec.rb)
54
+ rescue
55
+ InvalidValue.new
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,11 @@
1
+ module Grape
2
+ module Validations
3
+ class PresenceValidator < Validator
4
+ def validate_param!(attr_name, params)
5
+ unless params.has_key?(attr_name)
6
+ raise ValidationError, :status => 400, :param => attr_name, :message => "missing parameter: #{attr_name}"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Grape
2
+ module Validations
3
+
4
+ class RegexpValidator < SingleOptionValidator
5
+ def validate_param!(attr_name, params)
6
+ if params[attr_name] && !( params[attr_name].to_s =~ @option )
7
+ raise ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
8
+ end
9
+ end
10
+ end
11
+
12
+ end
13
+ end