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