strobe 0.1.6 → 0.2.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,7 +18,8 @@ module Strobe
18
18
  end
19
19
 
20
20
  application_path_option
21
- action "add", "give a user access to this application" do |email|
21
+ action "add", "give a user access to this application",
22
+ :usage => "users add <email>" do |email|
22
23
  @team = get_current_team!
23
24
  @team.memberships.create! :email => email
24
25
 
@@ -26,7 +27,8 @@ module Strobe
26
27
  end
27
28
 
28
29
  application_path_option
29
- action "remove", "remove a user's access from this application" do |email|
30
+ action "remove", "remove a user's access from this application",
31
+ :usage => "users remove <email>" do |email|
30
32
  @team = get_current_team!
31
33
  @team.memberships.each do |m|
32
34
  m.destroy if (m.user ? m.user[:email] : m[:email]) == email
@@ -22,7 +22,7 @@ module Strobe
22
22
  klass.new(hash)
23
23
  end
24
24
  else
25
- raise "Something went wrong"
25
+ raise ServerError.new("Something went wrong", :request => resp.request, :response => resp)
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,181 @@
1
+ require 'json'
2
+
3
+ module Strobe
4
+ class Config
5
+ class InPlaceEditException < RuntimeError; end
6
+
7
+ # This function is specifically to migrate legacy applications
8
+ # still using .strobe/config to track the application ID
9
+ def self.new_or_stub(application_root)
10
+ application_root = File.expand_path(application_root.to_s)
11
+
12
+ IdentityMap.identify self, application_root do
13
+ config_path = "#{application_root}/strobe/config.json"
14
+ old_config_path = "#{application_root}/.strobe/config"
15
+
16
+ if File.exist?(config_path)
17
+ new(config_path)
18
+ elsif File.exist?(old_config_path)
19
+ c = YAML.load_file(old_config_path)
20
+ if Hash === c && application_id = c['STROBE_APPLICATION_ID']
21
+ c.delete('STROBE_APPLICATION_ID')
22
+ stub!(application_root, application_id.to_i)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.stub!(path, application_id = nil)
29
+ config_path = "#{path}/strobe/config.json"
30
+
31
+ if File.exist?(config_path)
32
+ raise "There already is a config file, cannot stub"
33
+ end
34
+
35
+ inst = new(config_path)
36
+ inst.set_application_id!(application_id || "[PENDING DEPLOY]")
37
+ inst
38
+ end
39
+
40
+ def initialize(path)
41
+ @path, @hash = path, nil
42
+ update!
43
+ end
44
+
45
+ def application_path
46
+ File.expand_path('../..', @path)
47
+ end
48
+
49
+ def [](key)
50
+ key = key.to_s
51
+
52
+ if key.blank?
53
+ raise ArgumentError, "Key cannot be blank"
54
+ end
55
+
56
+ if key == "application_id"
57
+ if @hash["application_id"] =~ /^\s*\d+\s*$/
58
+ return @hash["application_id"].to_i
59
+ end
60
+
61
+ return
62
+ end
63
+
64
+ key.split('.').inject(@hash) do |val, key|
65
+ val && val[key]
66
+ end
67
+ end
68
+
69
+ def unset_application_id!
70
+ set_application_id! "[PENDING DEPLOY]"
71
+ end
72
+
73
+ # MEGA HACK WARNING!
74
+ # We need to be able to update the application_id in the json file
75
+ # after the first deploy. It would be nice to do this without modifying
76
+ # anything else, so we find the spot where the old value is, swap in
77
+ # the new value
78
+ def set_application_id!(id)
79
+ id = id.to_s
80
+ old_id = @hash["application_id"]
81
+ self[:application_id] = id
82
+
83
+ # First make sure that there already is an application_id value
84
+ if old_id
85
+ first, rest = "", raw_json
86
+ raw_id = old_id.to_json
87
+
88
+ while m = /#{Regexp.escape(raw_id)}/.match(rest)
89
+ new_str = "#{first}#{m.pre_match}#{id.to_json}#{m.post_match}"
90
+ new_hash = JSON.parse(new_str) rescue nil
91
+
92
+ if @hash == new_hash
93
+ write_raw_json(new_str)
94
+ return
95
+ end
96
+
97
+ rest = m.post_match
98
+ first << m.pre_match << m[0]
99
+ end
100
+
101
+ raise InPlaceEditException, "Was unable to set application_id to #{id}"
102
+
103
+ # check if there are any other keys, if there are, then we need
104
+ # to inject the application_id key at the very beginning
105
+ elsif @hash.keys.length > 1
106
+ json = raw_json
107
+ json.sub!(/^(\s*){\s*(})?/m) do |m|
108
+ lines = "#{$1}{\n" \
109
+ "#{$1} // The Strobe Platform ID for the application. This is how the local\n" \
110
+ "#{$1} // application is mapped to one on the server.\n" \
111
+ "#{$1} \"application_id\": #{id.to_json},\n\n"
112
+
113
+ if $3
114
+ lines << "#{$1}}"
115
+ else
116
+ lines << "#{$1} "
117
+ end
118
+
119
+ lines
120
+ end
121
+
122
+ write_raw_json(json)
123
+
124
+ # Otherwise, the json file is basically empty, so we probably can just
125
+ # fully reset it
126
+ else
127
+
128
+ stub!(id)
129
+
130
+ end
131
+ end
132
+
133
+ def update!
134
+ parsed = parsed_json
135
+ @hash = parsed
136
+ end
137
+
138
+ private
139
+
140
+ def stub!(application_id = nil)
141
+ FileUtils.mkdir_p(File.dirname(@path))
142
+ write_raw_json <<-JSON
143
+ // Strobe Platform application configuration file. This file should
144
+ // be checked into revision control.
145
+ {
146
+
147
+ // The Strobe Platform ID for the application. This is how the local
148
+ // application is mapped to one on the server.
149
+ "application_id": "#{application_id || "[PENDING DEPLOY]"}"
150
+
151
+ }
152
+ JSON
153
+ end
154
+
155
+ def []=(key, val)
156
+ keys = key.to_s.split('.')
157
+
158
+ node = keys[0..-2].inject(@hash) do |val, key|
159
+ val[key]
160
+ end
161
+
162
+ node[keys.last] = val
163
+ end
164
+
165
+ def write_raw_json(str)
166
+ File.open(@path, 'w') { |f| f.write str }
167
+ end
168
+
169
+ def parsed_json
170
+ if str = raw_json
171
+ JSON.parse(str)
172
+ else
173
+ {}
174
+ end
175
+ end
176
+
177
+ def raw_json
178
+ File.read(@path) if File.exist?(@path)
179
+ end
180
+ end
181
+ end
@@ -45,11 +45,12 @@ module Strobe
45
45
  end
46
46
 
47
47
  class Response
48
- attr_reader :headers
48
+ attr_reader :headers, :request
49
49
 
50
- def initialize(response)
50
+ def initialize(response, request)
51
51
  @response = response
52
52
  @headers = {}
53
+ @request = request
53
54
 
54
55
  response.each_header do |key, val|
55
56
  @headers[key] = val
@@ -86,12 +87,20 @@ module Strobe
86
87
  when 404
87
88
  raise ResourceNotFoundError, msg
88
89
  when 500...600
89
- raise ServerError, "The server puked :(. Don't worry, the error has been reported."
90
+ raise ServerError.new("The server puked :(", :response => to_hash, :request => @request.to_hash)
90
91
  end
91
92
 
92
93
  self
93
94
  end
94
95
 
96
+ def to_hash
97
+ {
98
+ :body => body,
99
+ :headers => headers,
100
+ :status => status
101
+ }
102
+ end
103
+
95
104
  private
96
105
 
97
106
  def mime_type
@@ -103,6 +112,50 @@ module Strobe
103
112
  end
104
113
  end
105
114
 
115
+ class Request
116
+ attr_reader :connection, :method, :path, :body, :headers
117
+
118
+ def initialize(connection, method, path, body, headers)
119
+ @connection, @path, @body, @headers = connection, path, body, headers
120
+ @method = method.to_s.upcase
121
+ end
122
+
123
+ def host; connection.host end
124
+ def port; connection.port end
125
+
126
+ def send
127
+ http = build_http
128
+ request = Net::HTTPGenericRequest.new(method, !!body, true, path, headers)
129
+
130
+ body = self.body
131
+ if body.respond_to?(:read)
132
+ request.body_stream = body
133
+ body = nil
134
+ end
135
+
136
+ http.request(request, body)
137
+ end
138
+
139
+ def to_hash
140
+ {
141
+ :method => method,
142
+ :path => path,
143
+ :body => body,
144
+ :headers => headers
145
+ }
146
+ end
147
+
148
+ private
149
+
150
+ def build_http
151
+ http = Net::HTTP.new host, port || 80
152
+ http.read_timeout = 60
153
+ http.open_timeout = 10
154
+ http
155
+ end
156
+
157
+ end
158
+
106
159
  def request(method, path, body = nil, headers = {})
107
160
  headers.keys.each do |key|
108
161
  headers[key.to_s.downcase] = headers.delete(key)
@@ -124,27 +177,13 @@ module Strobe
124
177
  end
125
178
  end
126
179
 
127
- http = build_http
128
- request = Net::HTTPGenericRequest.new(
129
- method.to_s.upcase, !!body, true, path, headers)
130
-
131
- if body.respond_to?(:read)
132
- request.body_stream = body
133
- body = nil
134
- end
180
+ request = Request.new(self, method, path, body, headers)
135
181
 
136
- Response.new(http.request(request, body))
182
+ Response.new(request.send, request)
137
183
  end
138
184
 
139
185
  private
140
186
 
141
- def build_http
142
- http = Net::HTTP.new host, port || 80
143
- http.read_timeout = 60
144
- http.open_timeout = 10
145
- http
146
- end
147
-
148
187
  def authorization_header
149
188
  auth = Base64.encode64("#{@user}:#{@password}")
150
189
  auth.gsub!("\n", "")
@@ -0,0 +1,59 @@
1
+ module Strobe
2
+ class ExceptionNotifier
3
+ class << self
4
+ attr_accessor :enabled
5
+ alias_method :enabled?, :enabled
6
+
7
+ def notify(e, options = {})
8
+ return unless enabled?
9
+ message = "Something went wrong with the request. Send an error report to Strobe? [Yn] "
10
+ if options[:force] || Strobe::CLI::Input.new.agree(message)
11
+ new(e, options).notify
12
+ end
13
+ end
14
+ end
15
+
16
+ self.enabled = true
17
+ attr_reader :exception, :action
18
+
19
+ def initialize(e, options = {})
20
+ @exception = e
21
+ @options = options
22
+
23
+ extract_action
24
+ end
25
+
26
+ def notify
27
+ params = {
28
+ :error_class => error_class,
29
+ :error_message => error_message,
30
+ :strobe_action => action,
31
+ :parameters => parameters,
32
+ :backtrace => exception.backtrace.join("\n")
33
+ }
34
+ Strobe.connection.post "/errors", params
35
+ end
36
+
37
+ private
38
+
39
+ def error_class
40
+ exception.class.name
41
+ end
42
+
43
+ def error_message
44
+ "#{error_class}: #{exception.message}"
45
+ end
46
+
47
+ def parameters
48
+ exception.data if exception.respond_to? :data
49
+ end
50
+
51
+ def extract_action
52
+ if @options[:action]
53
+ @action = @options[:action]
54
+ elsif exception.respond_to?(:data) && exception.data.is_a?(Hash)
55
+ @action = exception.data.delete(:action)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -34,11 +34,13 @@ module Strobe
34
34
  end
35
35
 
36
36
  def [](klass, key)
37
- @map[klass.to_s][klass.key.typecast(key)]
37
+ key = klass.key.typecast(key) if klass.respond_to?(:key)
38
+ @map[klass.to_s][key]
38
39
  end
39
40
 
40
41
  def []=(klass, key, val)
41
- @map[klass.to_s][klass.key.typecast(key)] = val
42
+ key = klass.key.typecast(key) if klass.respond_to?(:key)
43
+ @map[klass.to_s][key] = val
42
44
  end
43
45
 
44
46
  def reset!
@@ -0,0 +1,73 @@
1
+ module Strobe
2
+ module Middleware
3
+ class Addons
4
+ ADDON_PATH = %r[/_strobe/([^/]+)(/.*)?]i
5
+
6
+ class Config
7
+ def initialize(config, settings)
8
+ @config, @settings = config || {}, settings || {}
9
+ end
10
+
11
+ def [](key)
12
+ @config[key] || @settings[key]
13
+ end
14
+ end
15
+
16
+ def initialize(app, opts = {})
17
+ @app, @opts = app, opts
18
+ end
19
+
20
+ def call(env)
21
+ if env['PATH_INFO'] =~ ADDON_PATH
22
+ addon, path = $1, $2
23
+ call_addon(env, addon, path)
24
+ else
25
+ @app.call(env)
26
+ end
27
+ end
28
+
29
+ def call_addon(env, addon, path)
30
+ script_name, path_info = "/_strobe/#{addon}", path || ""
31
+ env = env.merge("SCRIPT_NAME" => script_name, "PATH_INFO" => path_info)
32
+
33
+ begin
34
+ addon_klass = find_addon(addon)
35
+ rescue Exception => e
36
+ msg = "Not found"
37
+ return [ 404, {"Content-Length" => msg.bytesize.to_s}, [msg]]
38
+ end
39
+
40
+ status, hdrs, body = addon_klass.new(config).call(env)
41
+
42
+ if Proxy.proxy_response?(status, hdrs)
43
+ is_head = env['REQUEST_METHOD'] == 'HEAD'
44
+ Proxy.strobe_redirect(is_head, hdrs, body, env['async.callback'])
45
+ # Warp speed mr. zulu
46
+ throw :async
47
+ else
48
+ [ status, hdrs, body ]
49
+ end
50
+ rescue Exception => e
51
+ msg = "Something went wrong.\n#{e.message}\n" + e.backtrace.join("\n")
52
+ [ 500, {"Content-Length" => msg.bytesize.to_s}, [msg]]
53
+ end
54
+
55
+ private
56
+
57
+ def config
58
+ Config.new(@opts[:config], @opts[:settings])
59
+ end
60
+
61
+ def find_addon(addon)
62
+ klass_name = addon.camelize
63
+ klass =
64
+ begin
65
+ Strobe::Addons.const_get klass_name
66
+ rescue Exception
67
+ require "strobe/addons/#{addon}"
68
+ Strobe::Addons.const_get klass_name
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end