pagoda 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.bundle/config +2 -0
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +39 -0
- data/README +3 -0
- data/Rakefile +11 -0
- data/bin/pagoda +13 -0
- data/lib/pagoda/client.rb +221 -0
- data/lib/pagoda/command.rb +93 -0
- data/lib/pagoda/commands/app.rb +243 -0
- data/lib/pagoda/commands/auth.rb +149 -0
- data/lib/pagoda/commands/base.rb +184 -0
- data/lib/pagoda/commands/help.rb +100 -0
- data/lib/pagoda/commands/tunnel.rb +49 -0
- data/lib/pagoda/helpers.rb +127 -0
- data/lib/pagoda/tunnel_proxy.rb +130 -0
- data/lib/pagoda/version.rb +3 -0
- data/lib/pagoda.rb +3 -0
- data/pagoda.gemspec +29 -0
- data/spec/base.rb +21 -0
- data/spec/client_spec.rb +255 -0
- data/spec/command_spec.rb +26 -0
- data/spec/commands/auth_spec.rb +57 -0
- metadata +167 -0
data/.bundle/config
ADDED
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pagoda-*.*.*.gem
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
pagoda (0.0.1)
|
5
|
+
crack
|
6
|
+
iniparse
|
7
|
+
json_pure
|
8
|
+
rest-client
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: http://rubygems.org/
|
12
|
+
specs:
|
13
|
+
addressable (2.2.5)
|
14
|
+
crack (0.1.8)
|
15
|
+
diff-lcs (1.1.2)
|
16
|
+
iniparse (1.1.4)
|
17
|
+
json_pure (1.5.1)
|
18
|
+
mime-types (1.16)
|
19
|
+
rest-client (1.6.1)
|
20
|
+
mime-types (>= 1.16)
|
21
|
+
rspec (2.5.0)
|
22
|
+
rspec-core (~> 2.5.0)
|
23
|
+
rspec-expectations (~> 2.5.0)
|
24
|
+
rspec-mocks (~> 2.5.0)
|
25
|
+
rspec-core (2.5.1)
|
26
|
+
rspec-expectations (2.5.0)
|
27
|
+
diff-lcs (~> 1.1.2)
|
28
|
+
rspec-mocks (2.5.0)
|
29
|
+
webmock (1.6.2)
|
30
|
+
addressable (>= 2.2.2)
|
31
|
+
crack (>= 0.1.7)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
pagoda!
|
38
|
+
rspec
|
39
|
+
webmock
|
data/README
ADDED
data/Rakefile
ADDED
data/bin/pagoda
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'pagoda/client'
|
7
|
+
require 'pagoda/command'
|
8
|
+
|
9
|
+
args = ARGV.dup
|
10
|
+
ARGV.clear
|
11
|
+
command = args.shift.strip rescue 'help'
|
12
|
+
|
13
|
+
Pagoda::Command.run(command, args)
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'pagoda/version'
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'rest_client'
|
4
|
+
require 'uri'
|
5
|
+
require 'json/pure' unless {}.respond_to?(:to_json)
|
6
|
+
|
7
|
+
class Pagoda::Client
|
8
|
+
|
9
|
+
attr_reader :user, :password
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def version
|
13
|
+
Pagoda::VERSION
|
14
|
+
end
|
15
|
+
|
16
|
+
def gem_version_string
|
17
|
+
"pagoda-gem/#{version}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(user, password)
|
22
|
+
@user = user
|
23
|
+
@password = password
|
24
|
+
end
|
25
|
+
|
26
|
+
def app_list
|
27
|
+
doc = xml(get("/apps.xml").to_s)
|
28
|
+
doc.elements['apps'].elements.to_a('//app/').inject([]) do |list, app|
|
29
|
+
list << {
|
30
|
+
:name => app.elements['name'].text,
|
31
|
+
:instances => app.elements['instances'].text,
|
32
|
+
:git_url => app.elements['git-url'].text
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def database_exists?(app, mysql_instance)
|
38
|
+
begin
|
39
|
+
response = get("/apps/#{app}/databases/#{mysql_instance}.xml")
|
40
|
+
true
|
41
|
+
rescue RestClient::ResourceNotFound => e
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def app_create(name, git_url)
|
48
|
+
doc = xml(post("/apps.xml", {:app => {:name => name, :git_url => git_url}}).to_s)
|
49
|
+
doc.elements.to_a('//app/*').inject({}) do |hash, element|
|
50
|
+
case element.name
|
51
|
+
when "owner"
|
52
|
+
hash[:owner] = {:username => element.elements['username'].text, :email => element.elements['email'].text}
|
53
|
+
when "collaborators"
|
54
|
+
hash[:collaborators] = element.elements.to_a('//collaborator/').inject([]) do |list, collaborator|
|
55
|
+
list << {:username => collaborator.elements['username'].text, :email => collaborator.elements['email'].text}
|
56
|
+
end
|
57
|
+
when "transactions"
|
58
|
+
hash[:transactions] = element.elements.to_a('//transaction/').inject([]) do |list, transaction|
|
59
|
+
list << {
|
60
|
+
:id => transaction.elements["id"].text,
|
61
|
+
:name => transaction.elements["name"].text,
|
62
|
+
:description => transaction.elements["description"].text,
|
63
|
+
:state => transaction.elements["state"].text,
|
64
|
+
:status => transaction.elements["status"].text
|
65
|
+
}
|
66
|
+
end
|
67
|
+
else
|
68
|
+
hash[element.name.gsub(/-/, '_').to_sym] = element.text
|
69
|
+
end
|
70
|
+
hash
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def app_info(app)
|
75
|
+
doc = xml(get("/apps/#{app}.xml").to_s)
|
76
|
+
doc.elements.to_a('//app/*').inject({}) do |hash, element|
|
77
|
+
case element.name
|
78
|
+
when "owner"
|
79
|
+
hash[:owner] = {:username => element.elements['username'].text, :email => element.elements['email'].text}
|
80
|
+
when "collaborators"
|
81
|
+
hash[:collaborators] = element.elements.to_a('//collaborator/').inject([]) do |list, collaborator|
|
82
|
+
list << {:username => collaborator.elements['username'].text, :email => collaborator.elements['email'].text}
|
83
|
+
end
|
84
|
+
when "transactions"
|
85
|
+
hash[:transactions] = element.elements.to_a('//transaction/').inject([]) do |list, transaction|
|
86
|
+
list << {
|
87
|
+
:id => transaction.elements["id"].text,
|
88
|
+
:name => transaction.elements["name"].text,
|
89
|
+
:description => transaction.elements["description"].text,
|
90
|
+
:state => transaction.elements["state"].text,
|
91
|
+
:status => transaction.elements["status"].text
|
92
|
+
}
|
93
|
+
end
|
94
|
+
else
|
95
|
+
hash[element.name.gsub(/-/, '_').to_sym] = element.text
|
96
|
+
end
|
97
|
+
hash
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def app_update(app, updates)
|
102
|
+
put("/apps/#{app}.xml", {:update => updates}).to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
def app_destroy(app)
|
106
|
+
delete("/apps/#{app}.xml").to_s
|
107
|
+
end
|
108
|
+
|
109
|
+
def transaction_list(app)
|
110
|
+
doc = xml(get("/apps/#{app}/transactions.xml").to_s)
|
111
|
+
doc.elements['transactions'].elements.to_a('//transaction/').inject([]) do |list, transaction|
|
112
|
+
list << {
|
113
|
+
:id => transaction.elements['id'].text,
|
114
|
+
:name => transaction.elements['name'].text,
|
115
|
+
:description => transaction.elements['description'].text,
|
116
|
+
:state => transaction.elements['state'].text,
|
117
|
+
:status => transaction.elements['status'].text
|
118
|
+
}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def transaction_status(app, transaction)
|
123
|
+
doc = xml(get("/apps/#{app}/transactions/#{transaction}.xml").to_s)
|
124
|
+
doc.elements.to_a('//transaction/*').inject({}) { |hash, element| hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash }
|
125
|
+
end
|
126
|
+
|
127
|
+
def rewind(app, places=1)
|
128
|
+
doc = xml(put("/apps/#{app}/rewind.xml", {:places => places}).to_s)
|
129
|
+
doc.elements.to_a('//transaction/*').inject({}) { |hash, element| hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash }
|
130
|
+
end
|
131
|
+
|
132
|
+
def fast_forward(app, places=1)
|
133
|
+
doc = xml(put("/apps/#{app}/fast-forward.xml", {:places => places}).to_s)
|
134
|
+
doc.elements.to_a('//transaction/*').inject({}) { |hash, element| hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash }
|
135
|
+
end
|
136
|
+
|
137
|
+
def rollback(app)
|
138
|
+
get("/apps/#{app}/rollback.xml").to_s
|
139
|
+
end
|
140
|
+
|
141
|
+
def deploy_latest(app)
|
142
|
+
doc = xml(post("/apps/#{app}/deploy.xml").to_s)
|
143
|
+
doc.elements.to_a('//transaction/*').inject({}) { |hash, element| hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash }
|
144
|
+
end
|
145
|
+
|
146
|
+
def deploy(app, branch, commit)
|
147
|
+
doc = xml(post("/apps/#{app}/deploy.xml", {:deploy => {:branch => branch, :commit => commit}}).to_s)
|
148
|
+
doc.elements.to_a('//transaction/*').inject({}) { |hash, element| hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash }
|
149
|
+
end
|
150
|
+
|
151
|
+
# def deploy(app)
|
152
|
+
# doc = xml(put("/apps/#{app}/deploy.xml").to_s)
|
153
|
+
# doc.elements.to_a('//transaction/*').inject({}) { |hash, element| hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash }
|
154
|
+
# end
|
155
|
+
|
156
|
+
def scale_up(app, qty=1)
|
157
|
+
doc = xml(put("/apps/#{app}/scale-up.xml").to_s)
|
158
|
+
doc.elements.to_a('//transaction/*').inject({}) { |hash, element| hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash }
|
159
|
+
end
|
160
|
+
|
161
|
+
def scale_down(app, qty=1)
|
162
|
+
doc = xml(put("/apps/#{app}/scale-down.xml").to_s)
|
163
|
+
doc.elements.to_a('//transaction/*').inject({}) { |hash, element| hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash }
|
164
|
+
end
|
165
|
+
|
166
|
+
def app_databases(app)
|
167
|
+
doc = xml(get("/apps/#{app}/databases.xml").to_s)
|
168
|
+
doc.elements['databases'].elements.to_a('//database/').inject([]) {|list, instance| list << {:name => instance.elements['name'].text} }
|
169
|
+
end
|
170
|
+
|
171
|
+
def on_warning(&blk)
|
172
|
+
@warning_callback = blk
|
173
|
+
end
|
174
|
+
|
175
|
+
protected
|
176
|
+
|
177
|
+
def resource(uri)
|
178
|
+
RestClient.proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
|
179
|
+
if uri =~ /^https?/
|
180
|
+
RestClient::Resource.new(uri, @user, @password)
|
181
|
+
else
|
182
|
+
# RestClient::Resource.new("http://127.0.0.1:3000#{uri}", @user, @password)
|
183
|
+
RestClient::Resource.new("https://dashboard.pagodabox.com#{uri}", @user, @password)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def get(uri, extra_headers={})
|
188
|
+
process(:get, uri, extra_headers)
|
189
|
+
end
|
190
|
+
|
191
|
+
def post(uri, payload="", extra_headers={})
|
192
|
+
process(:post, uri, extra_headers, payload)
|
193
|
+
end
|
194
|
+
|
195
|
+
def put(uri, payload="", extra_headers={})
|
196
|
+
process(:put, uri, extra_headers, payload)
|
197
|
+
end
|
198
|
+
|
199
|
+
def delete(uri, extra_headers={})
|
200
|
+
process(:delete, uri, extra_headers)
|
201
|
+
end
|
202
|
+
|
203
|
+
def process(method, uri, extra_headers={}, payload=nil)
|
204
|
+
headers = pagoda_headers.merge(extra_headers)
|
205
|
+
args = [method, payload, headers].compact
|
206
|
+
response = resource(uri).send(*args)
|
207
|
+
end
|
208
|
+
|
209
|
+
def pagoda_headers
|
210
|
+
{
|
211
|
+
'User-Agent' => self.class.gem_version_string,
|
212
|
+
'X-Ruby-Version' => RUBY_VERSION,
|
213
|
+
'X-Ruby-Platform' => RUBY_PLATFORM,
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
def xml(raw) # :nodoc:
|
218
|
+
REXML::Document.new(raw)
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'pagoda/helpers'
|
2
|
+
require 'pagoda/tunnel_proxy'
|
3
|
+
require 'pagoda/commands/base'
|
4
|
+
|
5
|
+
Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
6
|
+
|
7
|
+
module Pagoda
|
8
|
+
module Command
|
9
|
+
class InvalidCommand < RuntimeError; end
|
10
|
+
class CommandFailed < RuntimeError; end
|
11
|
+
|
12
|
+
extend Pagoda::Helpers
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def run(command, args, retries=0)
|
17
|
+
begin
|
18
|
+
run_internal 'auth:reauthorize', args.dup if retries > 0
|
19
|
+
run_internal(command, args.dup)
|
20
|
+
rescue InvalidCommand
|
21
|
+
error "Unknown command: #{command}. Run 'pagoda help' for usage information."
|
22
|
+
rescue RestClient::Unauthorized
|
23
|
+
if retries < 3
|
24
|
+
STDERR.puts "Authentication failure"
|
25
|
+
run(command, args, retries+1)
|
26
|
+
else
|
27
|
+
error "Authentication failure"
|
28
|
+
end
|
29
|
+
rescue RestClient::ResourceNotFound => e
|
30
|
+
error extract_not_found(e.http_body)
|
31
|
+
rescue RestClient::RequestFailed => e
|
32
|
+
error extract_error(e.http_body) unless e.http_code == 402 || e.http_code == 102
|
33
|
+
rescue RestClient::RequestTimeout
|
34
|
+
error "API request timed out. Please try again, or contact support@pagodagrid.com if this issue persists."
|
35
|
+
rescue CommandFailed => e
|
36
|
+
error e.message
|
37
|
+
rescue Interrupt => e
|
38
|
+
error "\n[canceled]"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def run_internal(command, args)
|
43
|
+
klass, method = parse(command)
|
44
|
+
runner = klass.new(args)
|
45
|
+
raise InvalidCommand unless runner.respond_to?(method)
|
46
|
+
runner.send(method)
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(command)
|
50
|
+
parts = command.split(':')
|
51
|
+
case parts.size
|
52
|
+
when 1
|
53
|
+
begin
|
54
|
+
return eval("Pagoda::Command::#{command.capitalize}"), :index
|
55
|
+
rescue NameError, NoMethodError
|
56
|
+
return Pagoda::Command::App, command.to_sym
|
57
|
+
end
|
58
|
+
else
|
59
|
+
begin
|
60
|
+
const = Pagoda::Command
|
61
|
+
command = parts.pop
|
62
|
+
parts.each { |part| const = const.const_get(part.capitalize) }
|
63
|
+
return const, command.to_sym
|
64
|
+
rescue NameError
|
65
|
+
raise InvalidCommand
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def extract_not_found(body)
|
71
|
+
body =~ /^[\w\s]+ not found$/ ? body : "Resource not found"
|
72
|
+
end
|
73
|
+
|
74
|
+
def extract_error(body)
|
75
|
+
msg = parse_error_xml(body) || parse_error_json(body) || 'Internal server error'
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_error_xml(body)
|
79
|
+
xml_errors = REXML::Document.new(body).elements.to_a("//errors/error")
|
80
|
+
msg = xml_errors.map { |a| a.text }.join(" / ")
|
81
|
+
return msg unless msg.empty?
|
82
|
+
rescue Exception
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_error_json(body)
|
86
|
+
json = JSON.parse(body.to_s)
|
87
|
+
json['error']
|
88
|
+
rescue JSON::ParserError
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
module Pagoda::Command
|
2
|
+
class App < Base
|
3
|
+
|
4
|
+
def list
|
5
|
+
apps = client.app_list
|
6
|
+
if !apps.empty?
|
7
|
+
display
|
8
|
+
display "APPS"
|
9
|
+
display "//////////////////////////////////"
|
10
|
+
display
|
11
|
+
apps.each do |app|
|
12
|
+
display "- #{app[:name]}"
|
13
|
+
end
|
14
|
+
else
|
15
|
+
error ["looks like you haven't launched any apps", "type 'pagoda launch' to launch this project"]
|
16
|
+
end
|
17
|
+
display
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
if app_name = app(true)
|
22
|
+
error ["This project is already launched and paired to #{app_name}.", "To unpair run 'pagoda unpair'"]
|
23
|
+
end
|
24
|
+
|
25
|
+
unless locate_app_root
|
26
|
+
error ["Unable to find git config in this directory or in any parent directory"]
|
27
|
+
end
|
28
|
+
|
29
|
+
unless clone_url = extract_git_clone_url
|
30
|
+
errors = []
|
31
|
+
errors << "It appears you are using git (fantastic)."
|
32
|
+
errors << "However we only support git repos hosted with github."
|
33
|
+
errors << "Please ensure your repo is hosted with github."
|
34
|
+
error errors
|
35
|
+
end
|
36
|
+
|
37
|
+
unless name = args.dup.shift
|
38
|
+
error "Please Specify an app name ie. 'pagoda launch awesomeapp'"
|
39
|
+
end
|
40
|
+
|
41
|
+
display
|
42
|
+
display "+> Registering #{name}"
|
43
|
+
app = client.app_create(name, clone_url)
|
44
|
+
display "+> Launching...", false
|
45
|
+
loop_transaction(name)
|
46
|
+
add_app(name, clone_url)
|
47
|
+
display "+> #{name} launched"
|
48
|
+
|
49
|
+
unless option_value(nil, "--latest")
|
50
|
+
Pagoda::Command.run_internal("app:deploy", args)
|
51
|
+
end
|
52
|
+
|
53
|
+
display "-----------------------------------------------"
|
54
|
+
display
|
55
|
+
display "LIVE URL : http://#{name}.pagodabox.com"
|
56
|
+
display "ADMIN PANEL : http://dashboard.pagodabox.com"
|
57
|
+
display
|
58
|
+
display "-----------------------------------------------"
|
59
|
+
display
|
60
|
+
|
61
|
+
end
|
62
|
+
alias :launch :create
|
63
|
+
alias :register :create
|
64
|
+
|
65
|
+
def destroy
|
66
|
+
display
|
67
|
+
if confirm ["Are you totally completely sure you want to delete #{app} forever and ever?", "THIS CANNOT BE UNDONE! (y/n)"]
|
68
|
+
display "+> Destroying #{app}"
|
69
|
+
client.app_destroy(app)
|
70
|
+
display "+> #{app} has been successfully destroyed. RIP #{app}."
|
71
|
+
remove_app(app)
|
72
|
+
end
|
73
|
+
display
|
74
|
+
end
|
75
|
+
alias :delete :destroy
|
76
|
+
|
77
|
+
def info
|
78
|
+
display
|
79
|
+
info = client.app_info(app)
|
80
|
+
display "INFO - #{info[:name]}"
|
81
|
+
display "//////////////////////////////////"
|
82
|
+
display "name : #{info[:name]}"
|
83
|
+
display "clone_url : #{info[:git_url]}"
|
84
|
+
display
|
85
|
+
display "owner"
|
86
|
+
display "username : #{info[:owner][:username]}", true, 2
|
87
|
+
display "email : #{info[:owner][:email]}", true, 2
|
88
|
+
display
|
89
|
+
display "collaborators"
|
90
|
+
if info[:collaborators].any?
|
91
|
+
info[:collaborators].each_with_index do |collaborator, index|
|
92
|
+
display "username : #{collaborator[:username]}", true, 2
|
93
|
+
display "email : #{collaborator[:email]}", true, 2
|
94
|
+
end
|
95
|
+
else
|
96
|
+
display "(none)", true, 2
|
97
|
+
end
|
98
|
+
display
|
99
|
+
end
|
100
|
+
|
101
|
+
def pair
|
102
|
+
|
103
|
+
if app_name = app(true)
|
104
|
+
error ["This project is paired to #{app_name}.", "To unpair run 'pagoda unpair'"]
|
105
|
+
end
|
106
|
+
|
107
|
+
unless locate_app_root
|
108
|
+
error ["Unable to find git config in this directory or in any parent directory"]
|
109
|
+
end
|
110
|
+
|
111
|
+
unless my_repo = extract_git_clone_url
|
112
|
+
errors = []
|
113
|
+
errors << "It appears you are using git (fantastic)."
|
114
|
+
errors << "However we only support git repos hosted with github."
|
115
|
+
errors << "Please ensure your repo is hosted with github."
|
116
|
+
error errors
|
117
|
+
end
|
118
|
+
|
119
|
+
display
|
120
|
+
display "+> Locating deployed app with matching git repo"
|
121
|
+
|
122
|
+
apps = client.app_list
|
123
|
+
|
124
|
+
matching_apps = []
|
125
|
+
apps.each do |a|
|
126
|
+
if a[:git_url] == my_repo
|
127
|
+
matching_apps.push a
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
if matching_apps.count > 1
|
132
|
+
if name = app(true) || args.dup.shift
|
133
|
+
assign_app = nil
|
134
|
+
matching_apps.each do |a|
|
135
|
+
assign_app = a if a[:name] == name
|
136
|
+
end
|
137
|
+
if assign_app
|
138
|
+
display "+> Pairing this repo to deployed app - #{assign_app[:name]}"
|
139
|
+
pair_with_remote(assign_app)
|
140
|
+
display "+> Repo is now paired to '#{assign_app[:name]}'"
|
141
|
+
display
|
142
|
+
else
|
143
|
+
error "#{name} is not found among your launched app list"
|
144
|
+
end
|
145
|
+
else
|
146
|
+
errors = []
|
147
|
+
errors << "Multiple matches found"
|
148
|
+
errors << ""
|
149
|
+
matching_apps.each do |match|
|
150
|
+
errors << "-> #{match[:name]}"
|
151
|
+
end
|
152
|
+
errors << ""
|
153
|
+
errors << "You have more then one app that uses this repo."
|
154
|
+
errors << "Please specify which app you would like to pair to."
|
155
|
+
errors << ""
|
156
|
+
errors << "ex: pagoda pair #{matching_apps[0][:name]}"
|
157
|
+
error errors
|
158
|
+
end
|
159
|
+
elsif matching_apps.count == 1
|
160
|
+
match = matching_apps.first
|
161
|
+
display "+> Pairing this repo to deployed app - #{match[:name]}"
|
162
|
+
pair_with_remote match
|
163
|
+
display "+> Repo is now paired to '#{match[:name]}'"
|
164
|
+
display
|
165
|
+
else
|
166
|
+
error "Current git repo doesn't match any launched app repos"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def unpair
|
171
|
+
app
|
172
|
+
display
|
173
|
+
display "+> Unpairing this repo"
|
174
|
+
remove_app(app)
|
175
|
+
display "+> Free at last!"
|
176
|
+
display
|
177
|
+
end
|
178
|
+
|
179
|
+
def deploy
|
180
|
+
app
|
181
|
+
display
|
182
|
+
branch = parse_branch
|
183
|
+
commit = parse_commit
|
184
|
+
if option_value(nil, "--latest")
|
185
|
+
client.deploy_latest(app)
|
186
|
+
display "+> deploying to latest commit point on github...", false
|
187
|
+
loop_transaction
|
188
|
+
display "+> deployed"
|
189
|
+
display
|
190
|
+
else
|
191
|
+
client.deploy(app, branch, commit)
|
192
|
+
display "+> deploying to match current branch and commit...", false
|
193
|
+
loop_transaction
|
194
|
+
display "+> deployed"
|
195
|
+
display
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def rewind
|
200
|
+
app
|
201
|
+
display
|
202
|
+
transaction = client.rewind(app)
|
203
|
+
display "+> undo...", false
|
204
|
+
loop_transaction
|
205
|
+
display "+> done"
|
206
|
+
display
|
207
|
+
end
|
208
|
+
alias :rollback :rewind
|
209
|
+
alias :undo :rewind
|
210
|
+
|
211
|
+
def fast_forward
|
212
|
+
app
|
213
|
+
display
|
214
|
+
transaction = client.fast_forward(app)
|
215
|
+
display "+> redo...", false
|
216
|
+
loop_transaction
|
217
|
+
display "+> done"
|
218
|
+
display
|
219
|
+
end
|
220
|
+
alias :fastforward :fast_forward
|
221
|
+
alias :forward :fast_forward
|
222
|
+
alias :redo :fast_forward
|
223
|
+
|
224
|
+
protected
|
225
|
+
|
226
|
+
def pair_with_remote(app)
|
227
|
+
my_app_list = read_apps
|
228
|
+
current_root = locate_app_root
|
229
|
+
in_list = false
|
230
|
+
my_app_list.each do |app_str|
|
231
|
+
app_arr = app_str.split(" ")
|
232
|
+
if app[:git_url] == app_arr[1] && app[:name] == app_arr[0] || app_arr[2] == current_root
|
233
|
+
in_list = true
|
234
|
+
end
|
235
|
+
end
|
236
|
+
unless in_list
|
237
|
+
add_app app[:name]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
end
|
243
|
+
end
|