strobe 0.2.0.beta.2 → 0.2.0
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 +4 -2
- data/lib/strobe/addons/social.rb +21 -110
- data/lib/strobe/addons/social/facebook.rb +83 -0
- data/lib/strobe/addons/social/twitter.rb +159 -0
- data/lib/strobe/cli.rb +9 -3
- data/lib/strobe/cli/deploys.rb +56 -0
- data/lib/strobe/cli/main.rb +132 -17
- data/lib/strobe/cli/preview.rb +20 -13
- data/lib/strobe/cli/users.rb +1 -1
- data/lib/strobe/collection.rb +8 -0
- data/lib/strobe/config.rb +3 -2
- data/lib/strobe/connection.rb +4 -2
- data/lib/strobe/middleware/proxy.rb +12 -5
- data/lib/strobe/resources.rb +1 -0
- data/lib/strobe/resources/application.rb +37 -2
- data/lib/strobe/resources/assignment.rb +2 -2
- data/lib/strobe/resources/build.rb +13 -0
- data/lib/strobe/resources/deploy.rb +2 -4
- data/lib/strobe/sproutcore.rb +6 -2
- data/lib/strobe/version.rb +3 -0
- metadata +10 -5
data/lib/strobe.rb
CHANGED
@@ -8,6 +8,8 @@ require 'active_support/core_ext/hash/indifferent_access'
|
|
8
8
|
require 'active_support/core_ext/string/inflections'
|
9
9
|
|
10
10
|
module Strobe
|
11
|
+
require 'strobe/version'
|
12
|
+
|
11
13
|
autoload :Association, 'strobe/association'
|
12
14
|
autoload :CLI, 'strobe/cli'
|
13
15
|
autoload :Collection, 'strobe/collection'
|
@@ -50,8 +52,8 @@ module Strobe
|
|
50
52
|
end
|
51
53
|
end
|
52
54
|
|
53
|
-
class RequestError
|
54
|
-
class ServerError
|
55
|
+
class RequestError < NotifiableError ; end
|
56
|
+
class ServerError < NotifiableError ; end
|
55
57
|
|
56
58
|
def self.connection
|
57
59
|
@connection
|
data/lib/strobe/addons/social.rb
CHANGED
@@ -1,129 +1,40 @@
|
|
1
|
-
require 'oauth'
|
2
|
-
|
3
1
|
module Strobe
|
4
2
|
module Addons
|
5
3
|
class Social
|
6
|
-
|
7
|
-
REQUEST_TOKEN_SECRETS = {}
|
8
|
-
ACCESS_TOKEN_SECRETS = {}
|
4
|
+
ADDON_PATH = %r[/([^/]+)(/.*)?]i
|
9
5
|
|
10
6
|
def initialize(settings)
|
11
|
-
@settings
|
12
|
-
@cookie_key = "twitter_access_token"
|
13
|
-
@consumer_key = settings['social.twitter.consumer_key']
|
14
|
-
@consumer_secret = settings['social.twitter.consumer_secret']
|
15
|
-
|
16
|
-
unless @consumer_key
|
17
|
-
raise "Social addon requires `social.twitter.consumer_key` configuration"
|
18
|
-
end
|
19
|
-
|
20
|
-
unless @consumer_secret
|
21
|
-
raise "Social addon requires `social.twitter.consumer_secret` configuration"
|
22
|
-
end
|
23
|
-
|
24
|
-
@consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, :site => TWITTER_URL)
|
7
|
+
@settings = settings
|
25
8
|
end
|
26
9
|
|
27
10
|
def call(env)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def _call(env)
|
32
|
-
@request = Rack::Request.new(env)
|
33
|
-
|
34
|
-
case env["PATH_INFO"]
|
35
|
-
when "/twitter/authenticate"
|
36
|
-
get_request_token
|
37
|
-
when "/twitter/callback"
|
38
|
-
get_access_token
|
39
|
-
when %r[^/twitter(.*)$]
|
40
|
-
proxy
|
41
|
-
else
|
42
|
-
msg = "Not found"
|
43
|
-
[404, {"Content-Length" => msg.bytesize.to_s}, [msg]]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def get_request_token
|
50
|
-
request_token = @consumer.get_request_token(:oauth_callback => callback_url)
|
51
|
-
REQUEST_TOKEN_SECRETS[request_token.token] = request_token.secret
|
52
|
-
|
53
|
-
response = Rack::Response.new
|
54
|
-
response.redirect(request_token.authorize_url(:oauth_callback => callback_url))
|
55
|
-
response.finish
|
56
|
-
end
|
11
|
+
if env["PATH_INFO"] =~ ADDON_PATH
|
12
|
+
addon, path = $1, $2
|
57
13
|
|
58
|
-
|
59
|
-
response = Rack::Response.new
|
14
|
+
env = env.merge("SCRIPT_NAME" => "/_strobe/social/#{addon}", "PATH_INFO" => path || "")
|
60
15
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
request_token = OAuth::RequestToken.new(@consumer, oauth_token, request_token)
|
67
|
-
access_token = request_token.get_access_token(:oauth_verifier => oauth_verifier)
|
68
|
-
ACCESS_TOKEN_SECRETS[access_token.token] = access_token.secret
|
69
|
-
|
70
|
-
response.body = ['<html><head><script>window.opener?window.close():(window.location = "/")</script></head></html>']
|
71
|
-
response.set_cookie(@cookie_key, :value => access_token.token, :path => "/")
|
72
|
-
response.finish
|
16
|
+
if addon_class = find_addon(addon)
|
17
|
+
addon_class.new(@settings).call(env)
|
18
|
+
else
|
19
|
+
[404, {}, ["Not found"]]
|
20
|
+
end
|
73
21
|
else
|
74
|
-
|
75
|
-
[404, {"Content-Type" => "text/plain", "Content-Length" => msg.bytesize.to_s}, msg]
|
22
|
+
[404, {}, ["Not found"]]
|
76
23
|
end
|
77
24
|
end
|
78
25
|
|
79
|
-
|
80
|
-
token = @request.cookies[@cookie_key]
|
81
|
-
access_token = OAuth::AccessToken.new(@consumer, token, ACCESS_TOKEN_SECRETS[token])
|
82
|
-
|
83
|
-
request_method = @request.request_method.to_s.upcase
|
84
|
-
request_path = @request.fullpath.sub(%r<^/_strobe/social/twitter>, "")
|
85
|
-
request_body = @request.body.read
|
86
|
-
request_headers = {}
|
87
|
-
request_headers["Content-Type"] = @request.content_type if @request.content_type.present?
|
88
|
-
request_headers["Content-Length"] = @request.content_length if @request.content_length.present?
|
89
|
-
|
90
|
-
http = Net::HTTP.new(URI.parse(TWITTER_URL).host, URI.parse(TWITTER_URL).port)
|
91
|
-
oauth_request = Net::HTTPGenericRequest.new(request_method, request_body.present?, request_method != "HEAD", request_path, request_headers)
|
92
|
-
oauth_request.body = request_body if request_body.present?
|
93
|
-
oauth_request.oauth!(http, @consumer, access_token)
|
94
|
-
|
95
|
-
response_headers = {}
|
96
|
-
|
97
|
-
response_headers["Authorization"] = oauth_request["Authorization"]
|
98
|
-
response_headers["X-Strobe-Forward-Headers"] = "Authorization"
|
99
|
-
|
100
|
-
response_headers["X-Strobe-Location"] = URI.join(TWITTER_URL, request_path).to_s
|
101
|
-
response_headers["X-Strobe-Redirect-Method"] = request_method
|
102
|
-
|
103
|
-
if oauth_request["Content-Type"].present?
|
104
|
-
response_headers["X-Strobe-Forward-Headers"] += ",Content-Type"
|
105
|
-
response_headers["Content-Type"] = oauth_request["Content-Type"]
|
106
|
-
end
|
26
|
+
private
|
107
27
|
|
108
|
-
|
109
|
-
|
110
|
-
|
28
|
+
def find_addon(addon)
|
29
|
+
class_name = addon.camelize
|
30
|
+
Strobe::Addons::Social.const_get(class_name)
|
31
|
+
rescue
|
32
|
+
begin
|
33
|
+
require "strobe/addons/social/#{addon}"
|
34
|
+
Strobe::Addons::Social.const_get(class_name)
|
35
|
+
rescue
|
36
|
+
nil # not found
|
111
37
|
end
|
112
|
-
|
113
|
-
[302, response_headers, [request_body]]
|
114
|
-
end
|
115
|
-
|
116
|
-
def root_url
|
117
|
-
url_for("")
|
118
|
-
end
|
119
|
-
|
120
|
-
def callback_url
|
121
|
-
url_for("/twitter/callback")
|
122
|
-
end
|
123
|
-
|
124
|
-
def url_for(path)
|
125
|
-
# host_with_port returns http://localhost:9293 in tests
|
126
|
-
"#{@request.scheme}://#{@request.host_with_port.sub(%r<^https?://>, "")}#{@request.script_name}#{path}"
|
127
38
|
end
|
128
39
|
end
|
129
40
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "cgi"
|
2
|
+
|
3
|
+
module Strobe
|
4
|
+
module Addons
|
5
|
+
class Social
|
6
|
+
class Facebook
|
7
|
+
COOKIE_KEY = "facebook_access_token"
|
8
|
+
|
9
|
+
class NotFoundError < StandardError
|
10
|
+
def code; 404 end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(settings)
|
14
|
+
@settings = settings
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
dup._call(env)
|
19
|
+
rescue NotFoundError => error
|
20
|
+
Rack::Response.new("#{error.message}\n", error.code, "Content-Type" => "text/plain").finish
|
21
|
+
end
|
22
|
+
|
23
|
+
def _call(env)
|
24
|
+
@request = Rack::Request.new(env)
|
25
|
+
@response = Rack::Response.new
|
26
|
+
|
27
|
+
case @request.path_info
|
28
|
+
when "/callback"
|
29
|
+
callback
|
30
|
+
when "/status"
|
31
|
+
status
|
32
|
+
else
|
33
|
+
raise NotFoundError, "Not found"
|
34
|
+
end
|
35
|
+
|
36
|
+
@response.finish
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def callback
|
42
|
+
body = <<-EOHTML
|
43
|
+
<html><head><script>
|
44
|
+
var i, l, a, e, kv, p = (window.location.toString().split("#", 2)[1] || "").split("&");
|
45
|
+
for (i = 0, l = p.length; i < l; i++) {
|
46
|
+
kv = p[i].split("=", 2);
|
47
|
+
switch (kv[0]) {
|
48
|
+
case "access_token":
|
49
|
+
a = kv[1];
|
50
|
+
break;
|
51
|
+
case "expires_in":
|
52
|
+
e = kv[1];
|
53
|
+
break;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
if (a && e) {
|
57
|
+
document.cookie = "#{COOKIE_KEY}=" + decodeURIComponent(a) + "; path=/_strobe/social/facebook; expires=" + new Date(+new Date() + +e * 1000).toString();
|
58
|
+
}
|
59
|
+
window.opener ? window.close() : window.location = "/";
|
60
|
+
</script></head></html>
|
61
|
+
EOHTML
|
62
|
+
@response["Content-Type"] = "text/html"
|
63
|
+
@response.write(body)
|
64
|
+
end
|
65
|
+
|
66
|
+
def status
|
67
|
+
@response["Content-Type"] = "text/plain"
|
68
|
+
if token.present?
|
69
|
+
@response.write("OK")
|
70
|
+
else
|
71
|
+
@response.write("UNAUTHORIZED")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def token
|
78
|
+
CGI.escape(@request.cookies[COOKIE_KEY].to_s)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require "oauth"
|
2
|
+
|
3
|
+
module Strobe
|
4
|
+
module Addons
|
5
|
+
class Social
|
6
|
+
class Twitter
|
7
|
+
COOKIE_KEY = "twitter_access_token"
|
8
|
+
REQUEST_TOKEN_SECRETS = {}
|
9
|
+
ACCESS_TOKEN_SECRETS = {}
|
10
|
+
|
11
|
+
class NotFoundError < StandardError
|
12
|
+
def code; 404 end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ServerError < StandardError
|
16
|
+
def code; 500 end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(settings)
|
20
|
+
@settings = settings
|
21
|
+
@consumer_key = settings["social.twitter.consumer_key"]
|
22
|
+
@consumer_secret = settings["social.twitter.consumer_secret"]
|
23
|
+
@url = settings["social.twitter.url"] || "http://api.twitter.com"
|
24
|
+
@consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, :site => @url)
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(env)
|
28
|
+
dup._call(env)
|
29
|
+
rescue ServerError, NotFoundError => error
|
30
|
+
Rack::Response.new("#{error.message}\n", error.code, "Content-Type" => "text/plain").finish
|
31
|
+
end
|
32
|
+
|
33
|
+
def _call(env)
|
34
|
+
@request = Rack::Request.new(env)
|
35
|
+
@response = Rack::Response.new
|
36
|
+
|
37
|
+
raise ServerError, "Social addon requires `social.twitter.consumer_key` configuration" unless @consumer_key
|
38
|
+
raise ServerError, "Social addon requires `social.twitter.consumer_secret` configuration"unless @consumer_secret
|
39
|
+
|
40
|
+
case @request.path_info
|
41
|
+
when "/authentication"
|
42
|
+
env['REQUEST_METHOD'] == 'DELETE' ?
|
43
|
+
unauthenticate : authenticate
|
44
|
+
when "/callback"
|
45
|
+
callback
|
46
|
+
else
|
47
|
+
proxy
|
48
|
+
end
|
49
|
+
|
50
|
+
@response.finish
|
51
|
+
rescue Exception => e
|
52
|
+
puts e.message
|
53
|
+
puts e.backtrace
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def authenticate
|
59
|
+
@response['content-type'] = 'application/json'
|
60
|
+
|
61
|
+
if ACCESS_TOKEN_SECRETS[token]
|
62
|
+
@response.body << {
|
63
|
+
:authentication => {
|
64
|
+
:status => 'authenticated',
|
65
|
+
:authentication_uri => nil
|
66
|
+
}
|
67
|
+
}.to_json
|
68
|
+
else
|
69
|
+
if oauth_callback = @request.params["oauth_callback"]
|
70
|
+
|
71
|
+
request_token = @consumer.get_request_token(:oauth_callback => oauth_callback)
|
72
|
+
REQUEST_TOKEN_SECRETS[request_token.token] = request_token.secret
|
73
|
+
|
74
|
+
@response.body << {
|
75
|
+
:authentication => {
|
76
|
+
:status => 'unauthenticated',
|
77
|
+
:authentication_uri => request_token.authorize_url(:oauth_callback => oauth_callback)
|
78
|
+
}
|
79
|
+
}.to_json
|
80
|
+
else
|
81
|
+
@response.status = 422
|
82
|
+
@response.body = ['{"errors": {"request": "The OAuth callback must be specified."}}']
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def unauthenticate
|
88
|
+
@response.status = 204
|
89
|
+
@response.delete_cookie(COOKIE_KEY)
|
90
|
+
end
|
91
|
+
|
92
|
+
def callback
|
93
|
+
@response.write('<html><head><script>window.opener?window.close():(window.location="/")</script></head></html>')
|
94
|
+
@response["Content-Type"] = "text/html"
|
95
|
+
|
96
|
+
unless @request.params["denied"]
|
97
|
+
oauth_token = @request.params["oauth_token"]
|
98
|
+
oauth_verifier = @request.params["oauth_verifier"]
|
99
|
+
request_token_secret = REQUEST_TOKEN_SECRETS[oauth_token]
|
100
|
+
|
101
|
+
raise NotFoundError, "Could not find a proper request token. Was the server restarted?" unless request_token_secret
|
102
|
+
|
103
|
+
if oauth_token && request_token_secret
|
104
|
+
request_token = OAuth::RequestToken.new(@consumer, oauth_token, request_token_secret)
|
105
|
+
access_token = request_token.get_access_token(:oauth_verifier => oauth_verifier)
|
106
|
+
ACCESS_TOKEN_SECRETS[access_token.token] = access_token.secret
|
107
|
+
@response.set_cookie(COOKIE_KEY, :value => access_token.token, :path => "/_strobe/social/twitter")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def proxy
|
113
|
+
token_secret = ACCESS_TOKEN_SECRETS[token]
|
114
|
+
|
115
|
+
raise NotFoundError, "Could not find a proper access token. Was the server restarted?" unless token_secret
|
116
|
+
|
117
|
+
access_token = OAuth::AccessToken.new(@consumer, token, token_secret)
|
118
|
+
|
119
|
+
request_method = @request.request_method.to_s.upcase
|
120
|
+
request_path = @request.fullpath.sub(%r<^/_strobe/social/twitter>, "")
|
121
|
+
request_body = @request.body.read
|
122
|
+
request_headers = {}
|
123
|
+
request_headers["Content-Type"] = @request.content_type if @request.content_type.present?
|
124
|
+
request_headers["Content-Length"] = @request.content_length if @request.content_length.present?
|
125
|
+
|
126
|
+
http = Net::HTTP.new(URI.parse(@url).host, URI.parse(@url).port)
|
127
|
+
oauth_request = Net::HTTPGenericRequest.new(request_method, request_body.present?, request_method != "HEAD", request_path, request_headers)
|
128
|
+
oauth_request.body = request_body if request_body.present?
|
129
|
+
oauth_request.oauth!(http, @consumer, access_token)
|
130
|
+
|
131
|
+
@response["Authorization"] = oauth_request["Authorization"]
|
132
|
+
@response["X-Strobe-Forward-Headers"] = "Authorization"
|
133
|
+
|
134
|
+
@response["X-Strobe-Location"] = URI.join(@url, request_path).to_s
|
135
|
+
@response["X-Strobe-Redirect-Method"] = request_method
|
136
|
+
|
137
|
+
if oauth_request["Content-Type"].present?
|
138
|
+
@response["X-Strobe-Forward-Headers"] += ",Content-Type"
|
139
|
+
@response["Content-Type"] = oauth_request["Content-Type"]
|
140
|
+
end
|
141
|
+
|
142
|
+
if oauth_request["Content-Length"].present?
|
143
|
+
@response["X-Strobe-Forward-Headers"] += ",Content-Length"
|
144
|
+
@response["Content-Length"] = oauth_request["Content-Length"]
|
145
|
+
end
|
146
|
+
|
147
|
+
@response.body = [request_body]
|
148
|
+
@response.status = 302
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def token
|
154
|
+
@request.cookies[COOKIE_KEY]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
data/lib/strobe/cli.rb
CHANGED
@@ -13,6 +13,7 @@ module Strobe
|
|
13
13
|
autoload :Ticker, 'strobe/cli/ticker'
|
14
14
|
autoload :Users, 'strobe/cli/users'
|
15
15
|
autoload :Input, 'strobe/cli/input'
|
16
|
+
autoload :Deploys, 'strobe/cli/deploys'
|
16
17
|
|
17
18
|
include Resources
|
18
19
|
|
@@ -20,17 +21,17 @@ module Strobe
|
|
20
21
|
class_option "yes", :type => :boolean, :aliases => "-y", :banner => "answer yes to all confirmation questions"
|
21
22
|
|
22
23
|
def self.start(args)
|
23
|
-
$
|
24
|
+
$__ARGV = args
|
24
25
|
IdentityMap.wrap do
|
25
26
|
super
|
26
27
|
end
|
27
28
|
rescue NotifiableError => e
|
28
|
-
raise if $
|
29
|
+
raise if $__ARGV.include?('--backtrace')
|
29
30
|
STDERR.puts "[ERROR] #{e.message}"
|
30
31
|
ExceptionNotifier.notify(e, :action => args.join(" "))
|
31
32
|
abort
|
32
33
|
rescue StrobeError => e
|
33
|
-
raise if $
|
34
|
+
raise if $__ARGV.include?('--backtrace')
|
34
35
|
STDERR.puts "[ERROR] #{e.message}"
|
35
36
|
abort
|
36
37
|
rescue Interrupt
|
@@ -91,6 +92,11 @@ module Strobe
|
|
91
92
|
@config = Config.new_or_stub(path) || Config.stub!(path)
|
92
93
|
end
|
93
94
|
|
95
|
+
def application_dir?
|
96
|
+
File.exist?("#{path}/strobe/config.json") ||
|
97
|
+
File.exist?("#{path}/.strobe") || nil # return true or nil
|
98
|
+
end
|
99
|
+
|
94
100
|
def settings
|
95
101
|
@settings ||= Settings.for(path)
|
96
102
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Strobe
|
2
|
+
class CLI::Deploys < CLI
|
3
|
+
|
4
|
+
method_option "application-id", :type => :numeric, :banner => "Use application with given id"
|
5
|
+
action "list", "list deploys" do
|
6
|
+
id = options['application-id'] || config[:application_id]
|
7
|
+
app = Application.get!(id)
|
8
|
+
deploys = app.deploys.all
|
9
|
+
|
10
|
+
deploys_table deploys
|
11
|
+
end
|
12
|
+
|
13
|
+
method_option "application-id", :type => :numeric, :banner => "Use application with given id"
|
14
|
+
method_option "full", :type => :boolean, :banner => "Display full deploy info"
|
15
|
+
method_option "include-builds", :type => :boolean, :banner => "Include builds data"
|
16
|
+
action "show", "show the last deploy" do |*args|
|
17
|
+
sha = args.first
|
18
|
+
|
19
|
+
id = options['application-id'] || config[:application_id]
|
20
|
+
app = Application.get!(id)
|
21
|
+
|
22
|
+
deploy = sha ? app.deploys.where(:id => sha) : Deploy.where(:limit => 1, :application_id => id).first
|
23
|
+
|
24
|
+
puts "deploy #{deploy[:id]}"
|
25
|
+
|
26
|
+
if options['full'] || options['include-builds']
|
27
|
+
puts <<-DEPLOY
|
28
|
+
Author: #{deploy[:name]} <#{deploy[:email]}>
|
29
|
+
Date: #{deploy[:created_at]}
|
30
|
+
|
31
|
+
#{deploy[:message]}
|
32
|
+
DEPLOY
|
33
|
+
end
|
34
|
+
|
35
|
+
if options['include-builds']
|
36
|
+
deploy.builds.all.each do |build|
|
37
|
+
puts
|
38
|
+
puts "Build ##{build[:id]}"
|
39
|
+
puts "Status: #{build[:status]}"
|
40
|
+
puts "Download uri: #{build[:download_uri]}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def deploys_table(deploys)
|
48
|
+
table deploys do |t|
|
49
|
+
t.column :key do |d| (d[:sha1] || d[:id])[0..7] end
|
50
|
+
t.column :name
|
51
|
+
t.column :email
|
52
|
+
t.column :message
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/strobe/cli/main.rb
CHANGED
@@ -12,9 +12,64 @@ module Strobe
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
desc "version", "Prints the version information"
|
16
|
+
def version
|
17
|
+
puts "Strobe version #{VERSION}"
|
18
|
+
end
|
19
|
+
map %w(-v --version) => :version
|
20
|
+
|
21
|
+
# TODO: add ability to extend CLI with addons
|
22
|
+
# TODO: remove this after it works in Hellfire
|
23
|
+
method_option "provisioning-profile", :type => :string
|
24
|
+
method_option "certificate", :type => :string
|
25
|
+
method_option "application-id", :type => :numeric, :banner => "Use application with given id"
|
26
|
+
action "phonegap:configure:ios", "configure ios for phonegap" do
|
27
|
+
pswd = @input.ask_for_password "password: "
|
28
|
+
provisioning_path = options["provisioning-profile"]
|
29
|
+
certificate_path = options["certificate"]
|
30
|
+
|
31
|
+
id = options['application-id'] || config[:application_id]
|
32
|
+
response = Strobe.connection.get "/applications/#{id}?include=platform_installs"
|
33
|
+
if response.status == 200
|
34
|
+
ios = response.body["platform_installs"].detect { |p| p["platform_id"] == 1 }
|
35
|
+
if ios
|
36
|
+
boundary = "57f5d6d3e21c57f5d6d3e2157f5d6d3e21c57f5"
|
37
|
+
body = []
|
38
|
+
body << "--#{boundary}\r\n"
|
39
|
+
body << "Content-Disposition: form-data; name=\"application[ios_cert_file]\"; filename=\"cert.p12\"\r\n"
|
40
|
+
body << "Content-Type: application/octet-stream\r\n"
|
41
|
+
body << "\r\n"
|
42
|
+
body << File.read(certificate_path)
|
43
|
+
body << "\r\n--#{boundary}\r\n"
|
44
|
+
body << "Content-Disposition: form-data; name=\"application[ios_profile_file]\"; filename=\"provision.mobileprovision\"\r\n"
|
45
|
+
body << "Content-Type: application/octet-stream\r\n"
|
46
|
+
body << "\r\n"
|
47
|
+
body << File.read(provisioning_path)
|
48
|
+
body << "\r\n--#{boundary}\r\n"
|
49
|
+
body << "Content-Disposition: form-data; name=\"application[ios_password]\"\r\n"
|
50
|
+
body << "\r\n"
|
51
|
+
body << "#{pswd}\r\n"
|
52
|
+
body << "--#{boundary}--\r\n"
|
53
|
+
|
54
|
+
response = Strobe.connection.post "/platform_installs/#{ios["id"]}/pages/configuration", body.join, "Content-Type" => "multipart/form-data; boundary=#{boundary}", "Connection" => ""
|
55
|
+
if response.status == 201 || response.status == 204
|
56
|
+
say "Successfuly updated ios keys"
|
57
|
+
else
|
58
|
+
say "Something went wrong, server responded with #{response.status}:"
|
59
|
+
puts response.body
|
60
|
+
end
|
61
|
+
else
|
62
|
+
say "[ERROR] ios seems to be not installed in this application"
|
63
|
+
end
|
64
|
+
else
|
65
|
+
say "[ERROR] #{response.body}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
15
69
|
method_option "global", :type => :boolean, :banner => "Set option globally (for all applications)"
|
16
70
|
method_option "unset", :type => :boolean, :banner => "Unset a config option"
|
17
|
-
action "config", "configure strobe", :usage => "config [KEY] [VALUE]" do |key, val
|
71
|
+
action "config", "configure strobe", :usage => "config [KEY] [VALUE]" do |key, *val|
|
72
|
+
val = val.first
|
18
73
|
case
|
19
74
|
when val
|
20
75
|
if options[:global]
|
@@ -37,7 +92,7 @@ module Strobe
|
|
37
92
|
end
|
38
93
|
|
39
94
|
action "users", 'manage users' do |*args|
|
40
|
-
argv = $
|
95
|
+
argv = $__ARGV[1..-1]
|
41
96
|
argv = [ 'list' ] + argv if args.empty?
|
42
97
|
CLI::Users.start(argv)
|
43
98
|
end
|
@@ -49,18 +104,10 @@ module Strobe
|
|
49
104
|
Launchy.open("http://#{url}")
|
50
105
|
end
|
51
106
|
|
52
|
-
action "deploys", "manage deploys" do
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
table deploys do |t|
|
58
|
-
t.column :key do |d| d[:id][0..7] end
|
59
|
-
t.column :parent do |d| d[:parent] ? d[:parent][0..7] : "" end
|
60
|
-
t.column :name
|
61
|
-
t.column :email
|
62
|
-
t.column :message
|
63
|
-
end
|
107
|
+
action "deploys", "manage deploys" do |*args|
|
108
|
+
argv = $__ARGV[1..-1]
|
109
|
+
argv = [ 'list' ] + argv if args.empty?
|
110
|
+
CLI::Deploys.start(argv)
|
64
111
|
end
|
65
112
|
|
66
113
|
action "signup", "signup for a new Strobe account" do
|
@@ -115,6 +162,30 @@ module Strobe
|
|
115
162
|
end
|
116
163
|
|
117
164
|
application_path_option
|
165
|
+
method_option "message", :type => :string, :banner => "add rollback message", :aliases => "-m"
|
166
|
+
method_option "staging", :type => :boolean, :banner => "rollback a staging environment"
|
167
|
+
action "rollback", "rollback application to a state from given deploy" do |sha|
|
168
|
+
ensure_computer_is_registered
|
169
|
+
|
170
|
+
id = options['application-id'] || config[:application_id]
|
171
|
+
resource Application.get!(id)
|
172
|
+
|
173
|
+
host = resource[:url]
|
174
|
+
host = "staging.#{host}" if options[:staging]
|
175
|
+
|
176
|
+
unless agree "Rolling back '#{resource[:name]}' to #{sha} for http://#{host}, continue? [Yn] "
|
177
|
+
say "exiting..."
|
178
|
+
exit
|
179
|
+
end
|
180
|
+
|
181
|
+
host = resource.rollback! sha, :environment => options[:staging] && 'staging',
|
182
|
+
:message => options[:message]
|
183
|
+
|
184
|
+
say "The application has successfully been rolled back to #{sha} and is available at #{host}"
|
185
|
+
end
|
186
|
+
|
187
|
+
application_path_option
|
188
|
+
method_option "message", :type => :string, :banner => "add deploy message", :aliases => "-m"
|
118
189
|
method_option "staging", :type => :boolean, :banner => "deploy to a staging environment"
|
119
190
|
method_option "sc-build", :type => :boolean, :banner => "run `sc-build -c` before deploying"
|
120
191
|
method_option "no-sc-build", :type => :boolean, :banner => "skip the `sc-build -c` step"
|
@@ -161,8 +232,11 @@ module Strobe
|
|
161
232
|
|
162
233
|
host = resource.deploy! :environment => options[:staging] && 'staging',
|
163
234
|
:callback => CLI::DeployProgress.new,
|
235
|
+
:message => options[:message] || git_message,
|
164
236
|
:sproutcore => is_sproutcore?,
|
165
|
-
:bpm_project => bpm_project
|
237
|
+
:bpm_project => bpm_project,
|
238
|
+
:scm_version => scm_version,
|
239
|
+
:github_url => github_url
|
166
240
|
|
167
241
|
say "The application has successfully been deployed and is available at #{host}"
|
168
242
|
end
|
@@ -192,7 +266,7 @@ module Strobe
|
|
192
266
|
application_path_option
|
193
267
|
method_option "application-id", :type => :numeric, :banner => "Delete application with given id"
|
194
268
|
action "delete", "delete a specific application" do
|
195
|
-
id = options['application-id'] || config[:application_id]
|
269
|
+
id = options['application-id'] || (application_dir? && config[:application_id])
|
196
270
|
resource(id ? Application.get!(id) : pick_application)
|
197
271
|
|
198
272
|
say "Deleting application '#{resource[:name]}'"
|
@@ -321,12 +395,53 @@ module Strobe
|
|
321
395
|
end
|
322
396
|
|
323
397
|
def get_application(opts = {})
|
324
|
-
unless id = opts.delete(:application_id) || config[:application_id]
|
398
|
+
unless application_dir? && (id = opts.delete(:application_id) || config[:application_id])
|
325
399
|
error "No application found. Are you currently in the correct directory?"
|
326
400
|
exit 1
|
327
401
|
end
|
328
402
|
|
329
403
|
Application.get!(id, opts)
|
330
404
|
end
|
405
|
+
|
406
|
+
def git_repo?
|
407
|
+
@is_git_repo ||= begin
|
408
|
+
git_config_path = "#{path}/.git/config"
|
409
|
+
File.exist?(git_config_path)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def github_url
|
414
|
+
@github_url ||= if git_repo?
|
415
|
+
case git "config remote.origin.url"
|
416
|
+
when %r[^git@github.com:(.*?)\.git$]i
|
417
|
+
"https://github.com/#{$1}"
|
418
|
+
when %r[^https?://[^@]+@github\.com/(.*?)\.git$]i
|
419
|
+
"https://github.com/#{$1}"
|
420
|
+
when %r[^git://github\.com/(.*?)\.git]i
|
421
|
+
"https://github.com/#{$1}"
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def scm_version
|
427
|
+
@scm_version ||= git "rev-parse HEAD" if git_repo?
|
428
|
+
end
|
429
|
+
|
430
|
+
def git_message
|
431
|
+
return unless git_repo?
|
432
|
+
|
433
|
+
msg = git "cat-file commit #{scm_version}"
|
434
|
+
|
435
|
+
return unless msg
|
436
|
+
|
437
|
+
msg.split("\n\n", 2).last
|
438
|
+
end
|
439
|
+
|
440
|
+
def git(cmd)
|
441
|
+
Dir.chdir path do
|
442
|
+
retval = `git #{cmd}`
|
443
|
+
retval.chomp if $? == 0
|
444
|
+
end
|
445
|
+
end
|
331
446
|
end
|
332
447
|
end
|
data/lib/strobe/cli/preview.rb
CHANGED
@@ -11,22 +11,15 @@ module Strobe
|
|
11
11
|
alias wrapped_app app
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
def preview_strobe_application
|
17
|
-
is_sproutcore?? preview_sproutcore_application : preview_html_application
|
18
|
-
end
|
19
|
-
|
20
|
-
def bpm_project
|
21
|
-
@bpm_project ||= BPM::Project.nearest_project(Dir.pwd)
|
22
|
-
end
|
23
|
-
|
24
|
-
def preview_html_application
|
14
|
+
def wrap(endpoint)
|
25
15
|
project = bpm_project
|
26
16
|
|
17
|
+
c = config
|
18
|
+
s = settings
|
19
|
+
|
27
20
|
app = Rack::Builder.new do
|
28
21
|
use Middleware::Proxy
|
29
|
-
use Middleware::Addons
|
22
|
+
use Middleware::Addons, :config => c, :settings => s
|
30
23
|
use Middleware::Rewrite
|
31
24
|
|
32
25
|
if project
|
@@ -36,15 +29,29 @@ module Strobe
|
|
36
29
|
end
|
37
30
|
|
38
31
|
map "/" do
|
39
|
-
run
|
32
|
+
run endpoint
|
40
33
|
end
|
41
34
|
end.to_app
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def preview_strobe_application
|
40
|
+
is_sproutcore?? preview_sproutcore_application : preview_html_application
|
41
|
+
end
|
42
|
+
|
43
|
+
def bpm_project
|
44
|
+
@bpm_project ||= BPM::Project.nearest_project(Dir.pwd)
|
45
|
+
end
|
42
46
|
|
47
|
+
def preview_html_application
|
48
|
+
app = wrap(Rack::Static.new lambda { |e| [] }, :urls => [ '/' ], :root => '.')
|
43
49
|
Server.start :app => app, :host => '0.0.0.0', :server => "thin", :Port => 9292
|
44
50
|
end
|
45
51
|
|
46
52
|
def preview_sproutcore_application
|
47
53
|
require 'strobe/sproutcore'
|
54
|
+
SC::Rack::Service.strobe_stack(self)
|
48
55
|
SC::Tools.start ['server'] + ARGV[1..-1] + [ '--port', '9292' ]
|
49
56
|
end
|
50
57
|
end
|
data/lib/strobe/cli/users.rb
CHANGED
@@ -56,7 +56,7 @@ module Strobe
|
|
56
56
|
def current_application
|
57
57
|
return @current_application if @current_application
|
58
58
|
|
59
|
-
unless id = config[:application_id]
|
59
|
+
unless application_dir? && id = config[:application_id]
|
60
60
|
error "No application found. Are you currently in the correct directory?"
|
61
61
|
exit 1
|
62
62
|
end
|
data/lib/strobe/collection.rb
CHANGED
data/lib/strobe/config.rb
CHANGED
@@ -19,7 +19,8 @@ module Strobe
|
|
19
19
|
c = YAML.load_file(old_config_path)
|
20
20
|
if Hash === c && application_id = c['STROBE_APPLICATION_ID']
|
21
21
|
c.delete('STROBE_APPLICATION_ID')
|
22
|
-
stub!(application_root, application_id.to_i)
|
22
|
+
inst = stub!(application_root, application_id.to_i)
|
23
|
+
inst
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
@@ -104,7 +105,7 @@ module Strobe
|
|
104
105
|
# to inject the application_id key at the very beginning
|
105
106
|
elsif @hash.keys.length > 1
|
106
107
|
json = raw_json
|
107
|
-
json.sub!(/^(\s*){\s*(})?/m) do |m|
|
108
|
+
json.sub!(/^(\s*)\{\s*(\})?/m) do |m|
|
108
109
|
lines = "#{$1}{\n" \
|
109
110
|
"#{$1} // The Strobe Platform ID for the application. This is how the local\n" \
|
110
111
|
"#{$1} // application is mapped to one on the server.\n" \
|
data/lib/strobe/connection.rb
CHANGED
@@ -149,7 +149,7 @@ module Strobe
|
|
149
149
|
|
150
150
|
def build_http
|
151
151
|
http = Net::HTTP.new host, port || 80
|
152
|
-
http.read_timeout =
|
152
|
+
http.read_timeout = 900 # For deploys
|
153
153
|
http.open_timeout = 10
|
154
154
|
http
|
155
155
|
end
|
@@ -189,7 +189,9 @@ module Strobe
|
|
189
189
|
end
|
190
190
|
|
191
191
|
if body
|
192
|
-
|
192
|
+
if Hash === body
|
193
|
+
headers['content-type'] ||= 'application/json'
|
194
|
+
end
|
193
195
|
|
194
196
|
if headers['content-type'] == 'application/json'
|
195
197
|
body = ActiveSupport::JSON.encode(body)
|
@@ -32,7 +32,12 @@ module Strobe
|
|
32
32
|
env['REQUEST_METHOD'] = hdrs['x-strobe-redirect-method']
|
33
33
|
env['REQUEST_METHOD'] ||= is_head ? 'HEAD' : 'GET'
|
34
34
|
env['async.callback'] = callback
|
35
|
-
|
35
|
+
|
36
|
+
# Rack::Request doesn't have join method
|
37
|
+
body_str = ""
|
38
|
+
body.each { |chunk| body_str << chunk }
|
39
|
+
|
40
|
+
env['rack.input'] = StringIO.new(body_str)
|
36
41
|
env
|
37
42
|
end
|
38
43
|
|
@@ -84,9 +89,10 @@ module Strobe
|
|
84
89
|
|
85
90
|
def call(env)
|
86
91
|
if env['PATH_INFO'] =~ PROXY_PATH
|
92
|
+
p1, p2 = $1, $2
|
87
93
|
scheme = scheme_from_env(env)
|
88
|
-
host, port =
|
89
|
-
path =
|
94
|
+
host, port = p1.split(':')
|
95
|
+
path = p2 || '/'
|
90
96
|
port = (port || 80).to_i
|
91
97
|
|
92
98
|
url = "#{scheme}://#{host}"
|
@@ -213,8 +219,9 @@ module Strobe
|
|
213
219
|
@redirect = headers['Location'] && @http.req.follow_redirect?
|
214
220
|
|
215
221
|
unless @redirect
|
216
|
-
|
217
|
-
headers =
|
222
|
+
h = {}
|
223
|
+
headers.each { |k,v| h[k] = v if !block_headers.include?(k.downcase) }
|
224
|
+
headers = h.merge(extra_headers)
|
218
225
|
@response_headers = ::Rack::Utils::HeaderHash.new(headers)
|
219
226
|
@body = DeferrableBody.new(self) if has_response_body?
|
220
227
|
respond(@http.response_header.status, @response_headers)
|
data/lib/strobe/resources.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'zlib'
|
2
|
+
require 'uri'
|
2
3
|
require 'digest/sha1'
|
3
4
|
|
4
5
|
module Strobe
|
@@ -15,17 +16,51 @@ module Strobe
|
|
15
16
|
|
16
17
|
validates "account", "name", :presence => true
|
17
18
|
|
19
|
+
def rollback!(sha, opts = {})
|
20
|
+
environment = opts[:environment]
|
21
|
+
message = opts[:message]
|
22
|
+
|
23
|
+
id = self[:id]
|
24
|
+
|
25
|
+
request do
|
26
|
+
qs = []
|
27
|
+
qs << "environment=#{environment}" if environment
|
28
|
+
qs << "message=#{message}" if message
|
29
|
+
qs << "deploy=#{sha}"
|
30
|
+
qs << "application_id=#{id}"
|
31
|
+
|
32
|
+
uri = URI.escape("/deploys?#{qs.join('&')}")
|
33
|
+
|
34
|
+
response = connection.post(uri)
|
35
|
+
response
|
36
|
+
end
|
37
|
+
|
38
|
+
[ environment, self['url'] ].compact.join('.')
|
39
|
+
end
|
40
|
+
|
18
41
|
def deploy!(opts = {})
|
19
42
|
self['path'] = opts[:path] if opts[:path]
|
20
43
|
environment = opts[:environment]
|
21
44
|
callback = opts[:callback]
|
45
|
+
message = opts[:message]
|
46
|
+
github_url = opts[:github_url]
|
47
|
+
scm_version = opts[:scm_version]
|
22
48
|
|
23
49
|
validate_for_deploy or return
|
24
50
|
|
25
51
|
request do
|
26
|
-
qs
|
52
|
+
qs = []
|
53
|
+
qs << "environment=#{environment}" if environment
|
54
|
+
qs << "message=#{message}" if message
|
55
|
+
qs << "github_url=#{URI.escape(github_url)}" if github_url
|
56
|
+
qs << "scm_version=#{scm_version}" if scm_version
|
57
|
+
|
58
|
+
uri = "#{http_uri}/deploy"
|
59
|
+
uri = "#{uri}?#{qs.join('&')}" unless qs.blank?
|
60
|
+
uri = URI.escape(uri)
|
61
|
+
|
27
62
|
packfile = build_packfile(opts)
|
28
|
-
response = connection.put(
|
63
|
+
response = connection.put(uri, packfile, packfile.headers)
|
29
64
|
callback.deploy_complete if callback
|
30
65
|
response
|
31
66
|
end
|
data/lib/strobe/sproutcore.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
require 'sproutcore'
|
2
2
|
|
3
3
|
class SC::Rack::Service
|
4
|
+
def self.strobe_stack(obj = nil)
|
5
|
+
@strobe_stack = obj if obj
|
6
|
+
@strobe_stack
|
7
|
+
end
|
8
|
+
|
4
9
|
def call(env)
|
5
10
|
wrapped_app.call(env)
|
6
11
|
end
|
7
12
|
|
8
13
|
def wrapped_app
|
9
|
-
|
10
|
-
@app
|
14
|
+
@wrapped_app ||= self.class.strobe_stack.wrap(@app)
|
11
15
|
end
|
12
16
|
end
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strobe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
version: 0.2.0
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Carl Lerche
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-08-16 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
requirements:
|
55
55
|
- - ~>
|
56
56
|
- !ruby/object:Gem::Version
|
57
|
-
version: 1.
|
57
|
+
version: 1.3.0
|
58
58
|
type: :runtime
|
59
59
|
version_requirements: *id004
|
60
60
|
- !ruby/object:Gem::Dependency
|
@@ -98,7 +98,7 @@ dependencies:
|
|
98
98
|
requirements:
|
99
99
|
- - ~>
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version: 0.4.
|
101
|
+
version: 0.4.5
|
102
102
|
type: :runtime
|
103
103
|
version_requirements: *id008
|
104
104
|
- !ruby/object:Gem::Dependency
|
@@ -122,9 +122,12 @@ extensions: []
|
|
122
122
|
extra_rdoc_files: []
|
123
123
|
|
124
124
|
files:
|
125
|
+
- lib/strobe/addons/social/facebook.rb
|
126
|
+
- lib/strobe/addons/social/twitter.rb
|
125
127
|
- lib/strobe/addons/social.rb
|
126
128
|
- lib/strobe/association.rb
|
127
129
|
- lib/strobe/cli/deploy_progress.rb
|
130
|
+
- lib/strobe/cli/deploys.rb
|
128
131
|
- lib/strobe/cli/input.rb
|
129
132
|
- lib/strobe/cli/main.rb
|
130
133
|
- lib/strobe/cli/preview.rb
|
@@ -148,6 +151,7 @@ files:
|
|
148
151
|
- lib/strobe/resources/account.rb
|
149
152
|
- lib/strobe/resources/application.rb
|
150
153
|
- lib/strobe/resources/assignment.rb
|
154
|
+
- lib/strobe/resources/build.rb
|
151
155
|
- lib/strobe/resources/deploy.rb
|
152
156
|
- lib/strobe/resources/me.rb
|
153
157
|
- lib/strobe/resources/membership.rb
|
@@ -157,6 +161,7 @@ files:
|
|
157
161
|
- lib/strobe/resources.rb
|
158
162
|
- lib/strobe/sproutcore.rb
|
159
163
|
- lib/strobe/validations.rb
|
164
|
+
- lib/strobe/version.rb
|
160
165
|
- lib/strobe.rb
|
161
166
|
- CHANGELOG.md
|
162
167
|
- README.md
|