rhc 1.21.3 → 1.22.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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="env snapshot create delete start stop scale-up scale-down force-stop restart reload tidy show deploy configure"
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
@@ -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(:all) do
162
+ before(:each) do
163
163
  standard_config
164
164
  @app = has_a_scalable_application
165
165
  end
166
166
 
167
- after(:all) do
167
+ after(:each) do
168
168
  debug.puts "cleaning up scalable app" if debug?
169
169
  @app.destroy
170
170
  end
@@ -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.downcase == name} || use_cart(RHC::Rest::Cartridge.for_url(name), name) if name =~ %r(\Ahttps?://)i
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
@@ -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
- ["Gear Size:", options.gear_size || "default"],
99
- ["Scaling:", options.scaling ? "yes" : "no"],
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, options.scaling, options.from_code, env, options.auto_deploy, options.keep_deployments, options.deployment_branch)
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
- if options.git
166
- section(:now => true, :top => 1, :bottom => 1) do
167
- begin
168
- if has_git?
169
- repo_dir = git_clone_application(rest_app)
170
- else
171
- warn "You do not have git installed, so your application's git repo will not be cloned"
172
- end
173
- rescue RHC::GitException => e
174
- warn "#{e}"
175
- unless RHC::Helpers.windows? and windows_nslookup_bug?(rest_app)
176
- add_issue("We were unable to clone your application's git repo - #{e}",
177
- "Clone your git repo",
178
- "rhc git-clone #{rest_app.name}")
179
- end
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{ |item| item.to_hash } if environment_variables.present?
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
- rest_app = rest_domain.add_application(name, app_options)
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
@@ -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 = rest_client.applications(:include => :cartridges).sort
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} applications"
20
+ success "You have#{options.mine ? '' : ' access to'} #{pluralize(applications.length, 'application')}."
17
21
  0
18
22
  end
19
23
  end
@@ -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", "--transfer_direction upload|download"], :optional => false
25
- argument :local_path, "Local filesystem path", ["-l", "--local_path file_path"], :optional => false
26
- argument :remote_path, "Remote filesystem path", ["-r", "--remote_path file_path"], :optional => false
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
- raise RHC::RemoteFileOrPathNotFound.new("Remote file, file_path, or directory could not be found.")
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
- ssh = check_ssh_executable! options.ssh
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
- snapshot_cmd = options.deployment ? 'gear archive-deployment' : 'snapshot'
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
@@ -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 options.app
79
- if options.app =~ /\//
80
- options.app.split(/\//)
79
+ if option
80
+ if option =~ /\//
81
+ option.split(/\//)
81
82
  else
82
- [options.namespace || namespace_context, options.app]
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
@@ -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
@@ -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
- get_properties(cart, *properties).
90
- concat([[:downloaded_cartridge_url, cart.url]]).
91
- concat([[cart.scalable? ? :scaling : :gears, format_cart_gears(cart)]]).
92
- concat(cart.properties.map{ |p| ["#{table_heading(p['name'])}:", p['value']] }.sort{ |a,b| a[0] <=> b[0] }).
93
- concat(cart.environment_variables.present? ? [[:environment_variables, cart.environment_variables.map{|item| "#{item[:name]}=#{item[:value]}" }.sort.join(', ')]] : []),
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?
@@ -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
@@ -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 namesapce
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)
@@ -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
 
@@ -1,5 +1,6 @@
1
1
  require 'open4'
2
2
  require 'rhc/helpers'
3
+ require 'tmpdir'
3
4
 
4
5
  $source_bin_rhc = File.expand_path('bin/rhc')
5
6
 
@@ -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 { run_output.should match(/scaled.*\-\-.*php.*Scaling:.*x2 \(minimum/m) }
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
- context 'remote file not found' do
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("Remote file, file_path, or directory could not be found.") }
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("Success") }
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
- - 21
9
- - 3
10
- version: 1.21.3
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-03-10 00:00:00 Z
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