rhc 1.21.3 → 1.22.5
Sign up to get free protection for your applications and to get access to all the features.
- data/autocomplete/rhc_bash +34 -10
- data/features/app_feature.rb +25 -0
- data/features/core_feature.rb +2 -2
- data/features/members_feature.rb +20 -0
- data/lib/rhc/cartridge_helpers.rb +11 -1
- data/lib/rhc/commands/app.rb +79 -29
- data/lib/rhc/commands/apps.rb +7 -3
- data/lib/rhc/commands/scp.rb +8 -4
- data/lib/rhc/commands/snapshot.rb +4 -79
- data/lib/rhc/context_helper.rb +7 -6
- data/lib/rhc/exceptions.rb +6 -0
- data/lib/rhc/output_helpers.rb +14 -7
- data/lib/rhc/rest/cartridge.rb +6 -1
- data/lib/rhc/rest/client.rb +31 -1
- data/lib/rhc/rest/mock.rb +2 -1
- data/lib/rhc/ssh_helpers.rb +88 -0
- data/lib/rhc/usage_templates/command_help.erb +1 -1
- data/spec/direct_execution_helper.rb +1 -0
- data/spec/rhc/commands/app_spec.rb +81 -1
- data/spec/rhc/commands/apps_spec.rb +41 -1
- data/spec/rhc/commands/scp_spec.rb +14 -3
- data/spec/rhc/commands/snapshot_spec.rb +1 -2
- data/spec/rhc/rest_client_spec.rb +23 -0
- data/spec/spec_helper.rb +36 -0
- metadata +6 -4
data/autocomplete/rhc_bash
CHANGED
@@ -10,9 +10,9 @@ _rhc()
|
|
10
10
|
if [[ "$cur" == -* ]]; then
|
11
11
|
opts="--always-prefix --clean --config --debug --insecure --limit --mock --noprompt --password --raw --rhlogin --server --ssl-ca-file --ssl-client-cert-file --ssl-version --timeout --token"
|
12
12
|
elif [ -z $cur ]; then
|
13
|
-
opts="account alias alias-add alias-delete-cert alias-list alias-remove alias-update-cert app app-configure app-create app-delete app-deploy app-force-stop app-reload app-restart app-scale-down app-scale-up app-show app-start app-stop app-tidy apps authorization authorization-add authorization-delete authorization-delete-all authorization-list cartridge cartridge-add cartridge-list cartridge-reload cartridge-remove cartridge-restart cartridge-scale cartridge-show cartridge-start cartridge-status cartridge-stop cartridge-storage deployment deployment-activate deployment-list deployment-show domain domain-configure domain-create domain-delete domain-leave domain-list domain-rename domain-show env env-list env-set env-show env-unset git-clone logout member member-add member-list member-remove port-forward server setup snapshot snapshot-restore snapshot-save ssh sshkey sshkey-add sshkey-list sshkey-remove sshkey-show tail threaddump"
|
13
|
+
opts="account alias alias-add alias-delete-cert alias-list alias-remove alias-update-cert app app-configure app-create app-delete app-deploy app-force-stop app-reload app-restart app-scale-down app-scale-up app-show app-start app-stop app-tidy apps authorization authorization-add authorization-delete authorization-delete-all authorization-list cartridge cartridge-add cartridge-list cartridge-reload cartridge-remove cartridge-restart cartridge-scale cartridge-show cartridge-start cartridge-status cartridge-stop cartridge-storage deployment deployment-activate deployment-list deployment-show domain domain-configure domain-create domain-delete domain-leave domain-list domain-rename domain-show env env-list env-set env-show env-unset git-clone logout member member-add member-list member-remove port-forward scp server setup snapshot snapshot-restore snapshot-save ssh sshkey sshkey-add sshkey-list sshkey-remove sshkey-show tail threaddump"
|
14
14
|
else
|
15
|
-
opts="account account-logout activate-deployment add-alias add-authorization add-cartridge add-member add-sshkey alias alias-add alias-delete-cert alias-list alias-remove alias-update-cert aliases app app-configure app-create app-delete app-deploy app-env app-force-stop app-reload app-restart app-scale-down app-scale-up app-show app-snapshot app-ssh app-start app-stop app-tidy apps authorization authorization-add authorization-delete authorization-delete-all authorization-list authorizations cartridge cartridge-add cartridge-list cartridge-reload cartridge-remove cartridge-restart cartridge-scale cartridge-show cartridge-start cartridge-status cartridge-stop cartridge-storage cartridges configure-app configure-domain create-app create-domain delete-all-authorization delete-app delete-authorization delete-cert-alias delete-domain deploy deploy-app deployment deployment-activate deployment-list deployment-show deployments domain domain-configure domain-create domain-delete domain-leave domain-list domain-rename domain-show domains env env-add env-list env-remove env-set env-show env-unset force-stop-app git-clone leave-domain list-alias list-authorization list-cartridge list-deployment list-domain list-env list-member list-sshkey logout member member-add member-list member-remove members port-forward reload-app reload-cartridge remove-alias remove-cartridge remove-member remove-sshkey rename-domain restart-app restart-cartridge restore-snapshot save-snapshot scale-cartridge scale-down-app scale-up-app server set-env setup show-app show-cartridge show-deployment show-domain show-env show-sshkey snapshot snapshot-restore snapshot-save ssh sshkey sshkey-add sshkey-list sshkey-remove sshkey-show start-app start-cartridge status-cartridge stop-app stop-cartridge storage-cartridge tail threaddump tidy-app unset-env update-cert-alias"
|
15
|
+
opts="account account-logout activate-deployment add-alias add-authorization add-cartridge add-member add-sshkey alias alias-add alias-delete-cert alias-list alias-remove alias-update-cert aliases app app-configure app-create app-delete app-deploy app-env app-force-stop app-reload app-restart app-scale-down app-scale-up app-scp app-show app-snapshot app-ssh app-start app-stop app-tidy apps authorization authorization-add authorization-delete authorization-delete-all authorization-list authorizations cartridge cartridge-add cartridge-list cartridge-reload cartridge-remove cartridge-restart cartridge-scale cartridge-show cartridge-start cartridge-status cartridge-stop cartridge-storage cartridges configure-app configure-domain create-app create-domain delete-all-authorization delete-app delete-authorization delete-cert-alias delete-domain deploy deploy-app deployment deployment-activate deployment-list deployment-show deployments domain domain-configure domain-create domain-delete domain-leave domain-list domain-rename domain-show domains env env-add env-list env-remove env-set env-show env-unset force-stop-app git-clone leave-domain list-alias list-authorization list-cartridge list-deployment list-domain list-env list-member list-sshkey logout member member-add member-list member-remove members port-forward reload-app reload-cartridge remove-alias remove-cartridge remove-member remove-sshkey rename-domain restart-app restart-cartridge restore-snapshot save-snapshot scale-cartridge scale-down-app scale-up-app scp server set-env setup show-app show-cartridge show-deployment show-domain show-env show-sshkey snapshot snapshot-restore snapshot-save ssh sshkey sshkey-add sshkey-list sshkey-remove sshkey-show start-app start-cartridge status-cartridge stop-app stop-cartridge storage-cartridge tail threaddump tidy-app unset-env update-cert-alias"
|
16
16
|
fi
|
17
17
|
else
|
18
18
|
prev="${COMP_WORDS[@]:0:COMP_CWORD}"
|
@@ -192,7 +192,7 @@ _rhc()
|
|
192
192
|
if [[ "$cur" == -* ]]; then
|
193
193
|
opts=""
|
194
194
|
else
|
195
|
-
opts="
|
195
|
+
opts="create delete start stop scale-up scale-down force-stop restart reload tidy show deploy configure env snapshot"
|
196
196
|
fi
|
197
197
|
;;
|
198
198
|
|
@@ -206,7 +206,7 @@ _rhc()
|
|
206
206
|
|
207
207
|
"rhc app create")
|
208
208
|
if [[ "$cur" == -* ]]; then
|
209
|
-
opts="--app --dns --enable-jenkins --env --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --repo --scaling --type"
|
209
|
+
opts="--app --dns --enable-jenkins --env --from-app --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --no-scaling --repo --scaling --type"
|
210
210
|
else
|
211
211
|
opts=""
|
212
212
|
fi
|
@@ -276,9 +276,17 @@ _rhc()
|
|
276
276
|
fi
|
277
277
|
;;
|
278
278
|
|
279
|
+
"rhc app scp")
|
280
|
+
if [[ "$cur" == -* ]]; then
|
281
|
+
opts="--app --application-id --local-path --namespace --remote-path --transfer-direction"
|
282
|
+
else
|
283
|
+
opts=""
|
284
|
+
fi
|
285
|
+
;;
|
286
|
+
|
279
287
|
"rhc app show")
|
280
288
|
if [[ "$cur" == -* ]]; then
|
281
|
-
opts="--app --application-id --configuration --gears --namespace --state"
|
289
|
+
opts="--app --application-id --configuration --gears --namespace --state --verbose"
|
282
290
|
else
|
283
291
|
opts=""
|
284
292
|
fi
|
@@ -334,7 +342,7 @@ _rhc()
|
|
334
342
|
|
335
343
|
"rhc app-create")
|
336
344
|
if [[ "$cur" == -* ]]; then
|
337
|
-
opts="--app --dns --enable-jenkins --env --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --repo --scaling --type"
|
345
|
+
opts="--app --dns --enable-jenkins --env --from-app --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --no-scaling --repo --scaling --type"
|
338
346
|
else
|
339
347
|
opts=""
|
340
348
|
fi
|
@@ -404,9 +412,17 @@ _rhc()
|
|
404
412
|
fi
|
405
413
|
;;
|
406
414
|
|
415
|
+
"rhc app-scp")
|
416
|
+
if [[ "$cur" == -* ]]; then
|
417
|
+
opts="--app --application-id --local-path --namespace --remote-path --transfer-direction"
|
418
|
+
else
|
419
|
+
opts=""
|
420
|
+
fi
|
421
|
+
;;
|
422
|
+
|
407
423
|
"rhc app-show")
|
408
424
|
if [[ "$cur" == -* ]]; then
|
409
|
-
opts="--app --application-id --configuration --gears --namespace --state"
|
425
|
+
opts="--app --application-id --configuration --gears --namespace --state --verbose"
|
410
426
|
else
|
411
427
|
opts=""
|
412
428
|
fi
|
@@ -454,7 +470,7 @@ _rhc()
|
|
454
470
|
|
455
471
|
"rhc apps")
|
456
472
|
if [[ "$cur" == -* ]]; then
|
457
|
-
opts=""
|
473
|
+
opts="--mine --verbose"
|
458
474
|
else
|
459
475
|
opts=""
|
460
476
|
fi
|
@@ -750,7 +766,7 @@ _rhc()
|
|
750
766
|
|
751
767
|
"rhc create-app")
|
752
768
|
if [[ "$cur" == -* ]]; then
|
753
|
-
opts="--app --dns --enable-jenkins --env --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --repo --scaling --type"
|
769
|
+
opts="--app --dns --enable-jenkins --env --from-app --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --no-scaling --repo --scaling --type"
|
754
770
|
else
|
755
771
|
opts=""
|
756
772
|
fi
|
@@ -1396,6 +1412,14 @@ _rhc()
|
|
1396
1412
|
fi
|
1397
1413
|
;;
|
1398
1414
|
|
1415
|
+
"rhc scp")
|
1416
|
+
if [[ "$cur" == -* ]]; then
|
1417
|
+
opts="--app --application-id --local-path --namespace --remote-path --transfer-direction"
|
1418
|
+
else
|
1419
|
+
opts=""
|
1420
|
+
fi
|
1421
|
+
;;
|
1422
|
+
|
1399
1423
|
"rhc server")
|
1400
1424
|
if [[ "$cur" == -* ]]; then
|
1401
1425
|
opts=""
|
@@ -1422,7 +1446,7 @@ _rhc()
|
|
1422
1446
|
|
1423
1447
|
"rhc show-app")
|
1424
1448
|
if [[ "$cur" == -* ]]; then
|
1425
|
-
opts="--app --application-id --configuration --gears --namespace --state"
|
1449
|
+
opts="--app --application-id --configuration --gears --namespace --state --verbose"
|
1426
1450
|
else
|
1427
1451
|
opts=""
|
1428
1452
|
fi
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'direct_execution_helper'
|
3
|
+
|
4
|
+
describe "rhc app scenarios" do
|
5
|
+
context "with an existing app" do
|
6
|
+
before(:all) do
|
7
|
+
standard_config
|
8
|
+
@app = has_an_application
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:app){ @app }
|
12
|
+
|
13
|
+
it "should clone successfully" do
|
14
|
+
app_name = "clone#{random}"
|
15
|
+
r = rhc 'create-app', app_name, '--from-app', app.name
|
16
|
+
r.stdout.should match /Domain:\s+#{app.domain}/
|
17
|
+
r.stdout.should match /Cartridges:\s+#{app.cartridges.collect{|c| c.name}.join(', ')}/
|
18
|
+
r.stdout.should match /From app:\s+#{app.name}/
|
19
|
+
r.stdout.should match /Gear Size:\s+Copied from '#{app.name}'/
|
20
|
+
r.stdout.should match /Scaling:\s+#{app.scalable? ? 'yes' : 'no'}/
|
21
|
+
r.stdout.should match /Setting deployment configuration/
|
22
|
+
r.stdout.should match /Pulling down a snapshot of application '#{app.name}'/
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/features/core_feature.rb
CHANGED
@@ -159,12 +159,12 @@ describe "rhc core scenarios" do
|
|
159
159
|
|
160
160
|
context "when adding a cartridge" do
|
161
161
|
context "with a scalable app" do
|
162
|
-
before(:
|
162
|
+
before(:each) do
|
163
163
|
standard_config
|
164
164
|
@app = has_a_scalable_application
|
165
165
|
end
|
166
166
|
|
167
|
-
after(:
|
167
|
+
after(:each) do
|
168
168
|
debug.puts "cleaning up scalable app" if debug?
|
169
169
|
@app.destroy
|
170
170
|
end
|
data/features/members_feature.rb
CHANGED
@@ -161,6 +161,26 @@ describe "rhc member scenarios" do
|
|
161
161
|
r.status.should == 0
|
162
162
|
end
|
163
163
|
end
|
164
|
+
|
165
|
+
it "should filter applications by owner" do
|
166
|
+
user = other_user.login
|
167
|
+
name = @domain.applications.first.name
|
168
|
+
|
169
|
+
r = rhc 'add-member', user, '--role', 'admin', '-n', domain.name
|
170
|
+
r.status.should == 0
|
171
|
+
|
172
|
+
with_environment(other_user) do
|
173
|
+
r = rhc 'apps', '--mine'
|
174
|
+
#r.status.should == 0
|
175
|
+
r.stdout.should match "No applications"
|
176
|
+
|
177
|
+
r = rhc 'apps'
|
178
|
+
r.status.should == 0
|
179
|
+
r.stdout.should match /You have access to \d+ applications?/
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
after { @domain.applications.first.destroy }
|
164
184
|
end
|
165
185
|
end
|
166
186
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
1
3
|
module RHC
|
2
4
|
module CartridgeHelpers
|
3
5
|
|
@@ -7,7 +9,7 @@ module RHC
|
|
7
9
|
from = opts[:from] || all_cartridges
|
8
10
|
|
9
11
|
cartridge_names.map do |name|
|
10
|
-
next from.find{ |c| c.url.present? && c.url
|
12
|
+
next from.find{ |c| c.url.present? && cartridge_url_downcase(c.url) == name} || use_cart(RHC::Rest::Cartridge.for_url(name), name) if name =~ %r(\Ahttps?://)i
|
11
13
|
|
12
14
|
name = name.downcase
|
13
15
|
from.find{ |c| c.name.downcase == name } ||
|
@@ -114,5 +116,13 @@ module RHC
|
|
114
116
|
def jenkins_client_cartridges
|
115
117
|
@jenkins_client_cartridges ||= filter_jenkins_cartridges('ci_builder')
|
116
118
|
end
|
119
|
+
|
120
|
+
def cartridge_url_downcase(url)
|
121
|
+
url = URI(url)
|
122
|
+
url.scheme = url.scheme.downcase rescue url.scheme
|
123
|
+
url.host = url.host.downcase rescue url.host
|
124
|
+
url.path = url.path.downcase rescue url.path
|
125
|
+
url.to_s
|
126
|
+
end
|
117
127
|
end
|
118
128
|
end
|
data/lib/rhc/commands/app.rb
CHANGED
@@ -3,6 +3,8 @@ require 'resolv'
|
|
3
3
|
require 'rhc/git_helpers'
|
4
4
|
require 'rhc/cartridge_helpers'
|
5
5
|
require 'rhc/deployment_helpers'
|
6
|
+
require 'optparse'
|
7
|
+
require 'tmpdir'
|
6
8
|
|
7
9
|
module RHC::Commands
|
8
10
|
class App < Base
|
@@ -51,9 +53,10 @@ module RHC::Commands
|
|
51
53
|
syntax "<name> <cartridge> [... <cartridge>] [... VARIABLE=VALUE] [-n namespace]"
|
52
54
|
option ["-n", "--namespace NAME"], "Namespace for the application"
|
53
55
|
option ["-g", "--gear-size SIZE"], "Gear size controls how much memory and CPU your cartridges can use."
|
54
|
-
option ["-s", "--scaling"], "Enable scaling for the web cartridge."
|
56
|
+
option ["-s", "--[no-]scaling"], "Enable scaling for the web cartridge."
|
55
57
|
option ["-r", "--repo DIR"], "Path to the Git repository (defaults to ./$app_name)"
|
56
58
|
option ["-e", "--env VARIABLE=VALUE"], "Environment variable(s) to be set on this app, or path to a file containing environment variables", :type => :list
|
59
|
+
option ["--from-app NAME"], "Create based on another application. All content and configurations will be copied from the original app."
|
57
60
|
option ["--from-code URL"], "URL to a Git repository that will become the initial contents of the application"
|
58
61
|
option ["--[no-]git"], "Skip creating the local Git repository."
|
59
62
|
option ["--[no-]dns"], "Skip waiting for the application DNS name to resolve. Must be used in combination with --no-git"
|
@@ -68,6 +71,22 @@ module RHC::Commands
|
|
68
71
|
|
69
72
|
arg_envs, cartridges = cartridges.partition{|item| item.match(env_var_regex_pattern)}
|
70
73
|
|
74
|
+
rest_domain = check_domain!
|
75
|
+
rest_app = nil
|
76
|
+
repo_dir = nil
|
77
|
+
|
78
|
+
if options.from_app
|
79
|
+
raise RHC::AppCloneNotSupportedException, "The server does not support creating apps based on others (rhc create-app --from-app)." if (!rest_domain.has_param?('ADD_APPLICATION', 'cartridges[][name]') || !rest_domain.has_param?('ADD_APPLICATION', 'cartridges[][url]'))
|
80
|
+
raise ArgumentError, "Option --from-code is incompatible with --from--app. When creating an app based on another resource you can either specify a Git repository URL with --from-code or an existing app name with --from-app." if options.from_code
|
81
|
+
raise ArgumentError, "Option --no-dns is incompatible with --from-app. We need to propagate the new app DNS to be able to configure it." if options.dns == false
|
82
|
+
raise ArgumentError, "Do not specify cartridges when creating an app based on another one. All cartridges will be copied from the original app." if !(cartridges || []).empty?
|
83
|
+
|
84
|
+
from_app = find_app(:app => options.from_app)
|
85
|
+
|
86
|
+
arg_envs = from_app.environment_variables.collect {|env| "#{env.name}=#{env.value} "} + arg_envs
|
87
|
+
cartridges = from_app.cartridges.reject{|c| c.tags.include?('web_proxy')}.collect{|c| c.custom? ? c.url : c.name}
|
88
|
+
end
|
89
|
+
|
71
90
|
cartridges = check_cartridges(cartridges, &require_one_web_cart)
|
72
91
|
|
73
92
|
options.default \
|
@@ -76,10 +95,6 @@ module RHC::Commands
|
|
76
95
|
|
77
96
|
raise ArgumentError, "You have named both your main application and your Jenkins application '#{name}'. In order to continue you'll need to specify a different name with --enable-jenkins or choose a different application name." if jenkins_app_name == name && enable_jenkins?
|
78
97
|
|
79
|
-
rest_domain = check_domain!
|
80
|
-
rest_app = nil
|
81
|
-
repo_dir = nil
|
82
|
-
|
83
98
|
cart_names = cartridges.collect do |c|
|
84
99
|
c.usage_rate? ? "#{c.short_name} (addtl. costs may apply)" : c.short_name
|
85
100
|
end.join(', ')
|
@@ -90,13 +105,31 @@ module RHC::Commands
|
|
90
105
|
warn "Server does not support environment variables."
|
91
106
|
end
|
92
107
|
|
108
|
+
scaling = options.scaling
|
109
|
+
|
110
|
+
if from_app
|
111
|
+
scaling = from_app.scalable if scaling.nil?
|
112
|
+
|
113
|
+
cartridges = from_app.cartridges.reject{|c| c.tags.include?('web_proxy')}.collect do |cartridge|
|
114
|
+
{
|
115
|
+
:name => (cartridge.name if !cartridge.custom?),
|
116
|
+
:url => (cartridge.url if cartridge.custom?),
|
117
|
+
:gear_size => options.gear_size || cartridge.gear_profile,
|
118
|
+
:additional_gear_storage => (cartridge.additional_gear_storage if cartridge.additional_gear_storage > 0),
|
119
|
+
:scales_from => (cartridge.scales_from if cartridge.scalable?),
|
120
|
+
:scales_to => (cartridge.scales_to if cartridge.scalable?)
|
121
|
+
}.reject{|k,v| v.nil? }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
93
125
|
paragraph do
|
94
126
|
header "Application Options"
|
95
127
|
say table([["Domain:", options.namespace],
|
96
128
|
["Cartridges:", cart_names],
|
97
129
|
(["Source Code:", options.from_code] if options.from_code),
|
98
|
-
|
99
|
-
["
|
130
|
+
(["From app:", from_app.name] if from_app),
|
131
|
+
["Gear Size:", options.gear_size || (from_app ? "Copied from '#{from_app.name}'" : "default")],
|
132
|
+
["Scaling:", (scaling ? "yes" : "no") + (from_app && options.scaling.nil? ? " (copied from '#{from_app.name}')" : '')],
|
100
133
|
(["Environment Variables:", env.map{|item| "#{item.name}=#{item.value}"}.join(', ')] if env.present?),
|
101
134
|
].compact
|
102
135
|
)
|
@@ -106,7 +139,7 @@ module RHC::Commands
|
|
106
139
|
say "Creating application '#{name}' ... "
|
107
140
|
|
108
141
|
# create the main app
|
109
|
-
rest_app = create_app(name, cartridges, rest_domain, options.gear_size,
|
142
|
+
rest_app = create_app(name, cartridges, rest_domain, options.gear_size, scaling, options.from_code, env, options.auto_deploy, options.keep_deployments, options.deployment_branch, options.deployment_type)
|
110
143
|
success "done"
|
111
144
|
|
112
145
|
paragraph{ indent{ success rest_app.messages.map(&:strip) } }
|
@@ -161,22 +194,35 @@ module RHC::Commands
|
|
161
194
|
return 0
|
162
195
|
end
|
163
196
|
end
|
197
|
+
end
|
164
198
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
199
|
+
if from_app
|
200
|
+
say "Setting deployment configuration ... "
|
201
|
+
rest_app.configure({:auto_deploy => from_app.auto_deploy, :keep_deployments => from_app.keep_deployments , :deployment_branch => from_app.deployment_branch, :deployment_type => from_app.deployment_type})
|
202
|
+
success 'done'
|
203
|
+
|
204
|
+
snapshot_filename = temporary_snapshot_filename(from_app.name)
|
205
|
+
save_snapshot(from_app, snapshot_filename)
|
206
|
+
restore_snapshot(rest_app, snapshot_filename)
|
207
|
+
File.delete(snapshot_filename) if File.exist?(snapshot_filename)
|
208
|
+
|
209
|
+
paragraph { warn "The application '#{from_app.name}' has aliases set which were not copied. Please configure the aliases of your new application manually." } unless from_app.aliases.empty?
|
210
|
+
end
|
211
|
+
|
212
|
+
if options.git
|
213
|
+
section(:now => true, :top => 1, :bottom => 1) do
|
214
|
+
begin
|
215
|
+
if has_git?
|
216
|
+
repo_dir = git_clone_application(rest_app)
|
217
|
+
else
|
218
|
+
warn "You do not have git installed, so your application's git repo will not be cloned"
|
219
|
+
end
|
220
|
+
rescue RHC::GitException => e
|
221
|
+
warn "#{e}"
|
222
|
+
unless RHC::Helpers.windows? and windows_nslookup_bug?(rest_app)
|
223
|
+
add_issue("We were unable to clone your application's git repo - #{e}",
|
224
|
+
"Clone your git repo",
|
225
|
+
"rhc git-clone #{rest_app.name}")
|
180
226
|
end
|
181
227
|
end
|
182
228
|
end
|
@@ -333,6 +379,7 @@ module RHC::Commands
|
|
333
379
|
option ["--state"], "Get the current state of the cartridges in this application"
|
334
380
|
option ["--configuration"], "Get the current configuration values set in this application"
|
335
381
|
option ["--gears [quota|ssh]"], "Show information about the cartridges on each gear in this application. Pass 'quota' to see per gear disk usage and limits. Pass 'ssh' to print only the SSH connection strings of each gear."
|
382
|
+
option ["-v", "--verbose"], "Display more details about the application's cartridges"
|
336
383
|
def show(app_name)
|
337
384
|
|
338
385
|
if options.state
|
@@ -376,7 +423,7 @@ module RHC::Commands
|
|
376
423
|
|
377
424
|
else
|
378
425
|
app = find_app(:include => :cartridges)
|
379
|
-
display_app(app, app.cartridges)
|
426
|
+
display_app(app, app.cartridges, nil, options.verbose)
|
380
427
|
end
|
381
428
|
|
382
429
|
0
|
@@ -514,20 +561,19 @@ module RHC::Commands
|
|
514
561
|
result
|
515
562
|
end
|
516
563
|
|
517
|
-
def create_app(name, cartridges, rest_domain, gear_size=nil, scale=nil, from_code=nil, environment_variables=nil, auto_deploy=nil, keep_deployments=nil, deployment_branch=nil)
|
564
|
+
def create_app(name, cartridges, rest_domain, gear_size=nil, scale=nil, from_code=nil, environment_variables=nil, auto_deploy=nil, keep_deployments=nil, deployment_branch=nil, deployment_type=nil)
|
518
565
|
app_options = {:cartridges => Array(cartridges)}
|
519
566
|
app_options[:gear_profile] = gear_size if gear_size
|
520
567
|
app_options[:scale] = scale if scale
|
521
568
|
app_options[:initial_git_url] = from_code if from_code
|
522
569
|
app_options[:debug] = true if @debug
|
523
|
-
app_options[:environment_variables] = environment_variables.map{
|
570
|
+
app_options[:environment_variables] = environment_variables.map{|i| i.to_hash}.group_by{|i| i[:name]}.values.map(&:last) if environment_variables.present?
|
524
571
|
app_options[:auto_deploy] = auto_deploy if !auto_deploy.nil?
|
525
572
|
app_options[:keep_deployments] = keep_deployments if keep_deployments
|
526
573
|
app_options[:deployment_branch] = deployment_branch if deployment_branch
|
574
|
+
app_options[:deployment_type] = deployment_type if deployment_type
|
527
575
|
debug "Creating application '#{name}' with these options - #{app_options.inspect}"
|
528
|
-
|
529
|
-
|
530
|
-
rest_app
|
576
|
+
rest_domain.add_application(name, app_options)
|
531
577
|
rescue RHC::Rest::Exception => e
|
532
578
|
if e.code == 109
|
533
579
|
paragraph{ say "Valid cartridge types:" }
|
@@ -721,5 +767,9 @@ WARNING_OUTPUT
|
|
721
767
|
def issues?
|
722
768
|
not @issues.nil?
|
723
769
|
end
|
770
|
+
|
771
|
+
def temporary_snapshot_filename(app_name)
|
772
|
+
"#{Dir.tmpdir}/#{app_name}_temp_clone.tar.gz"
|
773
|
+
end
|
724
774
|
end
|
725
775
|
end
|
data/lib/rhc/commands/apps.rb
CHANGED
@@ -4,16 +4,20 @@ module RHC::Commands
|
|
4
4
|
class Apps < Base
|
5
5
|
summary "List all your applications"
|
6
6
|
description "Display the list of applications that you own. Includes information about each application."
|
7
|
+
option ['--mine'], "Display only applications you own"
|
8
|
+
option ["-v", "--verbose"], "Display additional details about the application's cartridges"
|
7
9
|
def run
|
8
|
-
applications =
|
10
|
+
applications = (options.mine ?
|
11
|
+
rest_client.owned_applications(:include => :cartridges) :
|
12
|
+
rest_client.applications(:include => :cartridges)).sort
|
9
13
|
|
10
14
|
info "In order to deploy applications, you must create a domain with 'rhc setup' or 'rhc create-domain'." and return 1 if applications.empty? && rest_client.domains.empty?
|
11
15
|
|
12
|
-
applications.each{ |a| display_app(a, a.cartridges) }.blank? and
|
16
|
+
applications.each{ |a| display_app(a, a.cartridges, nil, options.verbose) }.blank? and
|
13
17
|
info "No applications. Use 'rhc create-app'." and
|
14
18
|
return 1
|
15
19
|
|
16
|
-
success "You have #{applications.length}
|
20
|
+
success "You have#{options.mine ? '' : ' access to'} #{pluralize(applications.length, 'application')}."
|
17
21
|
0
|
18
22
|
end
|
19
23
|
end
|
data/lib/rhc/commands/scp.rb
CHANGED
@@ -21,9 +21,9 @@ module RHC::Commands
|
|
21
21
|
DESC
|
22
22
|
syntax "[<app> --] <action> <local_path> <remote_path>"
|
23
23
|
takes_application :argument => true
|
24
|
-
argument :action, "Transfer direction: upload|download", ["-t", "--
|
25
|
-
argument :local_path, "Local filesystem path", ["-
|
26
|
-
argument :remote_path, "Remote filesystem path", ["-r", "--
|
24
|
+
argument :action, "Transfer direction: upload|download", ["-t", "--transfer-direction upload|download"], :optional => false
|
25
|
+
argument :local_path, "Local filesystem path", ["-f", "--local-path file_path"], :optional => false
|
26
|
+
argument :remote_path, "Remote filesystem path", ["-r", "--remote-path file_path"], :optional => false
|
27
27
|
alias_action 'app scp', :root_command => true
|
28
28
|
def run(_, action, local_path, remote_path)
|
29
29
|
rest_app = find_app
|
@@ -48,8 +48,12 @@ module RHC::Commands
|
|
48
48
|
raise RHC::SSHConnectionRefused.new(ssh_opts[0], ssh_opts[1])
|
49
49
|
rescue SocketError => e
|
50
50
|
raise RHC::ConnectionFailed, "The connection to #{ssh_opts[1]} failed: #{e.message}"
|
51
|
+
rescue Net::SSH::AuthenticationFailed => e
|
52
|
+
debug_error e
|
53
|
+
raise RHC::SSHAuthenticationFailed.new(ssh_opts[1], ssh_opts[0])
|
51
54
|
rescue Net::SCP::Error => e
|
52
|
-
|
55
|
+
debug_error e
|
56
|
+
raise RHC::RemoteFileOrPathNotFound.new("An unknown error occurred: #{e.message}")
|
53
57
|
end
|
54
58
|
end
|
55
59
|
|
@@ -25,45 +25,15 @@ module RHC::Commands
|
|
25
25
|
option ["--ssh PATH"], "Full path to your SSH executable with additional options"
|
26
26
|
alias_action :"app snapshot save", :root_command => true, :deprecated => true
|
27
27
|
def save(app)
|
28
|
-
|
28
|
+
|
29
29
|
rest_app = find_app
|
30
30
|
|
31
31
|
raise RHC::DeploymentsNotSupportedException.new if options.deployment && !rest_app.supports?("DEPLOY")
|
32
32
|
|
33
|
-
ssh_uri = URI.parse(rest_app.ssh_url)
|
34
33
|
filename = options.filepath ? options.filepath : "#{rest_app.name}.tar.gz"
|
35
34
|
|
36
|
-
|
37
|
-
ssh_cmd = "#{ssh} #{ssh_uri.user}@#{ssh_uri.host} '#{snapshot_cmd}' > #{filename}"
|
38
|
-
debug ssh_cmd
|
39
|
-
|
40
|
-
say "Pulling down a snapshot to #{filename}..."
|
35
|
+
save_snapshot(rest_app, filename, options.deployment, options.ssh)
|
41
36
|
|
42
|
-
begin
|
43
|
-
if !RHC::Helpers.windows?
|
44
|
-
status, output = exec(ssh_cmd)
|
45
|
-
if status != 0
|
46
|
-
debug output
|
47
|
-
raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
|
48
|
-
end
|
49
|
-
else
|
50
|
-
Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
|
51
|
-
File.open(filename, 'wb') do |file|
|
52
|
-
ssh.exec! "snapshot" do |channel, stream, data|
|
53
|
-
if stream == :stdout
|
54
|
-
file.write(data)
|
55
|
-
else
|
56
|
-
debug data
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
|
63
|
-
debug e.backtrace
|
64
|
-
raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
|
65
|
-
end
|
66
|
-
results { say "Success" }
|
67
37
|
0
|
68
38
|
end
|
69
39
|
|
@@ -74,59 +44,14 @@ module RHC::Commands
|
|
74
44
|
option ["--ssh PATH"], "Full path to your SSH executable with additional options"
|
75
45
|
alias_action :"app snapshot restore", :root_command => true, :deprecated => true
|
76
46
|
def restore(app)
|
77
|
-
ssh = check_ssh_executable! options.ssh
|
78
47
|
rest_app = find_app
|
79
48
|
filename = options.filepath ? options.filepath : "#{rest_app.name}.tar.gz"
|
80
49
|
|
81
50
|
if File.exists? filename
|
82
|
-
|
83
|
-
include_git = RHC::Helpers.windows? ? true : RHC::TarGz.contains(filename, './*/git')
|
84
|
-
ssh_uri = URI.parse(rest_app.ssh_url)
|
85
|
-
|
86
|
-
ssh_cmd = "cat '#{filename}' | #{ssh} #{ssh_uri.user}@#{ssh_uri.host} 'restore#{include_git ? ' INCLUDE_GIT' : ''}'"
|
87
|
-
|
88
|
-
say "Restoring from snapshot #{filename}..."
|
89
|
-
debug ssh_cmd
|
90
|
-
|
91
|
-
begin
|
92
|
-
if !RHC::Helpers.windows?
|
93
|
-
status, output = exec(ssh_cmd)
|
94
|
-
if status != 0
|
95
|
-
debug output
|
96
|
-
raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
|
97
|
-
end
|
98
|
-
else
|
99
|
-
ssh = Net::SSH.start(ssh_uri.host, ssh_uri.user)
|
100
|
-
ssh.open_channel do |channel|
|
101
|
-
channel.exec("restore#{include_git ? ' INCLUDE_GIT' : ''}") do |ch, success|
|
102
|
-
channel.on_data do |ch, data|
|
103
|
-
say data
|
104
|
-
end
|
105
|
-
channel.on_extended_data do |ch, type, data|
|
106
|
-
say data
|
107
|
-
end
|
108
|
-
channel.on_close do |ch|
|
109
|
-
say "Terminating..."
|
110
|
-
end
|
111
|
-
File.open(filename, 'rb') do |file|
|
112
|
-
file.chunk(1024) do |chunk|
|
113
|
-
channel.send_data chunk
|
114
|
-
end
|
115
|
-
end
|
116
|
-
channel.eof!
|
117
|
-
end
|
118
|
-
end
|
119
|
-
ssh.loop
|
120
|
-
end
|
121
|
-
rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
|
122
|
-
debug e.backtrace
|
123
|
-
raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
|
124
|
-
end
|
125
|
-
|
51
|
+
restore_snapshot(rest_app, filename, options.ssh)
|
126
52
|
else
|
127
53
|
raise RHC::SnapshotRestoreException.new "Archive not found: #{filename}"
|
128
54
|
end
|
129
|
-
results { say "Success" }
|
130
55
|
0
|
131
56
|
end
|
132
57
|
|
@@ -134,4 +59,4 @@ module RHC::Commands
|
|
134
59
|
include RHC::SSHHelpers
|
135
60
|
|
136
61
|
end
|
137
|
-
end
|
62
|
+
end
|
data/lib/rhc/context_helper.rb
CHANGED
@@ -67,22 +67,23 @@ module RHC
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def find_app(opts={})
|
70
|
-
if id = options.application_id
|
70
|
+
if id = options.application_id.presence
|
71
71
|
if opts.delete(:with_gear_groups)
|
72
72
|
return rest_client.find_application_by_id_gear_groups(id, opts)
|
73
73
|
else
|
74
74
|
return rest_client.find_application_by_id(id, opts)
|
75
75
|
end
|
76
76
|
end
|
77
|
+
option = (opts && opts[:app]) || options.app
|
77
78
|
domain, app =
|
78
|
-
if
|
79
|
-
if
|
80
|
-
|
79
|
+
if option
|
80
|
+
if option =~ /\//
|
81
|
+
option.split(/\//)
|
81
82
|
else
|
82
|
-
[options.namespace || namespace_context,
|
83
|
+
[options.namespace || namespace_context, option]
|
83
84
|
end
|
84
85
|
end
|
85
|
-
if app && domain
|
86
|
+
if app.present? && domain.present?
|
86
87
|
if opts.delete(:with_gear_groups)
|
87
88
|
rest_client.find_application_gear_groups(domain, app, opts)
|
88
89
|
else
|
data/lib/rhc/exceptions.rb
CHANGED
@@ -138,6 +138,12 @@ module RHC
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
|
+
class AppCloneNotSupportedException < Exception
|
142
|
+
def initialize(message="The server does not support cloning apps")
|
143
|
+
super message, 134
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
141
147
|
class MissingScalingValueException < Exception
|
142
148
|
def initialize(message="Must provide either a min or max value for scaling")
|
143
149
|
super message
|
data/lib/rhc/output_helpers.rb
CHANGED
@@ -26,7 +26,7 @@ module RHC
|
|
26
26
|
#---------------------------
|
27
27
|
# Application information
|
28
28
|
#---------------------------
|
29
|
-
def display_app(app, cartridges=nil, properties=nil)
|
29
|
+
def display_app(app, cartridges=nil, properties=nil, verbose=false)
|
30
30
|
paragraph do
|
31
31
|
header [app.name, "@ #{app.app_url}", "(uuid: #{app.uuid})"] do
|
32
32
|
section(:bottom => 1) do
|
@@ -43,7 +43,7 @@ module RHC
|
|
43
43
|
:aliases]),
|
44
44
|
:delete => true
|
45
45
|
end
|
46
|
-
cartridges.each{ |c| section(:bottom => 1){ display_cart(c) } } if cartridges
|
46
|
+
cartridges.each{ |c| section(:bottom => 1){ display_cart(c, verbose ? :verbose : []) } } if cartridges
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
@@ -69,6 +69,8 @@ module RHC
|
|
69
69
|
format_scaling_info(cart.scaling)
|
70
70
|
elsif cart.shares_gears?
|
71
71
|
"Located with #{cart.collocated_with.join(", ")}"
|
72
|
+
elsif cart.external? && cart.current_scale == 0
|
73
|
+
"none (external service)"
|
72
74
|
else
|
73
75
|
"%d %s" % [format_value(:current_scale, cart.current_scale), format_value(:gear_profile, cart.gear_profile)]
|
74
76
|
end
|
@@ -84,13 +86,18 @@ module RHC
|
|
84
86
|
#---------------------------
|
85
87
|
|
86
88
|
def display_cart(cart, *properties)
|
89
|
+
verbose = properties.delete(:verbose)
|
87
90
|
say format_table \
|
88
91
|
format_cart_header(cart),
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
get_properties(cart, *properties).
|
93
|
+
concat(verbose && cart.custom? ? [[:description, cart.description.strip]] : []).
|
94
|
+
concat([[:downloaded_cartridge_url, cart.url]]).
|
95
|
+
concat(verbose && cart.custom? ? [[:version, cart.version]] : []).
|
96
|
+
concat(verbose && cart.custom? && cart.license.strip.downcase != 'unknown' ? [[:license, cart.license]] : []).
|
97
|
+
concat(cart.custom? ? [[:website, cart.website]] : []).
|
98
|
+
concat([[cart.scalable? ? :scaling : :gears, format_cart_gears(cart)]]).
|
99
|
+
concat(cart.properties.map{ |p| ["#{table_heading(p['name'])}:", p['value']] }.sort{ |a,b| a[0] <=> b[0] }).
|
100
|
+
concat(cart.environment_variables.present? ? [[:environment_variables, cart.environment_variables.map{|item| "#{item[:name]}=#{item[:value]}" }.sort.join(', ')]] : []),
|
94
101
|
:delete => true
|
95
102
|
|
96
103
|
say format_usage_message(cart) if cart.usage_rate?
|
data/lib/rhc/rest/cartridge.rb
CHANGED
@@ -5,7 +5,8 @@ module RHC
|
|
5
5
|
|
6
6
|
define_attr :type, :name, :display_name, :properties, :gear_profile, :status_messages, :scales_to, :scales_from, :scales_with,
|
7
7
|
:current_scale, :supported_scales_to, :supported_scales_from, :tags, :description, :collocated_with, :base_gear_storage,
|
8
|
-
:additional_gear_storage, :url, :environment_variables, :gear_size, :automatic_updates
|
8
|
+
:additional_gear_storage, :url, :environment_variables, :gear_size, :automatic_updates,
|
9
|
+
:version, :license, :website, :description
|
9
10
|
|
10
11
|
def scalable?
|
11
12
|
supported_scales_to != supported_scales_from
|
@@ -31,6 +32,10 @@ module RHC
|
|
31
32
|
v
|
32
33
|
end
|
33
34
|
|
35
|
+
def external?
|
36
|
+
tags.include?('external')
|
37
|
+
end
|
38
|
+
|
34
39
|
def shares_gears?
|
35
40
|
Array(collocated_with).present?
|
36
41
|
end
|
data/lib/rhc/rest/client.rb
CHANGED
@@ -36,6 +36,7 @@ module RHC
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def applications(options={})
|
39
|
+
debug "Getting applications"
|
39
40
|
if link = api.link_href(:LIST_APPLICATIONS)
|
40
41
|
api.rest_method :LIST_APPLICATIONS, options
|
41
42
|
else
|
@@ -43,6 +44,16 @@ module RHC
|
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
47
|
+
def owned_applications(options={})
|
48
|
+
debug "Getting owned applications"
|
49
|
+
if link = api.link_href(:LIST_APPLICATIONS_BY_OWNER)
|
50
|
+
@owned_applications ||= api.rest_method 'LIST_APPLICATIONS_BY_OWNER', :owner => '@self'
|
51
|
+
else
|
52
|
+
owned_domains_names = owned_domains.map{|d| d.name}
|
53
|
+
@owned_applications ||= applications(options).select{|app| owned_domains_names.include?(app.domain)}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
46
57
|
def cartridges
|
47
58
|
debug "Getting all cartridges"
|
48
59
|
@cartridges ||= api.rest_method("LIST_CARTRIDGES", nil, :lazy_auth => true)
|
@@ -53,7 +64,7 @@ module RHC
|
|
53
64
|
@user ||= api.rest_method "GET_USER"
|
54
65
|
end
|
55
66
|
|
56
|
-
#Find Domain by
|
67
|
+
#Find Domain by namespace
|
57
68
|
def find_domain(id)
|
58
69
|
debug "Finding domain #{id}"
|
59
70
|
if link = api.link_href(:SHOW_DOMAIN, ':name' => id)
|
@@ -64,18 +75,25 @@ module RHC
|
|
64
75
|
end
|
65
76
|
|
66
77
|
def find_application(domain, application, options={})
|
78
|
+
precheck_domain_id(domain)
|
79
|
+
precheck_application_id(application)
|
67
80
|
request(:url => link_show_application_by_domain_name(domain, application), :method => "GET", :payload => options)
|
68
81
|
end
|
69
82
|
|
70
83
|
def find_application_gear_groups(domain, application, options={})
|
84
|
+
precheck_domain_id(domain)
|
85
|
+
precheck_application_id(application)
|
71
86
|
request(:url => link_show_application_by_domain_name(domain, application, "gear_groups"), :method => "GET", :payload => options)
|
72
87
|
end
|
73
88
|
|
74
89
|
def find_application_aliases(domain, application, options={})
|
90
|
+
precheck_domain_id(domain)
|
91
|
+
precheck_application_id(application)
|
75
92
|
request(:url => link_show_application_by_domain_name(domain, application, "aliases"), :method => "GET", :payload => options)
|
76
93
|
end
|
77
94
|
|
78
95
|
def find_application_by_id(id, options={})
|
96
|
+
precheck_application_id(id)
|
79
97
|
if api.supports? :show_application
|
80
98
|
request(:url => link_show_application_by_id(id), :method => "GET", :payload => options)
|
81
99
|
else
|
@@ -84,6 +102,7 @@ module RHC
|
|
84
102
|
end
|
85
103
|
|
86
104
|
def find_application_by_id_gear_groups(id, options={})
|
105
|
+
precheck_application_id(id)
|
87
106
|
if api.supports? :show_application
|
88
107
|
request(:url => link_show_application_by_id(id, 'gear_groups'), :method => "GET", :payload => options)
|
89
108
|
else
|
@@ -91,6 +110,17 @@ module RHC
|
|
91
110
|
end or raise ApplicationNotFoundException.new("Application with id #{id} not found")
|
92
111
|
end
|
93
112
|
|
113
|
+
# Catch domain ids which we can't make API calls for
|
114
|
+
def precheck_domain_id(domain)
|
115
|
+
raise DomainNotFoundException.new("Domain not specified") if domain.blank?
|
116
|
+
raise DomainNotFoundException.new("Domain #{domain} not found") if ['.','..'].include?(domain)
|
117
|
+
end
|
118
|
+
|
119
|
+
def precheck_application_id(application)
|
120
|
+
raise ApplicationNotFoundException.new("Application not specified") if application.blank?
|
121
|
+
raise ApplicationNotFoundException.new("Application #{application} not found") if ['.','..'].include?(application)
|
122
|
+
end
|
123
|
+
|
94
124
|
def link_show_application_by_domain_name(domain, application, *args)
|
95
125
|
[
|
96
126
|
api.links['LIST_DOMAINS']['href'],
|
data/lib/rhc/rest/mock.rb
CHANGED
@@ -444,7 +444,7 @@ module RHC::Rest::Mock
|
|
444
444
|
end
|
445
445
|
|
446
446
|
def mock_domain_links(domain_id='test_domain')
|
447
|
-
[['ADD_APPLICATION', "domains/#{domain_id}/apps/add", 'post', {'optional_params' => [{'name' => 'environment_variables'}]} ],
|
447
|
+
[['ADD_APPLICATION', "domains/#{domain_id}/apps/add", 'post', {'optional_params' => [{'name' => 'environment_variables'}, {'name' => 'cartridges[][name]'}, {'name' => 'cartridges[][url]'}]} ],
|
448
448
|
['LIST_APPLICATIONS', "domains/#{domain_id}/apps/", 'get' ],
|
449
449
|
['UPDATE', "domains/#{domain_id}/update", 'put'],
|
450
450
|
['DELETE', "domains/#{domain_id}/delete", 'post']]
|
@@ -681,6 +681,7 @@ module RHC::Rest::Mock
|
|
681
681
|
scale = type[:scale]
|
682
682
|
gear_profile = type[:gear_profile]
|
683
683
|
git_url = type[:initial_git_url]
|
684
|
+
tags = type[:tags]
|
684
685
|
type = Array(type[:cartridges] || type[:cartridge])
|
685
686
|
end
|
686
687
|
a = MockRestApplication.new(client, name, type, self, scale, gear_profile, git_url)
|
data/lib/rhc/ssh_helpers.rb
CHANGED
@@ -248,6 +248,94 @@ module RHC
|
|
248
248
|
end
|
249
249
|
end
|
250
250
|
|
251
|
+
def save_snapshot(app, filename, for_deployment=false, ssh_executable=nil)
|
252
|
+
ssh_uri = URI.parse(app.ssh_url)
|
253
|
+
ssh_executable = check_ssh_executable! ssh_executable
|
254
|
+
|
255
|
+
snapshot_cmd = for_deployment ? 'gear archive-deployment' : 'snapshot'
|
256
|
+
ssh_cmd = "#{ssh_executable} #{ssh_uri.user}@#{ssh_uri.host} '#{snapshot_cmd}' > #{filename}"
|
257
|
+
ssh_stderr = " 2>/dev/null"
|
258
|
+
debug ssh_cmd
|
259
|
+
|
260
|
+
say "Pulling down a snapshot of application '#{app.name}' to #{filename} ... "
|
261
|
+
|
262
|
+
begin
|
263
|
+
if !RHC::Helpers.windows?
|
264
|
+
status, output = exec(ssh_cmd + (debug? ? '' : ssh_stderr))
|
265
|
+
if status != 0
|
266
|
+
debug output
|
267
|
+
raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
|
268
|
+
end
|
269
|
+
else
|
270
|
+
Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
|
271
|
+
File.open(filename, 'wb') do |file|
|
272
|
+
ssh.exec! "snapshot" do |channel, stream, data|
|
273
|
+
if stream == :stdout
|
274
|
+
file.write(data)
|
275
|
+
else
|
276
|
+
debug data
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
|
283
|
+
debug e.backtrace
|
284
|
+
raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
|
285
|
+
end
|
286
|
+
|
287
|
+
success 'done'
|
288
|
+
end
|
289
|
+
|
290
|
+
def restore_snapshot(app, filename, ssh_executable=nil)
|
291
|
+
include_git = RHC::Helpers.windows? ? true : RHC::TarGz.contains(filename, './*/git')
|
292
|
+
ssh_uri = URI.parse(app.ssh_url)
|
293
|
+
ssh_executable = check_ssh_executable! options.ssh
|
294
|
+
|
295
|
+
ssh_cmd = "cat '#{filename}' | #{ssh_executable} #{ssh_uri.user}@#{ssh_uri.host} 'restore#{include_git ? ' INCLUDE_GIT' : ''}'"
|
296
|
+
ssh_stderr = " 2>/dev/null"
|
297
|
+
debug ssh_cmd
|
298
|
+
|
299
|
+
say "Restoring from snapshot #{filename} to application '#{app.name}' ... "
|
300
|
+
|
301
|
+
begin
|
302
|
+
if !RHC::Helpers.windows?
|
303
|
+
status, output = exec(ssh_cmd + (debug? ? '' : ssh_stderr))
|
304
|
+
if status != 0
|
305
|
+
debug output
|
306
|
+
raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
|
307
|
+
end
|
308
|
+
else
|
309
|
+
ssh = Net::SSH.start(ssh_uri.host, ssh_uri.user)
|
310
|
+
ssh.open_channel do |channel|
|
311
|
+
channel.exec("restore#{include_git ? ' INCLUDE_GIT' : ''}") do |ch, success|
|
312
|
+
channel.on_data do |ch, data|
|
313
|
+
debug data
|
314
|
+
end
|
315
|
+
channel.on_extended_data do |ch, type, data|
|
316
|
+
debug data
|
317
|
+
end
|
318
|
+
channel.on_close do |ch|
|
319
|
+
debug "Terminating..."
|
320
|
+
end
|
321
|
+
File.open(filename, 'rb') do |file|
|
322
|
+
file.chunk(4096) do |chunk|
|
323
|
+
channel.send_data chunk
|
324
|
+
end
|
325
|
+
end
|
326
|
+
channel.eof!
|
327
|
+
end
|
328
|
+
end
|
329
|
+
ssh.loop
|
330
|
+
end
|
331
|
+
rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
|
332
|
+
debug e.backtrace
|
333
|
+
raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
|
334
|
+
end
|
335
|
+
|
336
|
+
success 'done'
|
337
|
+
end
|
338
|
+
|
251
339
|
# Public: Generate an SSH key and store it in ~/.ssh/id_rsa
|
252
340
|
#
|
253
341
|
# type - The String type RSA or DSS.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Usage: <%= Array(program :name).first %> <%= @command.name %> <%= @command.syntax %>
|
2
2
|
|
3
3
|
<%= @command.description || @command.summary %>
|
4
|
-
<% if @actions.blank? or @command.root? and @command.info[:method].present? -%>
|
4
|
+
<% if @actions.blank? or @command.root? and @command.info.present? and @command.info[:method].present? -%>
|
5
5
|
|
6
6
|
<% unless @command.info[:args].blank? or @command.options.all?{ |o| o[:hide] or o[:switches].present? } -%>
|
7
7
|
|
@@ -212,7 +212,7 @@ describe RHC::Commands::App do
|
|
212
212
|
end
|
213
213
|
|
214
214
|
context 'when run with a git url' do
|
215
|
-
let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1', '--from', 'git://url'] }
|
215
|
+
let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1', '--from-code', 'git://url'] }
|
216
216
|
it { expect { run }.to exit_with_code(0) }
|
217
217
|
it { run_output.should match("Success") }
|
218
218
|
it { run_output.should match("Git remote: git:fake.foo/git/app1.git\n") }
|
@@ -466,6 +466,51 @@ describe RHC::Commands::App do
|
|
466
466
|
end
|
467
467
|
end
|
468
468
|
|
469
|
+
describe 'app create from another app' do
|
470
|
+
before(:each) do
|
471
|
+
FakeFS.deactivate!
|
472
|
+
@domain = rest_client.add_domain("mockdomain")
|
473
|
+
@app = @domain.add_application("app1", "mock_standalone_cart-1")
|
474
|
+
@app.add_alias('myfoo.com')
|
475
|
+
@cart1 = @app.add_cartridge('mock_cart-1')
|
476
|
+
@cart2 = @app.add_cartridge('mock_cart-2')
|
477
|
+
@cart2.gear_profile = 'medium'
|
478
|
+
@instance.stub(:save_snapshot)
|
479
|
+
@instance.stub(:restore_snapshot)
|
480
|
+
end
|
481
|
+
|
482
|
+
context 'when run' do
|
483
|
+
let(:arguments) { ['app', 'create', 'clone', '--from-app', 'app1', '--no-git'] }
|
484
|
+
it { expect { run }.to exit_with_code(0) }
|
485
|
+
it "should clone successfully" do
|
486
|
+
run_output.should match(/Cartridges:\s+mock_standalone_cart-1, mock_cart-1, mock_cart-2/)
|
487
|
+
run_output.should match(/Gear Size:\s+Copied from 'app1'/)
|
488
|
+
run_output.should match(/Setting deployment configuration/)
|
489
|
+
run_output.should match(/done/)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
context 'alias already registered' do
|
494
|
+
let(:arguments) { ['app', 'create', 'clone', '--from-app', 'app1', '--no-git'] }
|
495
|
+
before do
|
496
|
+
RHC::Rest::Mock::MockRestApplication.any_instance.stub(:aliases).and_return(['www.foo.com'])
|
497
|
+
end
|
498
|
+
it { expect { run }.to exit_with_code(0) }
|
499
|
+
it "should warn" do
|
500
|
+
run_output.should match(/The application 'app1' has aliases set which were not copied/)
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
context 'when run against unsupported server' do
|
505
|
+
let(:arguments) { ['app', 'create', 'clone', '--from-app', 'app1', '--no-git'] }
|
506
|
+
before { @domain.should_receive(:has_param?).with('ADD_APPLICATION','cartridges[][name]').and_return(false) }
|
507
|
+
it { expect { run }.to exit_with_code(134) }
|
508
|
+
it "should fail" do
|
509
|
+
run_output.should match(/The server does not support creating apps based on others/)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
469
514
|
describe 'app delete' do
|
470
515
|
let(:arguments) { ['app', 'delete', '--trace', '-a', 'app1', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password'] }
|
471
516
|
|
@@ -579,6 +624,41 @@ describe RHC::Commands::App do
|
|
579
624
|
it { run_output.should match(/Gears:\s+1 small/) }
|
580
625
|
it { run_output.should match(%r(From:\s+ https://foo.bar.com)) }
|
581
626
|
end
|
627
|
+
|
628
|
+
context 'when run with app with custom external cartridges' do
|
629
|
+
before do
|
630
|
+
@domain = rest_client.add_domain("mockdomain")
|
631
|
+
app = @domain.add_application("app1", "mock_type")
|
632
|
+
cart1 = app.add_cartridge('mock_cart-1')
|
633
|
+
cart1.url = 'https://foo.bar.com'
|
634
|
+
cart1.tags = ['external']
|
635
|
+
cart1.version = '2'
|
636
|
+
cart1.license = 'GPL'
|
637
|
+
cart1.website = 'http://bar.com'
|
638
|
+
cart1.current_scale = 0
|
639
|
+
end
|
640
|
+
context 'verbosely' do
|
641
|
+
let(:arguments) { ['app', 'show', 'app1', '-v'] }
|
642
|
+
it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") }
|
643
|
+
it { run_output.should match(/Gears:\s+1 small/) }
|
644
|
+
it { run_output.should match(/Gears:\s+none \(external service\)/) }
|
645
|
+
it { run_output.should match(/Description:\s+Description of mock_cart-1/) }
|
646
|
+
it { run_output.should match(%r(Website:\s+ http://bar.com)) }
|
647
|
+
it { run_output.should match(/Version:\s+2/) }
|
648
|
+
it { run_output.should match(/License:\s+GPL/) }
|
649
|
+
it { run_output.should match(%r(From:\s+ https://foo.bar.com)) }
|
650
|
+
end
|
651
|
+
context 'not verbosely' do
|
652
|
+
it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") }
|
653
|
+
it { run_output.should match(/Gears:\s+1 small/) }
|
654
|
+
it { run_output.should match(/Gears:\s+none \(external service\)/) }
|
655
|
+
it { run_output.should_not match(/Description:\s+Description of mock_cart-1/) }
|
656
|
+
it { run_output.should match(%r(Website:\s+ http://bar.com)) }
|
657
|
+
it { run_output.should_not match(/Version:\s+2/) }
|
658
|
+
it { run_output.should_not match(/License:\s+GPL/) }
|
659
|
+
it { run_output.should match(%r(From:\s+ https://foo.bar.com)) }
|
660
|
+
end
|
661
|
+
end
|
582
662
|
end
|
583
663
|
|
584
664
|
describe 'app show' do
|
@@ -26,8 +26,24 @@ describe RHC::Commands::Apps do
|
|
26
26
|
before{ domain.add_application('scaled', 'php', true) }
|
27
27
|
|
28
28
|
it { expect { run }.to exit_with_code(0) }
|
29
|
-
it
|
29
|
+
it "should match output" do
|
30
|
+
output = run_output
|
31
|
+
output.should match("You have access to 1 application\\.")
|
32
|
+
output.should match(/scaled.*\-\-.*php.*Scaling:.*x2 \(minimum/m)
|
33
|
+
end
|
30
34
|
end
|
35
|
+
|
36
|
+
context 'with one owned app' do
|
37
|
+
let(:arguments) { ['apps', '--mine'] }
|
38
|
+
before{ a = domain.add_application('scaled', 'php', true); rest_client.stub(:owned_applications).and_return([a]) }
|
39
|
+
it { expect { run }.to exit_with_code(0) }
|
40
|
+
it "should match output" do
|
41
|
+
output = run_output
|
42
|
+
output.should match("You have 1 application\\.")
|
43
|
+
output.should match(/scaled.*\-\-.*php.*Scaling:.*x2 \(minimum/m)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
31
47
|
end
|
32
48
|
|
33
49
|
context 'when help is shown' do
|
@@ -35,5 +51,29 @@ describe RHC::Commands::Apps do
|
|
35
51
|
it { expect { run }.to exit_with_code(0) }
|
36
52
|
it { run_output.should match(/rhc apps.*Display the list of applications/m) }
|
37
53
|
end
|
54
|
+
|
55
|
+
context 'when run verbose with custom external cartridges' do
|
56
|
+
let(:arguments) { ['apps', '-v'] }
|
57
|
+
before do
|
58
|
+
@domain = rest_client.add_domain("mockdomain")
|
59
|
+
app = @domain.add_application("app1", "mock_type")
|
60
|
+
cart1 = app.add_cartridge('mock_cart-1')
|
61
|
+
cart1.url = 'https://foo.bar.com'
|
62
|
+
cart1.tags = ['external']
|
63
|
+
cart1.version = '2'
|
64
|
+
cart1.license = 'GPL'
|
65
|
+
cart1.website = 'http://bar.com'
|
66
|
+
cart1.current_scale = 0
|
67
|
+
end
|
68
|
+
it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") }
|
69
|
+
it { run_output.should match(/Gears:\s+1 small/) }
|
70
|
+
it { run_output.should match(/Gears:\s+none \(external service\)/) }
|
71
|
+
it { run_output.should match(/Description:\s+Description of mock_cart-1/) }
|
72
|
+
it { run_output.should match(%r(Website:\s+ http://bar.com)) }
|
73
|
+
it { run_output.should match(/Version:\s+2/) }
|
74
|
+
it { run_output.should match(/License:\s+GPL/) }
|
75
|
+
it { run_output.should match(%r(From:\s+ https://foo.bar.com)) }
|
76
|
+
end
|
77
|
+
|
38
78
|
end
|
39
79
|
end
|
@@ -63,14 +63,25 @@ describe RHC::Commands::Scp do
|
|
63
63
|
it { run_output.should match("The connection to 127.0.0.1 failed: SocketError") }
|
64
64
|
end
|
65
65
|
|
66
|
-
|
66
|
+
|
67
|
+
context 'authentication error' do
|
68
|
+
before(:each) do
|
69
|
+
@domain = rest_client.add_domain("mockdomain")
|
70
|
+
@domain.add_application("app1", "mock_type")
|
71
|
+
File.should_receive(:exist?).with("file.txt").once.and_return(true)
|
72
|
+
Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(Net::SSH::AuthenticationFailed)
|
73
|
+
end
|
74
|
+
it { run_output.should match("Authentication to server 127.0.0.1 with user fakeuuidfortestsapp1 failed") }
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'unknown error' do
|
67
78
|
before(:each) do
|
68
79
|
@domain = rest_client.add_domain("mockdomain")
|
69
80
|
@domain.add_application("app1", "mock_type")
|
70
81
|
File.should_receive(:exist?).with("file.txt").once.and_return(true)
|
71
|
-
Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(Net::SCP::Error)
|
82
|
+
Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(Net::SCP::Error.new("SCP error message"))
|
72
83
|
end
|
73
|
-
it { run_output.should match("
|
84
|
+
it { run_output.should match("An unknown error occurred: SCP error message") }
|
74
85
|
end
|
75
86
|
end
|
76
87
|
|
@@ -57,7 +57,7 @@ describe RHC::Commands::Snapshot do
|
|
57
57
|
ssh.should_receive(:exec!).with("snapshot").and_yield(nil, :stdout, 'foo').and_yield(nil, :stderr, 'foo')
|
58
58
|
end
|
59
59
|
it { expect { run }.to exit_with_code(0) }
|
60
|
-
it { run_output.should match("
|
60
|
+
it { run_output.should match("done") }
|
61
61
|
end
|
62
62
|
|
63
63
|
context 'when timing out on windows' do
|
@@ -176,4 +176,3 @@ describe RHC::Commands::Snapshot do
|
|
176
176
|
it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable '#{@targz_filename}' is not executable./) }
|
177
177
|
end
|
178
178
|
end
|
179
|
-
|
@@ -316,6 +316,29 @@ module RHC
|
|
316
316
|
end
|
317
317
|
end
|
318
318
|
|
319
|
+
context "when server supports LIST_APPLICATIONS_BY_OWNER" do
|
320
|
+
let(:api_links){ client_links.merge!(mock_response_links([['LIST_APPLICATIONS_BY_OWNER', 'applications', 'get']])) }
|
321
|
+
before do
|
322
|
+
stub_api_request(:any, "#{api_links['LIST_APPLICATIONS_BY_OWNER']['relative']}?owner=@self").
|
323
|
+
to_return({ :body => {
|
324
|
+
:type => 'applications',
|
325
|
+
:data => [{
|
326
|
+
:name => 'mock_application_0',
|
327
|
+
:links => mock_response_links(mock_app_links('mock_domain_0', 'mock_application_0')),
|
328
|
+
}]
|
329
|
+
}.to_json,
|
330
|
+
:status => 200
|
331
|
+
})
|
332
|
+
end
|
333
|
+
it "returns owned applications when called" do
|
334
|
+
match = nil
|
335
|
+
expect { match = client.owned_applications }.to_not raise_error
|
336
|
+
match.length.should == 1
|
337
|
+
match.first.name.should == 'mock_application_0'
|
338
|
+
match.first.class.should == RHC::Rest::Application
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
319
342
|
context "find_application" do
|
320
343
|
let(:mock_domain){ 'mock_domain_0' }
|
321
344
|
let(:mock_app){ 'mock_app' }
|
data/spec/spec_helper.rb
CHANGED
@@ -233,6 +233,7 @@ module CommandHelpers
|
|
233
233
|
$terminal.close_write
|
234
234
|
run!(*args)
|
235
235
|
end
|
236
|
+
|
236
237
|
def command_output(args=arguments)
|
237
238
|
run_command(args)
|
238
239
|
rescue SystemExit => e
|
@@ -553,6 +554,41 @@ def mac?
|
|
553
554
|
RbConfig::CONFIG['host_os'] =~ /^darwin/
|
554
555
|
end
|
555
556
|
|
557
|
+
def mock_save_snapshot_file(app)
|
558
|
+
@ssh_uri = URI.parse app.ssh_url
|
559
|
+
mock_popen3("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'snapshot' > #{@app.name}.tar.gz", nil,
|
560
|
+
"Pulling down a snapshot of application '#{app.name}' to #{app.name}.tar.gz", nil)
|
561
|
+
end
|
562
|
+
|
563
|
+
def mock_restore_snapshot_file(app)
|
564
|
+
@ssh_uri = URI.parse app.ssh_url
|
565
|
+
File.stub(:exists?).and_return(true)
|
566
|
+
RHC::TarGz.stub(:contains).and_return(true)
|
567
|
+
in_mock, out_mock, err_mock =
|
568
|
+
mock_popen3("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'restore INCLUDE_GIT'", nil, "Restoring from snapshot #{app.name}.tar.gz to application '#{app.name}'", nil)
|
569
|
+
lines = ''
|
570
|
+
File.open(File.expand_path('../rhc/assets/targz_sample.tar.gz', __FILE__), 'rb') do |file|
|
571
|
+
file.chunk(4096) do |chunk|
|
572
|
+
lines << chunk
|
573
|
+
end
|
574
|
+
end
|
575
|
+
in_mock.stub(:write)
|
576
|
+
in_mock.stub(:close_write)
|
577
|
+
end
|
578
|
+
|
579
|
+
def mock_popen3(cmd, std_in, std_out, std_err)
|
580
|
+
in_mock = double('std in')
|
581
|
+
out_mock = double('std out')
|
582
|
+
err_mock = double('std err')
|
583
|
+
in_mock.stub(:gets).and_return(std_in)
|
584
|
+
out_mock.stub(:gets).and_return(std_out)
|
585
|
+
err_mock.stub(:gets).and_return(std_err)
|
586
|
+
Open3.stub(:popen3)
|
587
|
+
Open3.should_receive(:popen3).with(cmd).and_yield(in_mock, out_mock, err_mock)
|
588
|
+
return in_mock, out_mock, err_mock
|
589
|
+
end
|
590
|
+
|
591
|
+
|
556
592
|
RSpec.configure do |config|
|
557
593
|
config.include(ExitCodeMatchers)
|
558
594
|
config.include(CommanderInvocationMatchers)
|
metadata
CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 22
|
9
|
+
- 5
|
10
|
+
version: 1.22.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Red Hat
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2014-
|
18
|
+
date: 2014-04-07 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: net-ssh
|
@@ -389,6 +389,7 @@ files:
|
|
389
389
|
- features/members_feature.rb
|
390
390
|
- features/assets/deploy.tar.gz
|
391
391
|
- features/deployments_feature.rb
|
392
|
+
- features/app_feature.rb
|
392
393
|
- features/core_feature.rb
|
393
394
|
- features/keys_feature.rb
|
394
395
|
- features/domains_feature.rb
|
@@ -484,6 +485,7 @@ test_files:
|
|
484
485
|
- features/members_feature.rb
|
485
486
|
- features/assets/deploy.tar.gz
|
486
487
|
- features/deployments_feature.rb
|
488
|
+
- features/app_feature.rb
|
487
489
|
- features/core_feature.rb
|
488
490
|
- features/keys_feature.rb
|
489
491
|
- features/domains_feature.rb
|