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.
- data/lib/strobe.rb +32 -16
- data/lib/strobe/addons/social.rb +130 -0
- data/lib/strobe/cli.rb +20 -12
- data/lib/strobe/cli/main.rb +53 -11
- data/lib/strobe/cli/preview.rb +4 -2
- data/lib/strobe/cli/settings.rb +95 -42
- data/lib/strobe/cli/users.rb +4 -2
- data/lib/strobe/collection.rb +1 -1
- data/lib/strobe/config.rb +181 -0
- data/lib/strobe/connection.rb +58 -19
- data/lib/strobe/exception_notifier.rb +59 -0
- data/lib/strobe/identity_map.rb +4 -2
- data/lib/strobe/middleware/addons.rb +73 -0
- data/lib/strobe/middleware/proxy.rb +315 -105
- data/lib/strobe/middleware/rewrite.rb +14 -1
- data/lib/strobe/resource/base.rb +2 -2
- data/lib/strobe/resources.rb +1 -0
- data/lib/strobe/resources/application.rb +10 -1
- data/lib/strobe/resources/deploy.rb +14 -0
- data/lib/strobe/sproutcore.rb +2 -1
- metadata +46 -8
data/lib/strobe/cli/users.rb
CHANGED
@@ -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"
|
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"
|
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
|
data/lib/strobe/collection.rb
CHANGED
@@ -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
|
data/lib/strobe/connection.rb
CHANGED
@@ -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
|
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
|
-
|
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(
|
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
|
data/lib/strobe/identity_map.rb
CHANGED
@@ -34,11 +34,13 @@ module Strobe
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def [](klass, key)
|
37
|
-
|
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
|
-
|
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
|