strobe 0.1.6 → 0.2.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,18 +8,25 @@ require 'active_support/core_ext/hash/indifferent_access'
8
8
  require 'active_support/core_ext/string/inflections'
9
9
 
10
10
  module Strobe
11
- autoload :Association, 'strobe/association'
12
- autoload :CLI, 'strobe/cli'
13
- autoload :Collection, 'strobe/collection'
14
- autoload :Connection, 'strobe/connection'
15
- autoload :IdentityMap, 'strobe/identity_map'
16
- autoload :Key, 'strobe/key'
17
- autoload :Resources, 'strobe/resources'
18
- autoload :Validations, 'strobe/validations'
11
+ autoload :Association, 'strobe/association'
12
+ autoload :CLI, 'strobe/cli'
13
+ autoload :Collection, 'strobe/collection'
14
+ autoload :Config, 'strobe/config'
15
+ autoload :Connection, 'strobe/connection'
16
+ autoload :IdentityMap, 'strobe/identity_map'
17
+ autoload :Key, 'strobe/key'
18
+ autoload :Resources, 'strobe/resources'
19
+ autoload :Validations, 'strobe/validations'
20
+ autoload :ExceptionNotifier, 'strobe/exception_notifier'
21
+
22
+ module Addons
23
+ # Stubb out the Addons module
24
+ end
19
25
 
20
26
  module Middleware
21
- autoload :Proxy, 'strobe/middleware/proxy'
22
- autoload :Rewrite, 'strobe/middleware/rewrite'
27
+ autoload :Addons, 'strobe/middleware/addons'
28
+ autoload :Proxy, 'strobe/middleware/proxy'
29
+ autoload :Rewrite, 'strobe/middleware/rewrite'
23
30
  end
24
31
 
25
32
  module Resource
@@ -30,12 +37,21 @@ module Strobe
30
37
 
31
38
 
32
39
  # Errors
33
- class StrobeError < StandardError ; end
34
- class RequestError < StrobeError ; end
35
- class ResourceNotFoundError < StrobeError ; end
36
- class ServerError < StrobeError ; end
37
- class UnauthenticatedError < StrobeError ; end
38
- class ValidationError < StrobeError ; end
40
+ class StrobeError < StandardError ; end
41
+ class ResourceNotFoundError < StrobeError ; end
42
+ class UnauthenticatedError < StrobeError ; end
43
+ class ValidationError < StrobeError ; end
44
+
45
+ class NotifiableError < StrobeError
46
+ attr_reader :data
47
+ def initialize(message = nil, data = nil)
48
+ super(message)
49
+ @data = data
50
+ end
51
+ end
52
+
53
+ class RequestError < NotifiableError ; end
54
+ class ServerError < NotifiableError ; end
39
55
 
40
56
  def self.connection
41
57
  @connection
@@ -0,0 +1,130 @@
1
+ require 'oauth'
2
+
3
+ module Strobe
4
+ module Addons
5
+ class Social
6
+ TWITTER_URL = "http://api.twitter.com"
7
+ REQUEST_TOKEN_SECRETS = {}
8
+ ACCESS_TOKEN_SECRETS = {}
9
+
10
+ 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)
25
+ end
26
+
27
+ 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
57
+
58
+ def get_access_token
59
+ response = Rack::Response.new
60
+
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
73
+ 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]
76
+ end
77
+ end
78
+
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
107
+
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"]
111
+ 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
+ end
128
+ end
129
+ end
130
+ end
@@ -24,9 +24,15 @@ module Strobe
24
24
  IdentityMap.wrap do
25
25
  super
26
26
  end
27
- rescue Strobe::StrobeError => e
27
+ rescue NotifiableError => e
28
28
  raise if $ARGV.include?('--backtrace')
29
- abort "[ERROR] #{e.message}"
29
+ STDERR.puts "[ERROR] #{e.message}"
30
+ ExceptionNotifier.notify(e, :action => args.join(" "))
31
+ abort
32
+ rescue StrobeError => e
33
+ raise if $ARGV.include?('--backtrace')
34
+ STDERR.puts "[ERROR] #{e.message}"
35
+ abort
30
36
  rescue Interrupt
31
37
  abort "\nQuitting..."
32
38
  end
@@ -34,7 +40,12 @@ module Strobe
34
40
  def self.action(name, *args, &blk)
35
41
  @haxliases ||= []
36
42
  @haxliases << name.to_s
37
- usage = self == Users ? "users #{name}" : name
43
+ options = args.last if args.last.is_a?(Hash)
44
+ usage = if options && options[:usage]
45
+ options[:usage]
46
+ else
47
+ self == Users ? "users #{name}" : name
48
+ end
38
49
  desc usage, *args
39
50
  define_method("__hax__#{name}", &blk)
40
51
  map name => "__hax__#{name}"
@@ -75,12 +86,13 @@ module Strobe
75
86
  options[:path] || Dir.pwd
76
87
  end
77
88
 
78
- def config(app = nil)
79
- if app
80
- @config = settings.configs.find { |c| c[:application_id] = app[:id] }
81
- end
89
+ def config
90
+ return @config if @config
91
+ @config = Config.new_or_stub(path) || Config.stub!(path)
92
+ end
82
93
 
83
- @config ||= Settings.application(path)
94
+ def settings
95
+ @settings ||= Settings.for(path)
84
96
  end
85
97
 
86
98
  def invoke(action, *args)
@@ -93,10 +105,6 @@ module Strobe
93
105
  @resource
94
106
  end
95
107
 
96
- def settings
97
- @settings ||= Settings.new
98
- end
99
-
100
108
  def until_success(&blk)
101
109
  until @success
102
110
  retval = blk.call
@@ -10,6 +10,30 @@ module Strobe
10
10
  end
11
11
  end
12
12
 
13
+ method_option "global", :type => :boolean, :banner => "Set option globally (for all applications)"
14
+ method_option "unset", :type => :boolean, :banner => "Unset a config option"
15
+ action "config", "configure strobe", :usage => "config [KEY] [VALUE]" do |key, val = nil|
16
+ case
17
+ when val
18
+ if options[:global]
19
+ settings.global_set! key, val
20
+ say "Globally setting `#{key}` to `#{val}`"
21
+ else
22
+ settings.set! key, val
23
+ say "Setting `#{key}` to `#{val}`"
24
+ end
25
+ when options[:unset]
26
+ settings.unset!(key)
27
+ say "Unsetting `#{key}`"
28
+ else
29
+ if val = settings[key]
30
+ say "#{key}: #{val}"
31
+ else
32
+ say "No value for `#{key}`"
33
+ end
34
+ end
35
+ end
36
+
13
37
  action "users", 'manage users' do |*args|
14
38
  argv = $ARGV[1..-1]
15
39
  argv = [ 'list' ] + argv if args.empty?
@@ -23,6 +47,20 @@ module Strobe
23
47
  Launchy.open("http://#{url}")
24
48
  end
25
49
 
50
+ action "deploys", "manage deploys" do
51
+ id = options['application-id'] || config[:application_id]
52
+ app = Application.new(:id => id)
53
+ deploys = app.deploys.all
54
+
55
+ table deploys do |t|
56
+ t.column :key do |d| d[:id][0..7] end
57
+ t.column :parent do |d| d[:parent] ? d[:parent][0..7] : "" end
58
+ t.column :name
59
+ t.column :email
60
+ t.column :message
61
+ end
62
+ end
63
+
26
64
  action "signup", "signup for a new Strobe account" do
27
65
  resource Signup.new :account => { :name => 'default' }
28
66
 
@@ -46,7 +84,7 @@ module Strobe
46
84
  end
47
85
 
48
86
  save do
49
- settings[:token] = resource['user.authentication_token']
87
+ settings.global_set! :token, resource['user.authentication_token']
50
88
  settings.authenticate!
51
89
  say "Your account was created successfully."
52
90
  end
@@ -63,7 +101,7 @@ module Strobe
63
101
  password :password, :into => "user.password"
64
102
 
65
103
  save do
66
- settings[:token] = resource['user.authentication_token']
104
+ settings.global_set! :token, resource['user.authentication_token']
67
105
  settings.authenticate!
68
106
  say "The computer has been registered with the account."
69
107
  end
@@ -109,8 +147,11 @@ module Strobe
109
147
  ask :application_name, :into => "name"
110
148
 
111
149
  save do
112
- config[:application_id] ||= resource[:id]
113
- settings.register_app_path(config.filename)
150
+ unless config[:application_id]
151
+ config.set_application_id! resource[:id]
152
+ end
153
+
154
+ settings.register_app_path(path)
114
155
  end
115
156
  end
116
157
 
@@ -159,7 +200,10 @@ module Strobe
159
200
  end
160
201
 
161
202
  delete do
162
- config(resource).delete
203
+ if config = settings.configs.find { |c| c[:application_id] == id.to_i }
204
+ config.unset_application_id!
205
+ settings.unregister_app_path config.application_path
206
+ end
163
207
  say "The application has been deleted"
164
208
  end
165
209
  end
@@ -169,16 +213,14 @@ module Strobe
169
213
  action "register", "registers a local application with an existing Strobe application" do
170
214
  if config[:application_id] && app = Application.get(config[:application_id])
171
215
  say "The directory is already registered with the application `#{app[:name]}`"
172
- unless agree "Continue? [Yn] "
173
- say "Aborting..."
174
- end
175
- config.delete
216
+ say "Aborting..."
217
+ exit 1
176
218
  end
177
219
 
178
220
  application = options['application-id'] ? Application.get!(options['application-id']) : pick_application
179
221
 
180
- config[:application_id] = application[:id]
181
- settings.register_app_path(config.filename)
222
+ config.set_application_id! application[:id]
223
+ settings.register_app_path(path)
182
224
 
183
225
  say "You can now deploy to #{application[:name]} (http://#{application[:url]})"
184
226
  end
@@ -18,8 +18,10 @@ module Strobe
18
18
 
19
19
  def preview_html_application
20
20
  app = Rack::Static.new lambda { |e| [] }, :urls => [ '/' ], :root => '.'
21
- app = Middleware::Rewrite.new(Middleware::Proxy.new(app))
22
- Server.start :app => app, :host => '0.0.0.0', :Port => 9292
21
+ app = Middleware::Rewrite.new(app)
22
+ app = Middleware::Addons.new(app, :settings => settings, :config => config)
23
+ app = Middleware::Proxy.new(app)
24
+ Server.start :app => app, :host => '0.0.0.0', :server => "thin", :Port => 9292
23
25
  end
24
26
 
25
27
  def preview_sproutcore_application
@@ -12,34 +12,71 @@ module Strobe
12
12
  Pathname.new(root).join(".strobe/#{file}")
13
13
  end
14
14
 
15
- def self.application(root)
16
- new application_config_file(root)
15
+ def self.for(path)
16
+ new(application_config_file(path), global_config_file)
17
+ end
18
+
19
+ def self.global
20
+ new(nil, global_config_file)
17
21
  end
18
22
 
19
23
  attr_reader :filename
20
24
 
21
- def initialize(filename = self.class.global_config_file)
22
- @filename = Pathname.new(filename)
25
+ def initialize(local_filename, global_filename)
26
+ @local_filename = Pathname.new(local_filename) if local_filename
27
+ @global_filename = Pathname.new(global_filename)
23
28
 
24
- if @filename.exist?
25
- @hash = YAML.load_file(@filename)
29
+ if @local_filename && @local_filename.exist?
30
+ @local_hash = YAML.load_file(@local_filename)
26
31
  end
27
32
 
28
- @hash = {} unless Hash === @hash
29
- end
33
+ @local_hash = {} unless Hash === @local_hash
34
+
35
+ if @global_filename.exist?
36
+ @global_hash = YAML.load_file(@global_filename)
37
+ end
30
38
 
31
- def application_path
32
- filename.sub %r[/.strobe/config(\.[a-z]+)?$]i, ''
39
+ @global_hash = {} unless Hash === @global_hash
33
40
  end
34
41
 
42
+ # def application_path
43
+ # filename.sub %r[/.strobe/config(\.[a-z]+)?$]i, ''
44
+ # end
45
+
35
46
  def [](k)
36
- ENV[key(k)] || @hash[key(k)]
47
+ k = key(k)
48
+ ENV[k] || @local_hash[k] || @global_hash[k]
49
+ end
50
+
51
+ def fetch(k, v)
52
+ self[k] || v
53
+ end
54
+
55
+ def set!(k, v)
56
+ @local_hash[key(k)] = v
57
+ persist! @local_hash, @local_filename
58
+ v
37
59
  end
38
60
 
39
- def []=(k, val)
40
- @hash[key(k)] = val
41
- persist!
42
- val
61
+ def global_set!(k, v)
62
+ @global_hash[key(k)] = v
63
+ persist! @global_hash, @global_filename
64
+ v
65
+ end
66
+
67
+ def unset!(k)
68
+ k = key(k)
69
+ if @local_hash[k]
70
+ @local_hash.delete(k)
71
+ persist! @local_hash, @local_filename
72
+ end
73
+
74
+ if @global_hash[k]
75
+ @global_hash.delete(k)
76
+ persist! @global_hash, @global_filename
77
+ end
78
+
79
+ nil
43
80
  end
44
81
 
45
82
  def connection
@@ -59,38 +96,47 @@ module Strobe
59
96
  end
60
97
  end
61
98
 
62
- def delete
63
- FileUtils.rm_f(@filename)
64
- end
65
-
66
99
  def configs
67
100
  @configs ||= begin
68
- self[:applications] ||= []
69
- self[:applications].map do |path|
70
- next unless File.file?(path)
71
- self.class.new(path)
72
- end.compact
101
+ application_paths.map do |path|
102
+ Config.new_or_stub(path)
103
+ end
73
104
  end
74
105
  end
75
106
 
76
- def register_app_path(path)
77
- self[:applications] ||= []
78
- self[:applications] |= [File.expand_path(path.to_s)]
79
- self[:applications] = self[:applications].select do |path|
80
- File.file?(path)
107
+ def application_paths
108
+ paths = self[:applications] || []
109
+
110
+ # Handle legacy application paths
111
+ # TODO: When it's safe, remove this code
112
+ paths.map! do |p|
113
+ if File.file?(p) && p =~ /\.strobe/
114
+ File.expand_path('../..', p)
115
+ else
116
+ p
117
+ end
118
+ end
119
+
120
+ # TODO: Handle the logacy case where applications points
121
+ # to a .strobe/config file
122
+ paths.delete_if do |path|
123
+ !(File.file?("#{path}/.strobe/config") ||
124
+ File.file?("#{path}/strobe/config.json"))
81
125
  end
82
126
 
83
- persist!
127
+ paths
84
128
  end
85
129
 
86
- def unregister_app_path(path)
87
- self[:applications] ||= []
88
- self[:applications].delete File.expand_path(path.to_s)
89
- self[:applications] = self[:applications].select do |path|
90
- File.file?(path)
91
- end
130
+ def register_app_path(path)
131
+ paths = application_paths
132
+ paths |= [File.expand_path(path.to_s)]
133
+ global_set! :applications, paths
134
+ end
92
135
 
93
- persist!
136
+ def unregister_app_path(path)
137
+ paths = application_paths
138
+ paths.delete File.expand_path(path.to_s)
139
+ global_set! :applications, paths
94
140
  end
95
141
 
96
142
  private
@@ -99,11 +145,18 @@ module Strobe
99
145
  "STROBE_#{key}".upcase
100
146
  end
101
147
 
102
- def persist!
103
- # Persist the setting
104
- FileUtils.mkdir_p(@filename.dirname)
105
- File.open(@filename, "wb") do |file|
106
- file.puts @hash.to_yaml
148
+ def persist!(hash, filename)
149
+ FileUtils.mkdir_p(filename.dirname)
150
+ File.open(filename, "wb") do |file|
151
+ file.puts hash.to_yaml
152
+ end
153
+ end
154
+
155
+ def check_app_paths
156
+ self[:applications] ||= []
157
+ self[:applications] = self[:applications].select do |path|
158
+ File.file?("#{path}/.strobe") ||
159
+ File.file?("#{path}/strobe/config.json")
107
160
  end
108
161
  end
109
162
  end