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 +1 -1
- data/lib/strobe/association.rb +2 -1
- data/lib/strobe/cli/main.rb +167 -0
- data/lib/strobe/cli/settings.rb +9 -1
- data/lib/strobe/cli/table.rb +51 -0
- data/lib/strobe/cli/users.rb +65 -0
- data/lib/strobe/cli.rb +168 -32
- data/lib/strobe/collection.rb +13 -0
- data/lib/strobe/connection.rb +6 -3
- data/lib/strobe/identity_map.rb +48 -0
- data/lib/strobe/key.rb +21 -0
- data/lib/strobe/resource/base.rb +37 -11
- data/lib/strobe/resource/collection.rb +58 -8
- data/lib/strobe/resources/account.rb +2 -1
- data/lib/strobe/resources/application.rb +8 -4
- data/lib/strobe/resources/assignment.rb +12 -0
- data/lib/strobe/resources/membership.rb +13 -0
- data/lib/strobe/resources/team.rb +3 -1
- data/lib/strobe/resources/user.rb +2 -0
- data/lib/strobe/validations.rb +9 -1
- data/lib/strobe.rb +5 -0
- metadata +12 -6
- data/lib/strobe/cli/action.rb +0 -131
- data/lib/strobe/cli/actions.rb +0 -104
data/bin/strobe
CHANGED
data/lib/strobe/association.rb
CHANGED
@@ -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
|
data/lib/strobe/cli/settings.rb
CHANGED
@@ -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 :
|
8
|
-
autoload :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
|
-
|
11
|
-
|
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 $
|
23
|
+
raise if $ARGV.include?('--backtrace')
|
14
24
|
abort "[ERROR] #{e.message}"
|
15
25
|
end
|
16
26
|
|
17
|
-
|
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
|
-
|
23
|
-
|
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
|
-
|
27
|
-
require "strobe/cli/actions"
|
64
|
+
private
|
28
65
|
|
29
|
-
|
30
|
-
|
31
|
-
_run :signup
|
66
|
+
def path
|
67
|
+
options[:path] || Dir.pwd
|
32
68
|
end
|
33
69
|
|
34
|
-
|
35
|
-
|
36
|
-
_run :login
|
70
|
+
def config
|
71
|
+
@config ||= Settings.application(path)
|
37
72
|
end
|
38
73
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
79
|
+
def resource(resource = nil)
|
80
|
+
@resource = resource if resource
|
81
|
+
@resource
|
47
82
|
end
|
48
83
|
|
49
|
-
|
84
|
+
def settings
|
85
|
+
@settings ||= Settings.new
|
86
|
+
end
|
50
87
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
58
|
-
|
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
|
62
|
-
|
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
|
data/lib/strobe/collection.rb
CHANGED
@@ -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
|
data/lib/strobe/connection.rb
CHANGED
@@ -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,
|
78
|
+
raise UnauthenticatedError, msg
|
78
79
|
when 404
|
79
|
-
raise ResourceNotFoundError,
|
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))
|
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
|
data/lib/strobe/resource/base.rb
CHANGED
@@ -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(
|
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
|
-
|
135
|
-
|
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
|
-
|
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
|
33
|
-
inst =
|
34
|
-
|
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
|
-
|
78
|
+
raise NotImplementedError
|
41
79
|
end
|
42
80
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
@@ -12,17 +12,21 @@ module Strobe
|
|
12
12
|
|
13
13
|
filter :path
|
14
14
|
|
15
|
-
validates "account", "name",
|
15
|
+
validates "account", "name", :presence => true
|
16
16
|
|
17
|
-
def deploy!(
|
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
|
data/lib/strobe/validations.rb
CHANGED
@@ -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:
|
4
|
+
prerelease: true
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
-
|
9
|
+
- beta
|
9
10
|
- 1
|
10
|
-
version: 0.0.
|
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:
|
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/
|
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
|
data/lib/strobe/cli/action.rb
DELETED
@@ -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
|
data/lib/strobe/cli/actions.rb
DELETED
@@ -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
|