fragrant 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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