strobe 0.0.2.1 → 0.1.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/strobe CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'strobe'
3
3
 
4
- Strobe::CLI.start
4
+ Strobe::CLI::Main.start(ARGV)
@@ -43,7 +43,7 @@ module Strobe
43
43
 
44
44
  ruby << " return @__#{name} if @__#{name}"
45
45
  ruby << " return nil unless self[:#{name}_id]"
46
- ruby << " @__#{name} = #{target_name}.get( self[:#{name}_id] )"
46
+ ruby << " @__#{name} = #{target_name}.get!( self[:#{name}_id] )"
47
47
 
48
48
  end
49
49
 
@@ -80,6 +80,7 @@ module Strobe
80
80
  end
81
81
 
82
82
  ruby << "end"
83
+
83
84
  ruby.join("\n")
84
85
  end
85
86
  end
@@ -0,0 +1,167 @@
1
+ module Strobe
2
+ class CLI::Main < CLI
3
+
4
+ action "users", 'manage users' do |*args|
5
+ argv = $ARGV[1..-1]
6
+ argv = [ 'list' ] + argv if args.empty?
7
+ CLI::Users.start(argv)
8
+ end
9
+
10
+ action "signup", "signup for a new Strobe account" do
11
+ resource Signup.new :account => { :name => 'default' }
12
+
13
+ if settings[:token]
14
+ say "This computer is already registered with a Strobe account."
15
+ unless agree "Signup anyway? [Yn] "
16
+ say "exiting..."
17
+ exit
18
+ end
19
+ end
20
+
21
+ until_success do
22
+ say "Please fill out the following information."
23
+
24
+ ask :invitation
25
+ ask :email, :into => "user.email"
26
+
27
+ group :validate => "user.password" do
28
+ password :password, :into => "user.password"
29
+ password :password_confirmation, :into => "password_confirmation"
30
+ end
31
+
32
+ save do
33
+ settings[:token] = resource['user.authentication_token']
34
+ settings.authenticate!
35
+ say "Your account was created successfully."
36
+ end
37
+ end
38
+ end
39
+
40
+ action "login", "register this computer with an existing Strobe account" do
41
+ resource Me.new
42
+
43
+ until_success do
44
+ say "Please enter your Strobe username and password."
45
+
46
+ ask :email, :into => "user.email"
47
+ password :password, :into => "user.password"
48
+
49
+ save do
50
+ settings[:token] = resource['user.authentication_token']
51
+ settings.authenticate!
52
+ say "The computer has been registered with the account."
53
+ end
54
+ end
55
+ end
56
+
57
+ application_path_option
58
+ method_option :staging, :type => :boolean, :banner => "deploy to a staging environment"
59
+ action "deploy", "deploy your application to Strobe" do
60
+ ensure_computer_is_registered
61
+
62
+ if application_id = config[:application_id]
63
+ resource Application.get! application_id, :lazy => true
64
+ else
65
+ resource Account.first.applications.new
66
+ end
67
+
68
+ resource[:path] = determine_application_root
69
+
70
+ unless resource.persisted?
71
+ say "Please fill out the following information."
72
+
73
+ ask :name
74
+
75
+ save do
76
+ config[:application_id] ||= resource[:id]
77
+ end
78
+ end
79
+
80
+ host = resource.deploy! :environment => options[:staging] && 'staging'
81
+ say "The application has successfully been deployed and is available at #{host}"
82
+ end
83
+
84
+ action "applications", "list all of your applications" do
85
+ empty = "You do not have any applications. Try deploying one with `strobe deploy`."
86
+ table Resources::Application.all, :empty => empty do |t|
87
+ t.column :id
88
+ t.column :name
89
+ t.column :url
90
+ end
91
+ end
92
+
93
+ method_option "url", :type => :string
94
+ application_path_option
95
+ action "set", "update the settings for the application" do
96
+ resource get_application
97
+ resource['url'] = options['url']
98
+
99
+ save do
100
+ say "The application is now running at #{resource[:url]}"
101
+ end
102
+ end
103
+
104
+ application_path_option
105
+ action "delete", "delete a specific application" do
106
+ resource get_application
107
+
108
+ say "Deleting application '#{resource[:name]}'"
109
+
110
+ unless agree "Are you sure? [Yn] "
111
+ say "Aborting..."
112
+ exit
113
+ end
114
+
115
+ delete do
116
+ config.delete
117
+ say "The application has been deleted"
118
+ end
119
+ end
120
+
121
+ application_path_option
122
+ method_option "id", :type => :numeric, :banner => "The ID of the remote application"
123
+ action "register", "registers a local application with an existing Strobe application" do
124
+ if config[:application_id] && app = Application.get(config[:application_id])
125
+ say "The directory is already registered with the application `#{app[:name]}`"
126
+ unless agree "Continue? [Yn] "
127
+ say "Aborting..."
128
+ end
129
+ config.delete
130
+ end
131
+
132
+ application = Application.get! options[:id]
133
+ config[:application_id] = application[:id]
134
+ say "You can now deploy to #{application[:name]} (http://#{application[:url]})"
135
+ end
136
+
137
+ private
138
+
139
+ def determine_application_root
140
+ if File.exist?("#{path}/tmp/build")
141
+ return "#{path}/tmp/build"
142
+ end
143
+
144
+ path
145
+ end
146
+
147
+ def ensure_computer_is_registered
148
+ unless settings[:token]
149
+ say "This computer is not yet registered with a Strobe account."
150
+
151
+ choose do |m|
152
+ m.choice(:login) { invoke :login }
153
+ m.choice(:signup) { invoke :signup }
154
+ end
155
+ end
156
+ end
157
+
158
+ def get_application(opts = {})
159
+ unless id = config[:application_id]
160
+ error "No application found. Are you currently in the correct directory?"
161
+ exit 1
162
+ end
163
+
164
+ Application.get!(id, opts)
165
+ end
166
+ end
167
+ end
@@ -12,11 +12,15 @@ 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)
17
+ end
18
+
15
19
  def initialize(filename = self.class.global_config_file)
16
20
  @filename = filename
17
21
 
18
22
  if @filename.exist?
19
- @hash = YAML.load(@filename)
23
+ @hash = YAML.load(@filename) || {}
20
24
  else
21
25
  @hash = {}
22
26
  end
@@ -55,6 +59,10 @@ module Strobe
55
59
  end
56
60
  end
57
61
 
62
+ def delete
63
+ FileUtils.rm_f(@filename)
64
+ end
65
+
58
66
  private
59
67
 
60
68
  def key(key)
@@ -0,0 +1,51 @@
1
+ module Strobe
2
+ class CLI::Table
3
+ def initialize(collection, opts = {})
4
+ @collection = collection
5
+ @columns = []
6
+ @computed = []
7
+ @opts = opts
8
+ end
9
+
10
+ def column(key)
11
+ @columns << { :key => key, :max => 0 }
12
+ end
13
+
14
+ def to_s
15
+ @collection.each do |obj|
16
+ @computed << @columns.map do |col|
17
+ cell = obj[ col[:key] ].to_s
18
+ col[:max] = [ cell.length, col[:max] ].max
19
+ cell
20
+ end
21
+ end
22
+
23
+ if @computed.empty?
24
+ return @opts[:empty] || "Nothing to see here..."
25
+ end
26
+
27
+ str = ""
28
+
29
+ @columns.each do |col|
30
+ str << cell_to_s(col, col[:key].to_s.upcase)
31
+ end
32
+
33
+ str << "\n"
34
+
35
+ @computed.each do |cells|
36
+ cells.each_with_index do |cell, i|
37
+ str << cell_to_s(@columns[i], cell)
38
+ end
39
+ str << "\n"
40
+ end
41
+ str
42
+ end
43
+
44
+ private
45
+
46
+ def cell_to_s(col, cell)
47
+ width = [ col[:max], 25 ].max + 5
48
+ cell.ljust(width)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,65 @@
1
+ module Strobe
2
+ class CLI::Users < CLI
3
+
4
+ application_path_option
5
+ action "list", "list users that have access to this application" do
6
+ @team = get_current_team!
7
+ if @team.memberships.any?
8
+ say "The following users also have access to the application"
9
+ say
10
+
11
+ @team.memberships.each do |m|
12
+ email = m.user ? m.user[:email] : m[:email]
13
+ say " * #{email}"
14
+ end
15
+ else
16
+ say "You are the only user with access to this application"
17
+ end
18
+ end
19
+
20
+ application_path_option
21
+ action "add", "give a user access to this application" do |email|
22
+ @team = get_current_team!
23
+ @team.memberships.create! :email => email
24
+
25
+ say "`#{email}` has been invited to the application"
26
+ end
27
+
28
+ application_path_option
29
+ action "remove", "remove a user's access from this application" do |email|
30
+ @team = get_current_team!
31
+ @team.memberships.each do |m|
32
+ m.destroy if (m.user ? m.user[:email] : m[:email]) == email
33
+ end
34
+
35
+ say "`#{email}` has been removed from the application"
36
+ end
37
+
38
+ private
39
+
40
+ def get_current_team!
41
+ if team = current_application.teams.first
42
+ team
43
+ else
44
+ # Create a team for the application
45
+ account = current_application.account
46
+ name = "Developers for #{current_application[:name]}"
47
+ team = Team.create! :name => name, :account => account
48
+
49
+ team.assignments.create! :application => current_application
50
+ team
51
+ end
52
+ end
53
+
54
+ def current_application
55
+ return @current_application if @current_application
56
+
57
+ unless id = config[:application_id]
58
+ error "No application found. Are you currently in the correct directory?"
59
+ exit 1
60
+ end
61
+
62
+ @current_application = Application.get!(config[:application_id])
63
+ end
64
+ end
65
+ end
data/lib/strobe/cli.rb CHANGED
@@ -1,65 +1,201 @@
1
1
  require 'thor'
2
2
  require 'pathname'
3
3
  require 'fileutils'
4
+ require 'highline'
4
5
 
5
6
  module Strobe
6
7
  class CLI < Thor
7
- autoload :Action, 'strobe/cli/action'
8
- autoload :Settings, 'strobe/cli/settings'
8
+ autoload :Main, 'strobe/cli/main'
9
+ autoload :Settings, 'strobe/cli/settings'
10
+ autoload :Table, 'strobe/cli/table'
11
+ autoload :Users, 'strobe/cli/users'
9
12
 
10
- def self.start(*)
11
- super
13
+ include Resources
14
+
15
+ class_option "backtrace", :type => :boolean
16
+
17
+ def self.start(args)
18
+ $ARGV = args
19
+ IdentityMap.wrap do
20
+ super
21
+ end
12
22
  rescue Strobe::StrobeError => e
13
- raise if $STROBE_BACKTRACE
23
+ raise if $ARGV.include?('--backtrace')
14
24
  abort "[ERROR] #{e.message}"
15
25
  end
16
26
 
17
- class_option "backtrace", :type => :boolean, :banner => "Show a backtrace in case of an exception"
27
+ def self.action(name, *args, &blk)
28
+ @haxliases ||= []
29
+ @haxliases << name.to_s
30
+ desc name, *args
31
+ define_method("__hax__#{name}", &blk)
32
+ map name => "__hax__#{name}"
33
+ end
34
+
35
+ def self.__haxlias(name)
36
+ @haxliases ||= []
37
+
38
+ return name if Class === name
39
+
40
+ if @haxliases.include?(name.to_s)
41
+ "__hax__#{name}"
42
+ else
43
+ name
44
+ end
45
+ end
46
+
47
+ def self.application_path_option
48
+ method_option :path, :type => :string, :banner => "Path to your application"
49
+ end
18
50
 
19
51
  def initialize(*)
20
52
  super
21
53
 
22
- $STROBE_BACKTRACE = true if options[:backtrace]
23
- _settings.connect!
54
+ @resource = nil
55
+ @highline = HighLine.new
56
+
57
+ # State
58
+ @in_group = false
59
+ @success = false
60
+
61
+ settings.connect!
24
62
  end
25
63
 
26
- # Load the CLI actions
27
- require "strobe/cli/actions"
64
+ private
28
65
 
29
- desc "signup", "signup for a new Strobe account"
30
- def signup
31
- _run :signup
66
+ def path
67
+ options[:path] || Dir.pwd
32
68
  end
33
69
 
34
- desc "login", "register this computer with an existing Strobe account"
35
- def login
36
- _run :login
70
+ def config
71
+ @config ||= Settings.application(path)
37
72
  end
38
73
 
39
- desc "deploy", "deploy your application to Strobe"
40
- method_option "path", :type => :string, :banner => "The path to the application"
41
- def deploy
42
- path = options[:path] || Dir.pwd
43
- file = ENV['STROBE_CONFIG'] || 'config'
44
- config = Settings.new(Pathname.new(path).join(".strobe/#{file}"))
74
+ def invoke(action, *args)
75
+ action = self.class.__haxlias(action)
76
+ super
77
+ end
45
78
 
46
- _run :deploy, :config => config, :path => path
79
+ def resource(resource = nil)
80
+ @resource = resource if resource
81
+ @resource
47
82
  end
48
83
 
49
- private
84
+ def settings
85
+ @settings ||= Settings.new
86
+ end
50
87
 
51
- def _init_application
52
- path = options[:path]
53
- dir = Pathname.new(path).join(".strobe")
54
- FileUtils.mkdir_p dir
88
+ def until_success(&blk)
89
+ until @success
90
+ retval = blk.call
91
+ end
92
+ @success = false
93
+ retval
55
94
  end
56
95
 
57
- def _run(name, opts = {})
58
- Action.run name, _settings, opts
96
+ def save(opts = {})
97
+ if resource.save
98
+ @success = true
99
+ yield
100
+ else
101
+ display_errors
102
+ end
59
103
  end
60
104
 
61
- def _settings
62
- @settings ||= Settings.new
105
+ def delete(opts = {})
106
+ if resource.destroy
107
+ @success = true
108
+ yield
109
+ else
110
+ display_errors
111
+ end
112
+ end
113
+
114
+ def say(what = nil)
115
+ puts what
116
+ end
117
+
118
+ def error(what)
119
+ STDERR.puts "[ERROR] #{what}"
120
+ end
121
+
122
+ def agree(msg, *args)
123
+ @highline.agree(msg, *args) do |q|
124
+ next unless STDIN.tty? # For testing
125
+ q.readline = true
126
+ end
127
+ end
128
+
129
+ def ask(key, options = {})
130
+ input key, options do |msg|
131
+ resp = @highline.ask msg do |q|
132
+ next unless STDIN.tty? # For testing
133
+ q.readline = true
134
+ end
135
+ end
136
+ end
137
+
138
+ def password(key, options = {})
139
+ input key, options do |msg|
140
+ @highline.ask msg do |q|
141
+ next unless STDIN.tty? # For testing
142
+ q.echo = '*'
143
+ end
144
+ end
145
+ end
146
+
147
+ def choose
148
+ @highline.choose do |menu|
149
+ yield menu
150
+ end
151
+ end
152
+
153
+ def group(options = {}, &blk)
154
+ case
155
+ when key = options[:validate]
156
+ validation_group(key, &blk)
157
+ else
158
+ yield if block_given?
159
+ end
160
+ end
161
+
162
+ def input(key, options)
163
+ into = options[:into] || key
164
+
165
+ validation_group into do
166
+ msg = (key.to_s.humanize + ': ').ljust(25)
167
+ resource[into] = yield msg
168
+ end
169
+ end
170
+
171
+ def validation_group(resource_key)
172
+ return yield if @in_group
173
+
174
+ begin
175
+ @in_group = true
176
+ while true
177
+ yield
178
+
179
+ break if resource.valid_attribute?(resource_key)
180
+
181
+ display_errors
182
+ end
183
+ ensure
184
+ @in_group = false
185
+ end
186
+ end
187
+
188
+ def display_errors
189
+ resource.errors.each do |k, msg|
190
+ key = k.to_s.split('.').last
191
+ say "[ERROR] #{key.humanize} #{msg}"
192
+ end
193
+ end
194
+
195
+ def table(collection, opts = {})
196
+ table = Table.new(collection, opts)
197
+ yield table
198
+ puts table.to_s
63
199
  end
64
200
  end
65
201
  end
@@ -11,6 +11,7 @@ module Strobe
11
11
  def to_a
12
12
  @to_a ||= begin
13
13
  resp = Strobe.connection.get klass.resource_uri, params
14
+ resp.validate!
14
15
 
15
16
  if resp.status == 200
16
17
  resp.body[klass.resource_name].map do |hash|
@@ -42,6 +43,14 @@ module Strobe
42
43
  klass.new(@params.merge(params))
43
44
  end
44
45
 
46
+ def create(params = {})
47
+ klass.create(@params.merge(params))
48
+ end
49
+
50
+ def create!(params = {})
51
+ klass.create!(@params.merge(params))
52
+ end
53
+
45
54
  def ==(other)
46
55
  if Array === other
47
56
  to_a === other
@@ -49,5 +58,9 @@ module Strobe
49
58
  super
50
59
  end
51
60
  end
61
+
62
+ def reload!
63
+ @to_a = nil
64
+ end
52
65
  end
53
66
  end
@@ -71,12 +71,15 @@ module Strobe
71
71
  end
72
72
 
73
73
  def validate!
74
+ msg = body['errors'] && body['errors']['request']
74
75
  case status
75
76
  when 401
76
77
  # TODO Fix this on the server
77
- raise UnauthenticatedError, body['errors']
78
+ raise UnauthenticatedError, msg
78
79
  when 404
79
- raise ResourceNotFoundError, body['errors']
80
+ raise ResourceNotFoundError, msg
81
+ when 500...600
82
+ raise ServerError, "The server puked :(. Don't worry, the error has been reported."
80
83
  end
81
84
 
82
85
  self
@@ -119,7 +122,7 @@ module Strobe
119
122
  request = Net::HTTPGenericRequest.new(
120
123
  method.to_s.upcase, !!body, true, path, headers)
121
124
 
122
- Response.new(http.request(request, body)).validate!
125
+ Response.new(http.request(request, body))
123
126
  end
124
127
 
125
128
  private
@@ -0,0 +1,48 @@
1
+ module Strobe
2
+ class IdentityMap
3
+ def self.wrap
4
+ Thread.current[:__strobe_im] = new
5
+ yield
6
+ ensure
7
+ Thread.current[:__strobe_im] = nil
8
+ end
9
+
10
+ def self.identify(klass, key)
11
+ return yield unless current && key
12
+
13
+ inst = current[klass, key]
14
+
15
+ return inst if inst
16
+
17
+ current[klass, key] = yield if block_given?
18
+ end
19
+
20
+ def self.forget(klass, key)
21
+ current && key && current[klass, key] = nil
22
+ end
23
+
24
+ def self.reset!
25
+ current && current.reset!
26
+ end
27
+
28
+ def self.current
29
+ Thread.current[:__strobe_im]
30
+ end
31
+
32
+ def initialize
33
+ reset!
34
+ end
35
+
36
+ def [](klass, key)
37
+ @map[klass.to_s][klass.key.typecast(key)]
38
+ end
39
+
40
+ def []=(klass, key, val)
41
+ @map[klass.to_s][klass.key.typecast(key)] = val
42
+ end
43
+
44
+ def reset!
45
+ @map = Hash.new { |h,k| h[k] = {} }
46
+ end
47
+ end
48
+ end
data/lib/strobe/key.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Strobe
2
+ class Key
3
+ attr_reader :property, :type
4
+
5
+ def initialize(property, type)
6
+ @property = property.to_s
7
+ @type = type
8
+ end
9
+
10
+ def key_for(obj)
11
+ typecast(obj[property])
12
+ end
13
+
14
+ def typecast(val)
15
+ return if val.nil?
16
+ return val.to_s if type == String
17
+ return val.to_i if type == Integer
18
+ raise "Unknown key type `#{type}`"
19
+ end
20
+ end
21
+ end
@@ -1,4 +1,7 @@
1
1
  module Strobe
2
+ class RequestError < RuntimeError ; end
3
+ class ValidationError < RuntimeError ; end
4
+
2
5
  module Resource
3
6
  class AssociationValidator < ActiveModel::EachValidator
4
7
  def validate_each(record, attr, val)
@@ -71,6 +74,7 @@ module Strobe
71
74
 
72
75
  def initialize(params = {})
73
76
  @response = nil
77
+ @params = with_indifferent_access({})
74
78
  self.params = params || {}
75
79
  end
76
80
 
@@ -93,13 +97,8 @@ module Strobe
93
97
  end
94
98
 
95
99
  def params=(val)
96
- params = with_indifferent_access(val)
97
-
98
- extract_on_associations(params).each do |k, v|
99
- send("#{k}=", v)
100
- end
101
-
102
- @params = params
100
+ @params = with_indifferent_access({})
101
+ merge!(val)
103
102
  val
104
103
  end
105
104
 
@@ -131,10 +130,13 @@ module Strobe
131
130
  params = with_indifferent_access(params || {})
132
131
 
133
132
  extract_on_associations(params).each do |k, v|
134
- unless inst = send(k)
135
- inst = send("#{k}=", {})
133
+ if Base === v
134
+ send("#{k}=", v)
135
+ elsif inst = send(k)
136
+ inst.merge!(v)
137
+ else
138
+ send("#{k}=", v)
136
139
  end
137
- inst.merge! v
138
140
  end
139
141
 
140
142
  @params.deep_merge! params
@@ -150,8 +152,15 @@ module Strobe
150
152
  end
151
153
  end
152
154
 
155
+ def save!
156
+ save or raise ValidationError, "could not validate: #{errors.full_messages}"
157
+ end
158
+
153
159
  def destroy
154
- raise NotImplemented
160
+ if request { delete }
161
+ IdentityMap.forget(self.class, key)
162
+ true
163
+ end
155
164
  end
156
165
 
157
166
  private
@@ -174,11 +183,24 @@ module Strobe
174
183
  @response = yield
175
184
  root = self.class.singular_resource_name
176
185
 
186
+ @response.validate!
187
+
177
188
  if @response.success?
178
189
  merge! @response.body[root]
190
+
191
+ # register with the IM
192
+ IdentityMap.identify(self.class, self[:id]) do
193
+ self
194
+ end
195
+
179
196
  return true
180
197
  elsif @response.status == 422
181
198
  errors = @response.body['errors']
199
+
200
+ if msg = errors['request']
201
+ raise RequestError, msg
202
+ end
203
+
182
204
  errors = errors[root] if errors
183
205
  handle_errors(errors) if errors
184
206
  else
@@ -201,6 +223,10 @@ module Strobe
201
223
  connection.put http_uri, request_params, {}
202
224
  end
203
225
 
226
+ def delete
227
+ connection.delete http_uri
228
+ end
229
+
204
230
  def handle_errors(errors, key = nil)
205
231
  errors.each do |k, v|
206
232
  key = key ? "#{key}.#{k}" : k
@@ -7,6 +7,14 @@ module Strobe
7
7
  module ClassMethods
8
8
  include Enumerable
9
9
 
10
+ def key(property = nil, type = Integer)
11
+ @key ||= Key.new(:id, Integer)
12
+
13
+ return @key unless property
14
+
15
+ @key = Key.new(property, type)
16
+ end
17
+
10
18
  def each
11
19
  collection.each { |r| yield r }
12
20
  end
@@ -29,21 +37,63 @@ module Strobe
29
37
 
30
38
  alias all collection
31
39
 
32
- def get(id, opts = {})
33
- inst = new :id => id
34
- inst.reload unless opts[:lazy]
40
+ def new(params = {})
41
+ inst = IdentityMap.identify(self, key.key_for(params)) do
42
+ super({})
43
+ end
44
+ inst.merge!(params)
35
45
  inst
36
46
  end
47
+
48
+ def create(params = {})
49
+ new(params).tap { |inst| inst.save }
50
+ end
51
+
52
+ def create!(params = {})
53
+ new(params).tap { |inst| inst.save! }
54
+ end
55
+
56
+ def get(id, opts = {})
57
+ return nil unless id
58
+
59
+ IdentityMap.identify self, id do
60
+ inst = new :id => id
61
+ break inst if opts[:lazy]
62
+ inst.reload(opts) && inst
63
+ end
64
+ end
65
+
66
+ def get!(id, opts = {})
67
+ raise ResourceNotFoundError, "can not find resource will nil key" unless id
68
+
69
+ IdentityMap.identify self, id do
70
+ inst = new :id => id
71
+ inst.reload!
72
+ inst
73
+ end
74
+ end
37
75
  end
38
76
 
39
77
  def id
40
- self[:id]
78
+ raise NotImplementedError
41
79
  end
42
80
 
43
- def reload
44
- resp = Strobe.connection.get "#{self.class.resource_uri}/#{id}"
45
- self.params = resp.body[self.class.singular_resource_name]
46
- self
81
+ def key
82
+ self.class.key.key_for(self)
83
+ end
84
+
85
+ def reload!(opts = {})
86
+ reload(opts.merge(:validate => true))
87
+ end
88
+
89
+ def reload(opts = {})
90
+ resp = Strobe.connection.get "#{self.class.resource_uri}/#{key}"
91
+ resp.validate! if opts[:validate]
92
+
93
+ if resp.success?
94
+ self.params = resp.body[self.class.singular_resource_name]
95
+ self
96
+ end
47
97
  end
48
98
  end
49
99
  end
@@ -3,8 +3,9 @@ module Strobe
3
3
  class Account
4
4
  include Resource::Collection
5
5
 
6
- has n, :users
7
6
  has n, :applications
7
+ has n, :users
8
+ has n, :teams
8
9
 
9
10
  validates "name", :presence => true
10
11
  end
@@ -12,17 +12,21 @@ module Strobe
12
12
 
13
13
  filter :path
14
14
 
15
- validates "account", "name", "url", :presence => true
15
+ validates "account", "name", :presence => true
16
16
 
17
- def deploy!(path = nil)
18
- self['path'] = path if path
17
+ def deploy!(opts = {})
18
+ self['path'] = opts[:path] if opts[:path]
19
+ environment = opts[:environment]
19
20
 
20
21
  validate_for_deploy or return
21
22
 
22
23
  request do
24
+ qs = "?environment=#{environment}" if environment
23
25
  packfile = build_packfile
24
- connection.put("#{http_uri}/deploy", packfile.to_s, packfile.headers)
26
+ connection.put("#{http_uri}/deploy#{qs}", packfile.to_s, packfile.headers)
25
27
  end
28
+
29
+ [ environment, self['url'] ].compact.join('.')
26
30
  end
27
31
 
28
32
  private
@@ -0,0 +1,12 @@
1
+ module Strobe
2
+ module Resources
3
+ class Assignment
4
+ include Resource::Collection
5
+
6
+ has 1, :team
7
+ has 1, :application
8
+
9
+ validates :team, :application, :presence => true
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module Strobe
2
+ module Resources
3
+ class Membership
4
+ include Resource::Collection
5
+
6
+ has 1, :team
7
+ has 1, :user
8
+
9
+ validates :team, :presence => true
10
+ validates :email, :presence => true, :email => true
11
+ end
12
+ end
13
+ end
@@ -6,8 +6,10 @@ module Strobe
6
6
  has 1, :account
7
7
  has n, :users
8
8
  has n, :applications
9
+ has n, :assignments
10
+ has n, :memberships
9
11
 
10
- validates :name, :presence => true
12
+ validates :account, :name, :presence => true
11
13
  end
12
14
  end
13
15
  end
@@ -3,6 +3,8 @@ module Strobe
3
3
  class User
4
4
  include Resource::Collection
5
5
 
6
+ key :email, String
7
+
6
8
  has n, :accounts
7
9
 
8
10
  validates "email", :presence => true, :email => true
@@ -67,11 +67,15 @@ module Strobe
67
67
  def valid_for_given_attributes?
68
68
  errors.clear
69
69
 
70
- ran = []
70
+ ran = []
71
+ keys = []
71
72
 
72
73
  _validators.each do |attr, validators|
73
74
  validators.each do |v|
74
75
  next if !key?(attr) && v.skippable?
76
+
77
+ keys |= [attr.to_s]
78
+
75
79
  next if ran.include?(v)
76
80
 
77
81
  v.validate(self)
@@ -79,6 +83,10 @@ module Strobe
79
83
  end
80
84
  end
81
85
 
86
+ errors.keys.each do |k|
87
+ errors.delete(k) unless keys.include?(k.to_s)
88
+ end
89
+
82
90
  errors.empty?
83
91
  end
84
92
 
data/lib/strobe.rb CHANGED
@@ -12,6 +12,8 @@ module Strobe
12
12
  autoload :CLI, 'strobe/cli'
13
13
  autoload :Collection, 'strobe/collection'
14
14
  autoload :Connection, 'strobe/connection'
15
+ autoload :IdentityMap, 'strobe/identity_map'
16
+ autoload :Key, 'strobe/key'
15
17
  autoload :Validations, 'strobe/validations'
16
18
 
17
19
  module Resource
@@ -24,7 +26,9 @@ module Strobe
24
26
  module Resources
25
27
  autoload :Account, 'strobe/resources/account'
26
28
  autoload :Application, 'strobe/resources/application'
29
+ autoload :Assignment, 'strobe/resources/assignment'
27
30
  autoload :Me, 'strobe/resources/me'
31
+ autoload :Membership, 'strobe/resources/membership'
28
32
  autoload :Signup, 'strobe/resources/signup'
29
33
  autoload :Team, 'strobe/resources/team'
30
34
  autoload :User, 'strobe/resources/user'
@@ -34,6 +38,7 @@ module Strobe
34
38
  class StrobeError < StandardError ; end
35
39
  class UnauthenticatedError < StrobeError ; end
36
40
  class ResourceNotFoundError < StrobeError ; end
41
+ class ServerError < StrobeError ; end
37
42
 
38
43
  def self.connection
39
44
  @connection
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strobe
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ prerelease: true
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 2
9
+ - beta
9
10
  - 1
10
- version: 0.0.2.1
11
+ version: 0.1.0.beta.1
11
12
  platform: ruby
12
13
  authors:
13
14
  - Yehuda Katz
@@ -16,7 +17,7 @@ autorequire:
16
17
  bindir: bin
17
18
  cert_chain: []
18
19
 
19
- date: 2010-12-15 00:00:00 -08:00
20
+ date: 2011-01-02 00:00:00 -08:00
20
21
  default_executable:
21
22
  dependencies:
22
23
  - !ruby/object:Gem::Dependency
@@ -106,18 +107,23 @@ extra_rdoc_files: []
106
107
 
107
108
  files:
108
109
  - lib/strobe/association.rb
109
- - lib/strobe/cli/action.rb
110
- - lib/strobe/cli/actions.rb
110
+ - lib/strobe/cli/main.rb
111
111
  - lib/strobe/cli/settings.rb
112
+ - lib/strobe/cli/table.rb
113
+ - lib/strobe/cli/users.rb
112
114
  - lib/strobe/cli.rb
113
115
  - lib/strobe/collection.rb
114
116
  - lib/strobe/connection.rb
117
+ - lib/strobe/identity_map.rb
118
+ - lib/strobe/key.rb
115
119
  - lib/strobe/resource/base.rb
116
120
  - lib/strobe/resource/collection.rb
117
121
  - lib/strobe/resource/singleton.rb
118
122
  - lib/strobe/resources/account.rb
119
123
  - lib/strobe/resources/application.rb
124
+ - lib/strobe/resources/assignment.rb
120
125
  - lib/strobe/resources/me.rb
126
+ - lib/strobe/resources/membership.rb
121
127
  - lib/strobe/resources/signup.rb
122
128
  - lib/strobe/resources/team.rb
123
129
  - lib/strobe/resources/user.rb
@@ -1,131 +0,0 @@
1
- require 'highline'
2
-
3
- module Strobe
4
- class CLI::Action
5
- def self.run(action, settings, options = {})
6
- success = false
7
- klass = action.to_s.classify + 'Action'
8
- klass = CLI::Actions.const_get(klass)
9
-
10
- until success == true
11
- action = klass.new(settings, options)
12
- action.steps
13
- success = !action.restart
14
- end
15
- end
16
-
17
- include Resources
18
-
19
- attr_reader :actions, :settings, :restart, :options
20
-
21
- def initialize(settings, options)
22
- @settings = settings
23
- @options = options
24
- @highline = HighLine.new
25
- @resource = nil
26
-
27
- # State
28
- @in_group = false
29
- @restart = false
30
- end
31
-
32
- def resource(resource = nil)
33
- @resource = resource if resource
34
- @resource
35
- end
36
-
37
- def say(what)
38
- puts what
39
- end
40
-
41
- def agree(msg, *args)
42
- @highline.agree(msg, *args) do |q|
43
- next unless STDIN.tty? # For testing
44
- q.readline = true
45
- end
46
- end
47
-
48
- def ask(key, options = {})
49
- input key, options do |msg|
50
- resp = @highline.ask msg do |q|
51
- next unless STDIN.tty? # For testing
52
- q.readline = true
53
- end
54
- end
55
- end
56
-
57
- def password(key, options = {})
58
- input key, options do |msg|
59
- @highline.ask msg do |q|
60
- next unless STDIN.tty? # For testing
61
- q.echo = '*'
62
- end
63
- end
64
- end
65
-
66
- def choose
67
- @highline.choose do |menu|
68
- yield menu
69
- end
70
- end
71
-
72
- def group(options = {}, &blk)
73
- case
74
- when key = options[:validate]
75
- validation_group(key, &blk)
76
- else
77
- yield if block_given?
78
- end
79
- end
80
-
81
- def save(opts = {})
82
- if resource.save
83
- yield
84
- else
85
- display_errors
86
- if opts[:on_error] == :restart
87
- @restart = true
88
- end
89
- end
90
- end
91
-
92
- def run(action, opts = {})
93
- CLI::Action.run(action, settings, opts)
94
- end
95
-
96
- private
97
-
98
- def input(key, options)
99
- into = options[:into] || key
100
-
101
- validation_group into do
102
- msg = (key.to_s.humanize + ': ').ljust(25)
103
- resource[into] = yield msg
104
- end
105
- end
106
-
107
- def validation_group(resource_key)
108
- return yield if @in_group
109
-
110
- begin
111
- @in_group = true
112
- while true
113
- yield
114
-
115
- break if resource.valid_attribute?(resource_key)
116
-
117
- display_errors
118
- end
119
- ensure
120
- @in_group = false
121
- end
122
- end
123
-
124
- def display_errors
125
- resource.errors.each do |k, msg|
126
- key = k.to_s.split('.').last
127
- say "[ERROR] #{key.humanize} #{msg}"
128
- end
129
- end
130
- end
131
- end
@@ -1,104 +0,0 @@
1
- module Strobe
2
- module CLI::Actions
3
- class SignupAction < CLI::Action
4
- def steps
5
- resource Signup.new :account => { :name => 'default' }
6
-
7
- if settings[:token]
8
- say "This computer is already registered with a Strobe account."
9
- unless agree "Signup anyway? [Yn] "
10
- say "exiting..."
11
- exit
12
- end
13
- end
14
-
15
- say "Please fill out the following information."
16
-
17
- ask :invitation
18
- ask :email, :into => "user.email"
19
-
20
- group :validate => "user.password" do
21
- password :password, :into => "user.password"
22
- password :password_confirmation, :into => "password_confirmation"
23
- end
24
-
25
- save :on_error => :restart do
26
- settings[:token] = resource['user.authentication_token']
27
- settings.authenticate!
28
- say "Your account was created successfully."
29
- end
30
- end
31
- end
32
-
33
- class LoginAction < CLI::Action
34
- def steps
35
- resource Me.new
36
-
37
- say "Please enter your Strobe username and password."
38
-
39
- ask :email, :into => "user.email"
40
- password :password, :into => "user.password"
41
-
42
- save :on_error => :restart do
43
- settings[:token] = resource['user.authentication_token']
44
- settings.authenticate!
45
- say "The computer has been registered with the account."
46
- end
47
- end
48
- end
49
-
50
- class DeployAction < CLI::Action
51
- def steps
52
- ensure_computer_is_registered
53
-
54
- config = options[:config]
55
- path = determine_path
56
-
57
- if application_id = config[:application_id]
58
- resource Application.get application_id, :lazy => true
59
- else
60
- resource Account.first.applications.new
61
- end
62
-
63
- resource[:path] = path
64
-
65
- unless resource.persisted?
66
- say "Please fill out the following information."
67
-
68
- ask :name
69
- ask :url
70
-
71
- save do
72
- config[:application_id] ||= resource[:id]
73
- end
74
- end
75
-
76
- resource.deploy!
77
- say "The application has successfully been deployed and is available at #{resource[:url]}"
78
- end
79
-
80
- private
81
-
82
- def determine_path
83
- path = options[:path]
84
-
85
- if File.exist?("#{path}/tmp/build")
86
- return "#{path}/tmp/build"
87
- end
88
-
89
- path
90
- end
91
-
92
- def ensure_computer_is_registered
93
- unless settings[:token]
94
- say "This computer is not yet registered with a Strobe account."
95
-
96
- choose do |m|
97
- m.choice(:login) { run :login }
98
- m.choice(:signup) { run :signup }
99
- end
100
- end
101
- end
102
- end
103
- end
104
- end