bosh_cli 0.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/README +4 -0
  2. data/Rakefile +55 -0
  3. data/bin/bosh +17 -0
  4. data/lib/cli.rb +76 -0
  5. data/lib/cli/cache.rb +44 -0
  6. data/lib/cli/changeset_helper.rb +142 -0
  7. data/lib/cli/command_definition.rb +52 -0
  8. data/lib/cli/commands/base.rb +245 -0
  9. data/lib/cli/commands/biff.rb +300 -0
  10. data/lib/cli/commands/blob.rb +125 -0
  11. data/lib/cli/commands/cloudcheck.rb +169 -0
  12. data/lib/cli/commands/deployment.rb +147 -0
  13. data/lib/cli/commands/job.rb +42 -0
  14. data/lib/cli/commands/job_management.rb +117 -0
  15. data/lib/cli/commands/log_management.rb +81 -0
  16. data/lib/cli/commands/maintenance.rb +131 -0
  17. data/lib/cli/commands/misc.rb +240 -0
  18. data/lib/cli/commands/package.rb +112 -0
  19. data/lib/cli/commands/property_management.rb +125 -0
  20. data/lib/cli/commands/release.rb +469 -0
  21. data/lib/cli/commands/ssh.rb +271 -0
  22. data/lib/cli/commands/stemcell.rb +184 -0
  23. data/lib/cli/commands/task.rb +213 -0
  24. data/lib/cli/commands/user.rb +28 -0
  25. data/lib/cli/commands/vms.rb +53 -0
  26. data/lib/cli/config.rb +154 -0
  27. data/lib/cli/core_ext.rb +145 -0
  28. data/lib/cli/dependency_helper.rb +62 -0
  29. data/lib/cli/deployment_helper.rb +263 -0
  30. data/lib/cli/deployment_manifest_compiler.rb +28 -0
  31. data/lib/cli/director.rb +633 -0
  32. data/lib/cli/director_task.rb +64 -0
  33. data/lib/cli/errors.rb +48 -0
  34. data/lib/cli/event_log_renderer.rb +351 -0
  35. data/lib/cli/job_builder.rb +226 -0
  36. data/lib/cli/package_builder.rb +254 -0
  37. data/lib/cli/packaging_helper.rb +248 -0
  38. data/lib/cli/release.rb +176 -0
  39. data/lib/cli/release_builder.rb +215 -0
  40. data/lib/cli/release_compiler.rb +178 -0
  41. data/lib/cli/release_tarball.rb +272 -0
  42. data/lib/cli/runner.rb +771 -0
  43. data/lib/cli/stemcell.rb +83 -0
  44. data/lib/cli/task_log_renderer.rb +40 -0
  45. data/lib/cli/templates/help_message.erb +75 -0
  46. data/lib/cli/validation.rb +42 -0
  47. data/lib/cli/version.rb +7 -0
  48. data/lib/cli/version_calc.rb +48 -0
  49. data/lib/cli/versions_index.rb +126 -0
  50. data/lib/cli/yaml_helper.rb +62 -0
  51. data/spec/assets/biff/bad_gateway_config.yml +28 -0
  52. data/spec/assets/biff/good_simple_config.yml +63 -0
  53. data/spec/assets/biff/good_simple_golden_config.yml +63 -0
  54. data/spec/assets/biff/good_simple_template.erb +69 -0
  55. data/spec/assets/biff/multiple_subnets_config.yml +40 -0
  56. data/spec/assets/biff/network_only_template.erb +34 -0
  57. data/spec/assets/biff/no_cc_config.yml +27 -0
  58. data/spec/assets/biff/no_range_config.yml +27 -0
  59. data/spec/assets/biff/no_subnet_config.yml +16 -0
  60. data/spec/assets/biff/ok_network_config.yml +30 -0
  61. data/spec/assets/biff/properties_template.erb +6 -0
  62. data/spec/assets/deployment.MF +0 -0
  63. data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
  64. data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
  65. data/spec/assets/release/jobs/cacher.tgz +0 -0
  66. data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
  67. data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
  68. data/spec/assets/release/jobs/cacher/job.MF +6 -0
  69. data/spec/assets/release/jobs/cacher/monit +1 -0
  70. data/spec/assets/release/jobs/cleaner.tgz +0 -0
  71. data/spec/assets/release/jobs/cleaner/job.MF +4 -0
  72. data/spec/assets/release/jobs/cleaner/monit +1 -0
  73. data/spec/assets/release/jobs/sweeper.tgz +0 -0
  74. data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
  75. data/spec/assets/release/jobs/sweeper/job.MF +5 -0
  76. data/spec/assets/release/jobs/sweeper/monit +1 -0
  77. data/spec/assets/release/packages/mutator.tar.gz +0 -0
  78. data/spec/assets/release/packages/stuff.tgz +0 -0
  79. data/spec/assets/release/release.MF +17 -0
  80. data/spec/assets/release_invalid_checksum.tgz +0 -0
  81. data/spec/assets/release_invalid_jobs.tgz +0 -0
  82. data/spec/assets/release_no_name.tgz +0 -0
  83. data/spec/assets/release_no_version.tgz +0 -0
  84. data/spec/assets/stemcell/image +1 -0
  85. data/spec/assets/stemcell/stemcell.MF +6 -0
  86. data/spec/assets/stemcell_invalid_mf.tgz +0 -0
  87. data/spec/assets/stemcell_no_image.tgz +0 -0
  88. data/spec/assets/valid_release.tgz +0 -0
  89. data/spec/assets/valid_stemcell.tgz +0 -0
  90. data/spec/spec_helper.rb +25 -0
  91. data/spec/unit/base_command_spec.rb +66 -0
  92. data/spec/unit/biff_spec.rb +135 -0
  93. data/spec/unit/cache_spec.rb +36 -0
  94. data/spec/unit/cli_commands_spec.rb +481 -0
  95. data/spec/unit/config_spec.rb +139 -0
  96. data/spec/unit/core_ext_spec.rb +77 -0
  97. data/spec/unit/dependency_helper_spec.rb +52 -0
  98. data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
  99. data/spec/unit/director_spec.rb +511 -0
  100. data/spec/unit/director_task_spec.rb +48 -0
  101. data/spec/unit/event_log_renderer_spec.rb +171 -0
  102. data/spec/unit/hash_changeset_spec.rb +73 -0
  103. data/spec/unit/job_builder_spec.rb +454 -0
  104. data/spec/unit/package_builder_spec.rb +567 -0
  105. data/spec/unit/release_builder_spec.rb +65 -0
  106. data/spec/unit/release_spec.rb +66 -0
  107. data/spec/unit/release_tarball_spec.rb +33 -0
  108. data/spec/unit/runner_spec.rb +140 -0
  109. data/spec/unit/ssh_spec.rb +78 -0
  110. data/spec/unit/stemcell_spec.rb +17 -0
  111. data/spec/unit/version_calc_spec.rb +27 -0
  112. data/spec/unit/versions_index_spec.rb +132 -0
  113. metadata +338 -0
@@ -0,0 +1,24 @@
1
+ module Bosh::Cli::Command
2
+ class Ruby < Base
3
+
4
+ command :ruby_version do
5
+ usage "ruby version"
6
+ desc "Say ruby version"
7
+ route :ruby, :ruby_version
8
+ end
9
+
10
+ command :ruby_config do
11
+ usage "ruby config <string>"
12
+ desc "Query rbconfig"
13
+ route :ruby, :ruby_config
14
+ end
15
+
16
+ def ruby_version
17
+ say(RUBY_VERSION)
18
+ end
19
+
20
+ def ruby_config(string)
21
+ say(::Config::CONFIG[string])
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ ---
2
+ packages:
3
+ - stuff
4
+ templates:
5
+ file1.conf: config/file1.conf
6
+ file2.conf: config/file2.conf
@@ -0,0 +1 @@
1
+ Monit conf goes here
@@ -0,0 +1,4 @@
1
+ ---
2
+ packages:
3
+ - stuff
4
+ - mutator
@@ -0,0 +1 @@
1
+ Monit conf goes here
@@ -0,0 +1,5 @@
1
+ ---
2
+ packages:
3
+ - mutator
4
+ templates:
5
+ test.conf: config/test
@@ -0,0 +1 @@
1
+ Monit conf goes here
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: appcloud
3
+ version: 0.1
4
+
5
+ packages:
6
+ - name: stuff
7
+ version: 0.1.17
8
+ sha1: 6bbd4a12e3a59e10b96ecb9aac3d73ec4f819783
9
+ - name: mutator
10
+ version: 2.99.7
11
+ sha1: 86bd8b15562cde007f030a303fa64779af5fa4e7
12
+
13
+ jobs:
14
+ - cacher
15
+ - cleaner
16
+ - sweeper
17
+
@@ -0,0 +1 @@
1
+ STEMCELL
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: ubuntu-stemcell
3
+ version: 1
4
+ cloud_properties:
5
+ property1: test
6
+ property2: test
@@ -0,0 +1,25 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "rspec/core"
4
+
5
+ $:.unshift(File.expand_path("../../lib", __FILE__))
6
+ require "cli"
7
+
8
+ def spec_asset(filename)
9
+ File.expand_path(File.join(File.dirname(__FILE__), "assets", filename))
10
+ end
11
+
12
+ tmpdir = Dir.mktmpdir
13
+ ENV["TMPDIR"] = tmpdir
14
+ FileUtils.mkdir_p(tmpdir)
15
+ at_exit { FileUtils.rm_rf(tmpdir) }
16
+
17
+ RSpec.configure do |c|
18
+ c.before(:each) do
19
+ Bosh::Cli::Config.interactive = false
20
+ Bosh::Cli::Config.colorize = false
21
+ Bosh::Cli::Config.output = StringIO.new
22
+ end
23
+
24
+ c.color_enabled = true
25
+ end
@@ -0,0 +1,66 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "spec_helper"
4
+
5
+ describe Bosh::Cli::Command::Base do
6
+
7
+ before :each do
8
+ @config = File.join(Dir.mktmpdir, "bosh_config")
9
+ @cache_dir = Dir.mktmpdir
10
+ end
11
+
12
+ def add_config(object)
13
+ File.open(@config, "w") do |f|
14
+ f.write(YAML.dump(object))
15
+ end
16
+ end
17
+
18
+ def make_command(options = { })
19
+ Bosh::Cli::Command::Base.new({ :config => @config,
20
+ :cache_dir => @cache_dir }.merge(options))
21
+ end
22
+
23
+ it "can access configuration and respects options" do
24
+ add_config("target" => "localhost:8080", "deployment" => "test")
25
+
26
+ cmd = make_command(:verbose => true, :dry_run => true)
27
+ cmd.verbose?.should be_true
28
+ cmd.dry_run?.should be_true
29
+ cmd.target.should == "localhost:8080"
30
+ cmd.deployment.should == "test"
31
+ cmd.username.should == nil
32
+ cmd.password.should == nil
33
+ end
34
+
35
+ it "instantiates director when needed" do
36
+ add_config("target" => "localhost:8080", "deployment" => "test")
37
+
38
+ cmd = make_command()
39
+ cmd.director.director_uri.should == "localhost:8080"
40
+ end
41
+
42
+ it "can evaluate other commands" do
43
+ cmd = make_command
44
+ new_cmd = mock(Object)
45
+
46
+ Bosh::Cli::Command::Misc.should_receive(:new).and_return(new_cmd)
47
+ new_cmd.should_receive(:status).with(:arg1, :arg2)
48
+
49
+ cmd.run("misc", "status", :arg1, :arg2)
50
+ end
51
+
52
+ it "can redirect to other commands " +
53
+ "(effectively exiting after running them)" do
54
+ cmd = make_command
55
+ new_cmd = mock(Object)
56
+
57
+ Bosh::Cli::Command::Misc.should_receive(:new).and_return(new_cmd)
58
+ new_cmd.should_receive(:status).with(:arg1, :arg2)
59
+
60
+ lambda {
61
+ cmd.redirect("misc", "status", :arg1, :arg2)
62
+ }.should raise_error(Bosh::Cli::GracefulExit,
63
+ "redirected to misc status arg1 arg2")
64
+ end
65
+
66
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bosh::Cli::Command::Biff do
4
+
5
+ before :each do
6
+ # Let us test all private methods.
7
+ Bosh::Cli::Command::Biff.send(:public,
8
+ *Bosh::Cli::Command::Biff.private_instance_methods)
9
+ @biff = Bosh::Cli::Command::Biff.new
10
+ end
11
+
12
+ after :each do
13
+ @biff.delete_temp_diff_files
14
+ end
15
+
16
+ it "throws an error when there is more than one subnet for default" do
17
+ config_file = spec_asset("biff/multiple_subnets_config.yml")
18
+ template_file = spec_asset("biff/network_only_template.erb")
19
+ @biff.stub!(:deployment).and_return(config_file)
20
+ lambda {
21
+ @biff.biff(template_file)
22
+ }.should raise_error(RuntimeError, "Biff doesn't know how to deal with " +
23
+ "anything other than one subnet in default")
24
+ end
25
+
26
+ it "throws an error when the gateway is anything other than the first ip" do
27
+ config_file = spec_asset("biff/bad_gateway_config.yml")
28
+ template_file = spec_asset("biff/network_only_template.erb")
29
+ @biff.stub!(:deployment).and_return(config_file)
30
+ lambda {
31
+ @biff.biff(template_file)
32
+ }.should raise_error(RuntimeError, "Biff only supports configurations " +
33
+ "where the gateway is the first IP (e.g. 172.31.196.1).")
34
+ end
35
+
36
+ it "throws an error when the range is not specified in the config" do
37
+ config_file = spec_asset("biff/no_range_config.yml")
38
+ template_file = spec_asset("biff/network_only_template.erb")
39
+ @biff.stub!(:deployment).and_return(config_file)
40
+ lambda {
41
+ @biff.biff(template_file)
42
+ }.should raise_error(RuntimeError, "Biff requires each network to have " +
43
+ "range and dns entries.")
44
+ end
45
+
46
+ it "throws an error if there are no subnets" do
47
+ config_file = spec_asset("biff/no_subnet_config.yml")
48
+ template_file = spec_asset("biff/network_only_template.erb")
49
+ @biff.stub!(:deployment).and_return(config_file)
50
+ lambda {
51
+ @biff.biff(template_file)
52
+ }.should raise_error(RuntimeError, "You must have subnets in default")
53
+ end
54
+
55
+ it "outputs the required yaml when the input does not contain it" do
56
+ config_file = spec_asset("biff/no_cc_config.yml")
57
+ template_file = spec_asset("biff/properties_template.erb")
58
+ @biff.stub!(:deployment).and_return(config_file)
59
+
60
+ @biff.should_receive(:say).with(
61
+ "Could not find properties.cc.srv_api_uri.").once
62
+
63
+ @biff.should_receive(:say).with("'#{template_file}' has it but " +
64
+ "'#{config_file}' does not.").once
65
+
66
+ # Cannot use this because 1.8.7 does not preserve Hash order, so this string
67
+ # can come back in any order.
68
+ @biff.should_receive(:say).with(/Add this to '':/).once
69
+
70
+ #@biff.should_receive(:say).once
71
+
72
+ @biff.should_receive(:say).with("There were 1 errors.").once
73
+
74
+ @biff.biff(template_file)
75
+ end
76
+
77
+ it "correctly generates a file and reports when there are no differences" do
78
+ config_file = spec_asset("biff/good_simple_config.yml")
79
+ template_file = spec_asset("biff/good_simple_template.erb")
80
+ golden_file = spec_asset("biff/good_simple_golden_config.yml")
81
+ @biff.stub!(:deployment).and_return(config_file)
82
+ @biff.should_receive(:say).with("No differences.").once
83
+
84
+ @biff.biff(template_file)
85
+ @biff.template_output.should == File.read(golden_file)
86
+ end
87
+
88
+ it "asks whether you would like to keep the new file" do
89
+ config_file = spec_asset("biff/ok_network_config.yml")
90
+ template_file = spec_asset("biff/network_only_template.erb")
91
+ @biff.stub!(:deployment).and_return(config_file)
92
+ @biff.should_receive(:agree).with(
93
+ "Would you like to keep the new version? [yn]").once.and_return(false)
94
+
95
+ @biff.biff(template_file)
96
+ end
97
+
98
+ it "deletes temporary files" do
99
+ config_file = spec_asset("biff/ok_network_config.yml")
100
+ template_file = spec_asset("biff/network_only_template.erb")
101
+ @biff.stub!(:deployment).and_return(config_file)
102
+ @biff.should_receive(:agree).and_return(false)
103
+ # Twice because of after :each
104
+ @biff.should_receive(:delete_temp_diff_files).twice
105
+ @biff.biff(template_file)
106
+ end
107
+
108
+ it "finds the object path" do
109
+ obj = { "path1" => {"path2" => {"path3" => 3}} }
110
+ @biff.find_in("path1.path2.path3", obj).should == 3
111
+ end
112
+
113
+ it "finds the object path in an array by the name key" do
114
+ obj = { "by_key" => 1, "arr" => [{"name" => "by_name"}]}
115
+ @biff.find_in("arr.by_name", obj).should == {"name" => "by_name"}
116
+ end
117
+
118
+ it "allows ip_range to take negative ranges" do
119
+ @biff.ip_helper = {
120
+ "default" => NetAddr::CIDR.create("192.168.1.0/22")
121
+ }
122
+ @biff.ip_range(-11..-2, "default").should == "192.168.3.245 - 192.168.3.254"
123
+ end
124
+
125
+ it "deletes all except one entry from a Hash" do
126
+ obj = { "by_key" => 1, "arr" => [{"name" => "by_name"}]}
127
+ @biff.delete_all_except(obj, "by_key").should == { "by_key" => 1 }
128
+ end
129
+
130
+ it "deletes all except one entry from a Array" do
131
+ obj = [ {"name" => "a"}, {"name" => "b"}, {"name" => "c"}]
132
+ @biff.delete_all_except(obj, "b").should == [ {"name" => "b"} ]
133
+ end
134
+
135
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require 'spec_helper'
4
+ require 'fileutils'
5
+
6
+ describe Bosh::Cli::Cache do
7
+
8
+ before :each do
9
+ @cache_dir = File.join(Dir.mktmpdir, "bosh_cache")
10
+ end
11
+
12
+ after :each do
13
+ FileUtils.rm_rf(@cache_dir)
14
+ end
15
+
16
+ it "whines if cache directory turned out to be a file" do
17
+ FileUtils.touch(@cache_dir)
18
+ lambda {
19
+ Bosh::Cli::Cache.new(@cache_dir)
20
+ }.should raise_error(Bosh::Cli::CacheDirectoryError)
21
+ end
22
+
23
+ it "creates cache directory" do
24
+ File.directory?(@cache_dir).should be_false
25
+ cache = Bosh::Cli::Cache.new(@cache_dir)
26
+ File.directory?(@cache_dir).should be_true
27
+ end
28
+
29
+ it "performs read/write in an expected way" do
30
+ cache = Bosh::Cli::Cache.new(@cache_dir)
31
+ cache.read("foo").should be_nil
32
+ cache.write("foo", "12321")
33
+ cache.read("foo").should == "12321"
34
+ end
35
+
36
+ end
@@ -0,0 +1,481 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "spec_helper"
4
+
5
+ describe Bosh::Cli::Command::Base do
6
+
7
+ before :each do
8
+ @config = File.join(Dir.mktmpdir, "bosh_config")
9
+ @cache = File.join(Dir.mktmpdir, "bosh_cache")
10
+ @opts = { :config => @config, :cache_dir => @cache }
11
+ end
12
+
13
+ describe Bosh::Cli::Command::Misc do
14
+
15
+ before :each do
16
+ @cmd = Bosh::Cli::Command::Misc.new(@opts)
17
+ @cmd.stub!(:interactive?).and_return(false)
18
+ end
19
+
20
+ it "sets the target" do
21
+ @cmd.target.should == nil
22
+ @cmd.set_target("http://example.com:232")
23
+ @cmd.target.should == "http://example.com:232"
24
+ end
25
+
26
+ it "normalizes target" do
27
+ @cmd.target.should == nil
28
+ @cmd.set_target("test")
29
+ @cmd.target.should == "http://test"
30
+ end
31
+
32
+ it "respects director checks option when setting target" do
33
+ @cmd.options[:director_checks] = true
34
+
35
+ lambda {
36
+ mock_director = mock(Object)
37
+ mock_director.stub!(:get_status).and_raise(Bosh::Cli::DirectorError)
38
+ Bosh::Cli::Director.should_receive(:new).
39
+ with("http://test").and_return(mock_director)
40
+ @cmd.set_target("test")
41
+ }.should raise_error(Bosh::Cli::CliExit,
42
+ "Cannot talk to director at 'http://test', " +
43
+ "please set correct target")
44
+
45
+ @cmd.target.should == nil
46
+
47
+ mock_director = mock(Object)
48
+ mock_director.stub!(:get_status).and_return("name" => "ZB")
49
+ Bosh::Cli::Director.should_receive(:new).
50
+ with("http://test").and_return(mock_director)
51
+ @cmd.set_target("test")
52
+ @cmd.target.should == "http://test"
53
+ end
54
+
55
+ it "supports named targets" do
56
+ @cmd.set_target("test", "mytarget")
57
+ @cmd.target.should == "http://test"
58
+
59
+ @cmd.set_target("foo", "myfoo")
60
+
61
+ @cmd.set_target("mytarget")
62
+ @cmd.target.should == "http://test"
63
+
64
+ @cmd.set_target("myfoo")
65
+ @cmd.target.should == "http://foo"
66
+ end
67
+
68
+ it "logs user in" do
69
+ @cmd.set_target("test")
70
+ @cmd.login("user", "pass")
71
+ @cmd.logged_in?.should be_true
72
+ @cmd.username.should == "user"
73
+ @cmd.password.should == "pass"
74
+ end
75
+
76
+ it "logs user out" do
77
+ @cmd.set_target("test")
78
+ @cmd.login("user", "pass")
79
+ @cmd.logout
80
+ @cmd.logged_in?.should be_false
81
+ end
82
+
83
+ it "respects director checks option when logging in" do
84
+ @cmd.options[:director_checks] = true
85
+
86
+ mock_director = mock(Object)
87
+ mock_director.stub(:get_status).
88
+ and_return({ "user" => "user", "name" => "ZB" })
89
+ mock_director.stub(:authenticated?).and_return(true)
90
+
91
+ Bosh::Cli::Director.should_receive(:new).
92
+ with("http://test").and_return(mock_director)
93
+ @cmd.set_target("test")
94
+
95
+ Bosh::Cli::Director.should_receive(:new).
96
+ with("http://test", "user", "pass").and_return(mock_director)
97
+
98
+ @cmd.login("user", "pass")
99
+ @cmd.logged_in?.should be_true
100
+ @cmd.username.should == "user"
101
+ @cmd.password.should == "pass"
102
+ end
103
+ end
104
+
105
+ describe Bosh::Cli::Command::Stemcell do
106
+ before :each do
107
+ @cmd = Bosh::Cli::Command::Stemcell.new(@opts)
108
+ end
109
+
110
+ it "allows deleting the stemcell" do
111
+ mock_director = mock(Bosh::Cli::Director)
112
+ mock_director.should_receive(:delete_stemcell).with("foo", "123")
113
+
114
+ @cmd.stub!(:interactive?).and_return(false)
115
+ @cmd.stub!(:target).and_return("test")
116
+ @cmd.stub!(:username).and_return("user")
117
+ @cmd.stub!(:password).and_return("pass")
118
+ @cmd.stub!(:director).and_return(mock_director)
119
+ @cmd.delete("foo", "123")
120
+ end
121
+
122
+ it "needs confirmation to delete stemcell" do
123
+ mock_director = mock(Bosh::Cli::Director)
124
+ mock_director.should_not_receive(:delete_stemcell)
125
+
126
+ @cmd.stub!(:target).and_return("test")
127
+ @cmd.stub!(:username).and_return("user")
128
+ @cmd.stub!(:password).and_return("pass")
129
+ @cmd.stub!(:director).and_return(mock_director)
130
+ @cmd.stub!(:ask).and_return("")
131
+ @cmd.delete("foo", "123")
132
+ end
133
+ end
134
+
135
+ describe Bosh::Cli::Command::Deployment do
136
+ before :each do
137
+ @cmd = Bosh::Cli::Command::Deployment.new(@opts)
138
+ end
139
+
140
+ it "allows deleting the deployment" do
141
+ mock_director = mock(Bosh::Cli::Director)
142
+ mock_director.should_receive(:delete_deployment).
143
+ with("foo", :force => false)
144
+
145
+ @cmd.stub!(:interactive?).and_return(false)
146
+ @cmd.stub!(:target).and_return("test")
147
+ @cmd.stub!(:username).and_return("user")
148
+ @cmd.stub!(:password).and_return("pass")
149
+ @cmd.stub!(:director).and_return(mock_director)
150
+ @cmd.delete("foo")
151
+ end
152
+
153
+ it "needs confirmation to delete deployment" do
154
+ mock_director = mock(Bosh::Cli::Director)
155
+ mock_director.should_not_receive(:delete_deployment)
156
+
157
+ @cmd.stub!(:target).and_return("test")
158
+ @cmd.stub!(:username).and_return("user")
159
+ @cmd.stub!(:password).and_return("pass")
160
+ @cmd.stub!(:director).and_return(mock_director)
161
+ @cmd.stub!(:ask).and_return("")
162
+ @cmd.delete("foo")
163
+ end
164
+ end
165
+
166
+ describe Bosh::Cli::Command::Release do
167
+ before :each do
168
+ @cmd = Bosh::Cli::Command::Release.new(@opts)
169
+ @cmd.stub!(:target).and_return("test")
170
+ @cmd.stub!(:username).and_return("user")
171
+ @cmd.stub!(:password).and_return("pass")
172
+ end
173
+
174
+ it "allows deleting the release (non-force)" do
175
+ mock_director = mock(Bosh::Cli::Director)
176
+ mock_director.should_receive(:delete_release).
177
+ with("foo", :force => false, :version => nil)
178
+
179
+ @cmd.stub!(:interactive?).and_return(false)
180
+ @cmd.stub!(:director).and_return(mock_director)
181
+ @cmd.delete("foo")
182
+ end
183
+
184
+ it "allows deleting the release (non-force)" do
185
+ mock_director = mock(Bosh::Cli::Director)
186
+ mock_director.should_receive(:delete_release).
187
+ with("foo", :force => true, :version => nil)
188
+
189
+ @cmd.stub!(:ask).and_return("yes")
190
+ @cmd.stub!(:director).and_return(mock_director)
191
+ @cmd.delete("foo", "--force")
192
+ end
193
+
194
+ it "allows deleting a particular release version (non-force)" do
195
+ mock_director = mock(Bosh::Cli::Director)
196
+ mock_director.should_receive(:delete_release).
197
+ with("foo", :force => false, :version => "42")
198
+
199
+ @cmd.stub!(:ask).and_return("yes")
200
+ @cmd.stub!(:director).and_return(mock_director)
201
+ @cmd.delete("foo", "42")
202
+ end
203
+
204
+ it "allows deleting a particular release version (non-force)" do
205
+ mock_director = mock(Bosh::Cli::Director)
206
+ mock_director.should_receive(:delete_release).
207
+ with("foo", :force => true, :version => "42")
208
+
209
+ @cmd.stub!(:ask).and_return("yes")
210
+ @cmd.stub!(:director).and_return(mock_director)
211
+ @cmd.delete("foo", "42", "--force")
212
+ end
213
+
214
+ it "requires confirmation on deleting release" do
215
+ mock_director = mock(Bosh::Cli::Director)
216
+ mock_director.should_not_receive(:delete_release)
217
+ @cmd.stub!(:director).and_return(mock_director)
218
+ @cmd.stub!(:ask).and_return("")
219
+ @cmd.delete("foo")
220
+ end
221
+
222
+ end
223
+
224
+ describe Bosh::Cli::Command::JobManagement do
225
+ before :each do
226
+ @manifest_path = spec_asset("deployment.MF")
227
+ @manifest_yaml = YAML.dump({ "name" => "foo" })
228
+ @cmd = Bosh::Cli::Command::JobManagement.new(@opts)
229
+ @cmd.stub!(:prepare_deployment_manifest).
230
+ with(:yaml => true).and_return(@manifest_yaml)
231
+ @cmd.stub!(:interactive?).and_return(false)
232
+ @cmd.stub!(:deployment).and_return(@manifest_path)
233
+ @cmd.stub!(:target).and_return("test.com")
234
+ @cmd.stub!(:target_name).and_return("dev2")
235
+ @cmd.stub!(:username).and_return("user")
236
+ @cmd.stub!(:password).and_return("pass")
237
+ @director = mock(Bosh::Cli::Director)
238
+ @cmd.stub!(:director).and_return(@director)
239
+ end
240
+
241
+ it "allows starting jobs" do
242
+ @director.should_receive(:change_job_state).
243
+ with("foo", @manifest_yaml, "dea", nil, "started")
244
+ @cmd.start_job("dea")
245
+ end
246
+
247
+ it "allows starting job instances" do
248
+ @director.should_receive(:change_job_state).
249
+ with("foo", @manifest_yaml, "dea", 3, "started")
250
+ @cmd.start_job("dea", 3)
251
+ end
252
+
253
+ it "allows stopping jobs" do
254
+ @director.should_receive(:change_job_state).
255
+ with("foo", @manifest_yaml, "dea", nil, "stopped")
256
+ @cmd.stop_job("dea")
257
+ end
258
+
259
+ it "allows stopping job instances" do
260
+ @director.should_receive(:change_job_state).
261
+ with("foo", @manifest_yaml, "dea", 3, "stopped")
262
+ @cmd.stop_job("dea", 3)
263
+ end
264
+
265
+ it "allows restarting jobs" do
266
+ @director.should_receive(:change_job_state).
267
+ with("foo", @manifest_yaml, "dea", nil, "restart")
268
+ @cmd.restart_job("dea")
269
+ end
270
+
271
+ it "allows restart job instances" do
272
+ @director.should_receive(:change_job_state).
273
+ with("foo", @manifest_yaml, "dea", 3, "restart")
274
+ @cmd.restart_job("dea", 3)
275
+ end
276
+
277
+ it "allows recreating jobs" do
278
+ @director.should_receive(:change_job_state).
279
+ with("foo", @manifest_yaml, "dea", nil, "recreate")
280
+ @cmd.recreate_job("dea")
281
+ end
282
+
283
+ it "allows recreating job instances" do
284
+ @director.should_receive(:change_job_state).
285
+ with("foo", @manifest_yaml, "dea", 3, "recreate")
286
+ @cmd.recreate_job("dea", 3)
287
+ end
288
+
289
+ it "allows hard stop" do
290
+ @director.should_receive(:change_job_state).
291
+ with("foo", @manifest_yaml, "dea", 3, "detached")
292
+ @cmd.stop_job("dea", 3, "--hard")
293
+ end
294
+
295
+ it "allows soft stop (= regular stop)" do
296
+ @director.should_receive(:change_job_state).
297
+ with("foo", @manifest_yaml, "dea", 3, "stopped")
298
+ @cmd.stop_job("dea", 3, "--soft")
299
+ end
300
+
301
+ end
302
+
303
+ describe Bosh::Cli::Command::Blob do
304
+ before :each do
305
+ @cmd = Bosh::Cli::Command::Blob.new(@opts)
306
+ @release_dir = Dir.mktmpdir
307
+ @blob_dir = File.join(@release_dir, "blobs/")
308
+ @cmd.stub!(:work_dir).and_return(@release_dir)
309
+ @blobstore = mock("blobstore")
310
+ @cmd.stub!(:blobstore).and_return(@blobstore)
311
+ FileUtils.mkdir(@blob_dir)
312
+ end
313
+
314
+ after :each do
315
+ FileUtils.rm_rf(@release_dir)
316
+ end
317
+
318
+ it "refuse to run outside of the release directory" do
319
+ lambda {
320
+ @cmd.upload_blob("foo")
321
+ }.should raise_error(Bosh::Cli::CliExit,
322
+ "Sorry, your current directory doesn't " +
323
+ "look like release directory".red)
324
+
325
+ lambda {
326
+ @cmd.sync_blobs
327
+ }.should raise_error(Bosh::Cli::CliExit,
328
+ "Sorry, your current directory doesn't " +
329
+ "look like release directory".red)
330
+
331
+ lambda {
332
+ @cmd.blobs_info
333
+ }.should raise_error(Bosh::Cli::CliExit,
334
+ "Sorry, your current directory doesn't " +
335
+ "look like release directory".red)
336
+ end
337
+
338
+ it "refuse to upload blob outside of release/blobs" do
339
+ Dir.chdir(@release_dir) do
340
+ FileUtils.touch("test.tgz")
341
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
342
+ blob_path = Pathname.new(@release_dir).realpath.to_s
343
+ blobs_dir = Pathname.new(@blob_dir).realpath.to_s
344
+ lambda {
345
+ @cmd.upload_blob("test.tgz")
346
+ }.should raise_error(Bosh::Cli::CliExit,
347
+ "#{File.join(blob_path, "test.tgz")} is " +
348
+ "NOT under #{blobs_dir}/")
349
+ end
350
+ end
351
+
352
+ it "upload new blob to blobstore" do
353
+ Dir.chdir(@release_dir) do
354
+ FileUtils.mkdir("./blobs/test")
355
+ blob = FileUtils.touch("./blobs/test/test.tgz")
356
+ @cmd.should_receive(:get_blobs_index).and_return({})
357
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
358
+ @blobstore.should_receive(:create).and_return(2)
359
+ @cmd.upload_blob("./blobs/test/test.tgz")
360
+ YAML.load_file("blob_index.yml").should == {
361
+ "test/test.tgz" => {
362
+ "object_id" => 2,
363
+ "sha" => Digest::SHA1.file(blob.first).hexdigest
364
+ }
365
+ }
366
+ end
367
+ end
368
+
369
+ it "should skip upload if blob already exists" do
370
+ Dir.chdir(@release_dir) do
371
+ FileUtils.mkdir("./blobs/test")
372
+ blob = FileUtils.touch("./blobs/test/test.tgz")
373
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
374
+ @cmd.should_receive(:get_blobs_index).and_return('test/test.tgz' => {
375
+ "sha" => Digest::SHA1.file(blob.first).hexdigest,
376
+ "object_id" => 2
377
+ })
378
+ @blobstore.should_not_receive(:create)
379
+ @cmd.upload_blob("./blobs/test/test.tgz")
380
+ end
381
+ end
382
+
383
+ it "should ask user if blob was modified" do
384
+ Dir.chdir(@release_dir) do
385
+ FileUtils.mkdir("./blobs/test")
386
+ blob = FileUtils.touch("./blobs/test/test.tgz")
387
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
388
+ @cmd.should_receive(:get_blobs_index).and_return('test/test.tgz' => {
389
+ "sha" => 1,
390
+ "object_id" => 2
391
+ })
392
+ @cmd.should_receive(:ask).and_return("no")
393
+ @blobstore.should_not_receive(:create)
394
+ @cmd.upload_blob("./blobs/test/test.tgz")
395
+ end
396
+ end
397
+
398
+ it "should sync if file is not present" do
399
+ Dir.chdir(@release_dir) do
400
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
401
+ @cmd.should_receive(:get_blobs_index).and_return('test/test.tgz' => {
402
+ "sha" => 1,
403
+ "object_id" => 2
404
+ })
405
+ @cmd.should_receive(:fetch_blob).
406
+ with(File.join(@release_dir, "blobs", "test/test.tgz"),
407
+ { "sha" => 1, "object_id" => 2 })
408
+ @cmd.sync_blobs
409
+ end
410
+ end
411
+
412
+ it "should not sync if the same file is present" do
413
+ Dir.chdir(@release_dir) do
414
+ FileUtils.mkdir("./blobs/test")
415
+ blob = FileUtils.touch("./blobs/test/test.tgz")
416
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
417
+ @cmd.should_receive(:get_blobs_index).and_return('test/test.tgz' => {
418
+ "sha" => Digest::SHA1.file(blob.first).hexdigest,
419
+ "object_id" => 2
420
+ })
421
+ @cmd.should_not_receive(:fetch_blob)
422
+ @cmd.sync_blobs
423
+ end
424
+ end
425
+
426
+ it "should ask user if blob sha is different" do
427
+ Dir.chdir(@release_dir) do
428
+ FileUtils.mkdir("./blobs/test")
429
+ blob = FileUtils.touch("./blobs/test/test.tgz")
430
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
431
+ @cmd.should_receive(:get_blobs_index).and_return('test/test.tgz' => {
432
+ "sha" => 1,
433
+ "object_id" => 2
434
+ })
435
+ @cmd.should_receive(:ask).and_return("")
436
+ @cmd.should_not_receive(:fetch_blob)
437
+ @cmd.sync_blobs
438
+ end
439
+ end
440
+
441
+ it "reports untracked blobs" do
442
+ Dir.chdir(@release_dir) do
443
+ FileUtils.mkdir("./blobs/test")
444
+ FileUtils.touch("./blobs/test/test.tgz")
445
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
446
+ @cmd.should_receive(:get_blobs_index).and_return({})
447
+ @cmd.should_receive(:say).
448
+ with("\nNew blobs ('bosh upload blob' to add): ".green)
449
+ @cmd.should_receive(:say).with("test/test.tgz")
450
+ @cmd.blobs_info
451
+ end
452
+ end
453
+
454
+ it "reports modified blobs" do
455
+ Dir.chdir(@release_dir) do
456
+ FileUtils.mkdir("./blobs/test")
457
+ FileUtils.touch("./blobs/test/test.tgz")
458
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
459
+ @cmd.should_receive(:get_blobs_index).
460
+ and_return({ "test/test.tgz" => { "sha" => 1, "object_id" => 2}})
461
+ @cmd.should_receive(:say).
462
+ with("\nModified blobs ('bosh upload blob' to update): ".green)
463
+ @cmd.should_receive(:say).with("test/test.tgz")
464
+ @cmd.blobs_info
465
+ end
466
+ end
467
+
468
+ it "reports unsynced blobs" do
469
+ Dir.chdir(@release_dir) do
470
+ @cmd.should_receive(:check_if_blobs_supported).and_return(true)
471
+ @cmd.should_receive(:get_blobs_index).
472
+ and_return({ "test/test.tgz" => { "sha" => 1, "object_id" => 2}})
473
+ @cmd.should_receive(:say).
474
+ with("\nMissing blobs ('bosh sync blobs' to fetch) : ".green)
475
+ @cmd.should_receive(:say).with("test/test.tgz")
476
+ @cmd.blobs_info
477
+ end
478
+ end
479
+ end
480
+
481
+ end