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.
@@ -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 < NotifiableError ; end
54
- class ServerError < NotifiableError ; end
55
+ class RequestError < NotifiableError ; end
56
+ class ServerError < NotifiableError ; end
55
57
 
56
58
  def self.connection
57
59
  @connection
@@ -1,129 +1,40 @@
1
- require 'oauth'
2
-
3
1
  module Strobe
4
2
  module Addons
5
3
  class Social
6
- TWITTER_URL = "http://api.twitter.com"
7
- REQUEST_TOKEN_SECRETS = {}
8
- ACCESS_TOKEN_SECRETS = {}
4
+ ADDON_PATH = %r[/([^/]+)(/.*)?]i
9
5
 
10
6
  def initialize(settings)
11
- @settings = 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
- dup._call(env)
29
- end
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
- def get_access_token
59
- response = Rack::Response.new
14
+ env = env.merge("SCRIPT_NAME" => "/_strobe/social/#{addon}", "PATH_INFO" => path || "")
60
15
 
61
- oauth_token = @request.params["oauth_token"]
62
- oauth_verifier = @request.params["oauth_verifier"]
63
- request_token = REQUEST_TOKEN_SECRETS[oauth_token]
64
-
65
- if oauth_token && request_token
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
- msg = "Could not match oauth response with an oauth request. Was the server restarted?\n"
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
- def proxy
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
- if oauth_request["Content-Length"].present?
109
- response_headers["X-Strobe-Forward-Headers"] += ",Content-Length"
110
- response_headers["Content-Length"] = oauth_request["Content-Length"]
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
@@ -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
- $ARGV = args
24
+ $__ARGV = args
24
25
  IdentityMap.wrap do
25
26
  super
26
27
  end
27
28
  rescue NotifiableError => e
28
- raise if $ARGV.include?('--backtrace')
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 $ARGV.include?('--backtrace')
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
@@ -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 = nil|
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 = $ARGV[1..-1]
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
- id = options['application-id'] || config[:application_id]
54
- app = Application.new(:id => id)
55
- deploys = app.deploys.all
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
@@ -11,22 +11,15 @@ module Strobe
11
11
  alias wrapped_app app
12
12
  end
13
13
 
14
- private
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 Rack::Static.new lambda { |e| [] }, :urls => [ '/' ], :root => '.'
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
@@ -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
@@ -49,6 +49,14 @@ module Strobe
49
49
  to_a.length
50
50
  end
51
51
 
52
+ def first
53
+ to_a.first
54
+ end
55
+
56
+ def last
57
+ to_a.last
58
+ end
59
+
52
60
  def new(params = {})
53
61
  klass.new(@params.merge(params))
54
62
  end
@@ -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" \
@@ -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 = 60
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
- headers['content-type'] ||= 'application/json'
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
- env['rack.input'] = StringIO.new(body.join)
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 = $1.split(':')
89
- path = $2 || '/'
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
- headers = headers.select { |k,v| !block_headers.include?(k.downcase) }
217
- headers = headers.merge(extra_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)
@@ -10,5 +10,6 @@ module Strobe
10
10
  require 'strobe/resources/team'
11
11
  require 'strobe/resources/user'
12
12
  require 'strobe/resources/deploy'
13
+ require 'strobe/resources/build'
13
14
  end
14
15
  end
@@ -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 = "?environment=#{environment}" if environment
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("#{http_uri}/deploy#{qs}", packfile, packfile.headers)
63
+ response = connection.put(uri, packfile, packfile.headers)
29
64
  callback.deploy_complete if callback
30
65
  response
31
66
  end
@@ -3,8 +3,8 @@ module Strobe
3
3
  class Assignment
4
4
  include Resource::Collection
5
5
 
6
- has 1, :team
7
- has 1, :application
6
+ has 1, :team, :include => true
7
+ has 1, :application, :include => true
8
8
 
9
9
  validates :team, :application, :presence => true
10
10
  end
@@ -0,0 +1,13 @@
1
+ module Strobe
2
+ module Resources
3
+ class Build
4
+ include Resource::Collection
5
+
6
+ key :id, String
7
+ key :download_uri, String
8
+ key :status, String
9
+
10
+ has 1, :deploy
11
+ end
12
+ end
13
+ end
@@ -1,14 +1,12 @@
1
- require 'zlib'
2
- require 'digest/sha1'
3
-
4
1
  module Strobe
5
2
  module Resources
6
3
  class Deploy
7
4
  include Resource::Collection
8
5
 
9
- key :id, String
6
+ key :id, String
10
7
 
11
8
  has 1, :application
9
+ has n, :builds
12
10
  end
13
11
  end
14
12
  end
@@ -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
- # @wrapped_app ||= Strobe::Middleware::Proxy.new(@app)
10
- @app
14
+ @wrapped_app ||= self.class.strobe_stack.wrap(@app)
11
15
  end
12
16
  end
@@ -0,0 +1,3 @@
1
+ module Strobe
2
+ VERSION = "0.2.0"
3
+ 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: 6
5
- version: 0.2.0.beta.2
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-07-18 00:00:00 +09:00
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.2.0
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.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