strobe 0.1.6 → 0.2.0.beta.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.
@@ -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