bosh_cli 0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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