strobe 0.0.2.1 → 0.1.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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