knife-server 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +1 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +3 -0
  5. data/.travis.yml +12 -8
  6. data/CHANGELOG.md +32 -1
  7. data/Gemfile +9 -4
  8. data/Guardfile +28 -0
  9. data/README.md +28 -5
  10. data/Rakefile +31 -10
  11. data/knife-server.gemspec +18 -8
  12. data/lib/chef/knife/bootstrap/_omnibus.sh +63 -10
  13. data/lib/chef/knife/bootstrap/chef10/rhel.erb +2 -0
  14. data/lib/chef/knife/bootstrap/chef11/omnibus.erb +4 -1
  15. data/lib/chef/knife/bootstrap/chef11/rhel.erb +2 -0
  16. data/lib/chef/knife/server_backup.rb +24 -10
  17. data/lib/chef/knife/server_bootstrap_base.rb +68 -23
  18. data/lib/chef/knife/server_bootstrap_ec2.rb +33 -20
  19. data/lib/chef/knife/server_bootstrap_linode.rb +20 -13
  20. data/lib/chef/knife/server_bootstrap_openstack.rb +128 -0
  21. data/lib/chef/knife/server_bootstrap_standalone.rb +28 -16
  22. data/lib/chef/knife/server_restore.rb +23 -9
  23. data/lib/knife-server.rb +1 -0
  24. data/lib/knife/server/credentials.rb +78 -42
  25. data/lib/knife/server/ec2_security_group.rb +24 -21
  26. data/lib/knife/server/ssh.rb +54 -18
  27. data/lib/knife/server/version.rb +2 -1
  28. data/spec/chef/knife/server_backup_spec.rb +58 -44
  29. data/spec/chef/knife/server_bootstrap_ec2_spec.rb +108 -80
  30. data/spec/chef/knife/server_bootstrap_linode_spec.rb +93 -64
  31. data/spec/chef/knife/server_bootstrap_openstack_spec.rb +305 -0
  32. data/spec/chef/knife/server_bootstrap_standalone_spec.rb +113 -76
  33. data/spec/chef/knife/server_restore_spec.rb +38 -37
  34. data/spec/knife/server/credientials_spec.rb +248 -51
  35. data/spec/knife/server/ec2_security_group_spec.rb +76 -68
  36. data/spec/knife/server/ssh_spec.rb +138 -22
  37. metadata +107 -31
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  #
2
3
  # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
4
  # Copyright:: Copyright (c) 2012 Fletcher Nichol
@@ -18,6 +19,7 @@
18
19
 
19
20
  module Knife
20
21
  module Server
22
+ # Sets up EC2 security groups for a Chef Server.
21
23
  class Ec2SecurityGroup
22
24
  def initialize(connection, ui)
23
25
  @aws = connection
@@ -29,10 +31,10 @@ module Knife
29
31
 
30
32
  ip_permissions.each do |p|
31
33
  if permission_exists?(group, p)
32
- @ui.msg "Inbound security group rule " +
34
+ @ui.msg "Inbound security group rule " \
33
35
  "#{p[:proto]}(#{p[:from]} -> #{p[:to]}) exists"
34
36
  else
35
- @ui.msg "Creating inbound security group rule for " +
37
+ @ui.msg "Creating inbound security group rule for " \
36
38
  "#{p[:proto]}(#{p[:from]} -> #{p[:to]})"
37
39
  options = permission_options(group, p)
38
40
  @aws.authorize_security_group_ingress(group.name, options)
@@ -57,45 +59,46 @@ module Knife
57
59
  private
58
60
 
59
61
  def ip_permissions
60
- [ {:proto => 'icmp', :from => -1, :to => -1},
61
- {:proto => 'tcp', :from => 0, :to => 65535},
62
- {:proto => 'udp', :from => 0, :to => 65535},
63
- {:proto => 'tcp', :from => 22, :to => 22, :cidr => '0.0.0.0/0'},
64
- {:proto => 'tcp', :from => 443, :to => 443, :cidr => '0.0.0.0/0'},
65
- {:proto => 'tcp', :from => 444, :to => 444, :cidr => '0.0.0.0/0'},
62
+ [
63
+ { :proto => "icmp", :from => -1, :to => -1 },
64
+ { :proto => "tcp", :from => 0, :to => 65535 },
65
+ { :proto => "udp", :from => 0, :to => 65535 },
66
+ { :proto => "tcp", :from => 22, :to => 22, :cidr => "0.0.0.0/0" },
67
+ { :proto => "tcp", :from => 443, :to => 443, :cidr => "0.0.0.0/0" },
68
+ { :proto => "tcp", :from => 444, :to => 444, :cidr => "0.0.0.0/0" }
66
69
  ].freeze
67
70
  end
68
71
 
69
72
  def permission_exists?(group, perm)
70
73
  group.ip_permissions.find do |p|
71
- p['ipProtocol'] == perm[:proto] &&
72
- p['fromPort'] == perm[:from] &&
73
- p['toPort'] == perm[:to]
74
+ p["ipProtocol"] == perm[:proto] &&
75
+ p["fromPort"] == perm[:from] &&
76
+ p["toPort"] == perm[:to]
74
77
  end
75
78
  end
76
79
 
77
- def permission_options(group, opts)
80
+ def permission_options(group, opts) # rubocop:disable Metrics/MethodLength
78
81
  options = {
79
- 'IpPermissions' => [
82
+ "IpPermissions" => [
80
83
  {
81
- 'IpProtocol' => opts[:proto],
82
- 'FromPort' => opts[:from],
83
- 'ToPort' => opts[:to]
84
+ "IpProtocol" => opts[:proto],
85
+ "FromPort" => opts[:from],
86
+ "ToPort" => opts[:to]
84
87
  }
85
88
  ]
86
89
  }
87
90
 
88
91
  if opts[:cidr]
89
- options['IpPermissions'].first['IpRanges'] = [
92
+ options["IpPermissions"].first["IpRanges"] = [
90
93
  {
91
- 'CidrIp' => opts[:cidr]
94
+ "CidrIp" => opts[:cidr]
92
95
  }
93
96
  ]
94
97
  else
95
- options['IpPermissions'].first['Groups'] = [
98
+ options["IpPermissions"].first["Groups"] = [
96
99
  {
97
- 'GroupName' => group.name,
98
- 'UserId' => group.owner_id
100
+ "GroupName" => group.name,
101
+ "UserId" => group.owner_id
99
102
  }
100
103
  ]
101
104
  end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  #
2
3
  # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
4
  # Copyright:: Copyright (c) 2012 Fletcher Nichol
@@ -16,13 +17,15 @@
16
17
  # limitations under the License.
17
18
  #
18
19
 
19
- require 'net/ssh'
20
+ require "net/ssh"
20
21
 
21
22
  module Knife
22
23
  module Server
24
+ # Communicates with an SSH node.
23
25
  class SSH
24
26
  DEFAULT_OPTIONS = { :user => "root", :port => "22" }.freeze
25
- USER_SWITCH_COMMAND = %[sudo USER=root HOME="$(getent passwd root | cut -d : -f 6)"]
27
+ USER_SWITCH_COMMAND =
28
+ %{sudo USER=root HOME="$(getent passwd root | cut -d : -f 6)"}.freeze
26
29
 
27
30
  def initialize(params)
28
31
  options = DEFAULT_OPTIONS.merge(params)
@@ -33,20 +36,49 @@ module Knife
33
36
  end
34
37
 
35
38
  def exec!(cmd)
39
+ result = ""
40
+ exit_code = nil
41
+ Net::SSH.start(@host, @user, @options) do |session|
42
+ exit_code = ssh_session(session, full_cmd(cmd), result)
43
+ end
44
+ if exit_code != 0
45
+ raise "SSH exited with code #{exit_code} for [#{full_cmd(cmd)}]"
46
+ end
47
+ result
48
+ end
49
+
50
+ def full_cmd(cmd)
36
51
  if @user == "root"
37
- full_cmd = cmd
52
+ cmd
38
53
  else
39
- full_cmd = [
40
- USER_SWITCH_COMMAND,
41
- %[bash -c '#{cmd}']
42
- ].join(" ")
54
+ [USER_SWITCH_COMMAND, %{bash -c '#{cmd}'}].join(" ")
43
55
  end
56
+ end
44
57
 
45
- result = ""
46
- Net::SSH.start(@host, @user, @options) do |ssh|
47
- result = ssh.exec!(full_cmd)
58
+ def ssh_session(session, cmd, result)
59
+ exit_code = nil
60
+ session.open_channel do |channel|
61
+
62
+ channel.request_pty
63
+
64
+ channel.exec(cmd) do |_ch, _success|
65
+
66
+ channel.on_data do |_ch, data|
67
+ result << data
68
+ end
69
+
70
+ channel.on_extended_data do |_ch, _type, data|
71
+ result << data
72
+ end
73
+
74
+ channel.on_request("exit-status") do |_ch, data|
75
+ exit_code = data.read_long
76
+ end
77
+ end
48
78
  end
49
- result
79
+
80
+ session.loop
81
+ exit_code
50
82
  end
51
83
 
52
84
  # runs a script on the target host by passing it to the stdin of a sh
@@ -71,26 +103,30 @@ module Knife
71
103
  fi
72
104
  EOF
73
105
 
106
+ exec_ssh(wrapper, content)
107
+ end
108
+
109
+ def exec_ssh(wrapper, content) # rubocop:disable Metrics/MethodLength
74
110
  result = ""
75
111
  exit_status = nil
76
112
 
77
113
  Net::SSH.start(@host, @user, @options) do |ssh|
78
114
  ssh.open_channel do |ch|
79
- ch.on_open_failed do |ch, code, desc|
115
+ ch.on_open_failed do |_, _, desc|
80
116
  raise "Connection Error to #{ip}: #{desc}"
81
117
  end
82
118
 
83
- ch.exec(wrapper) do |channel, type, data|
84
- # spit out the shell script and close stdin so sh can do its magic.
119
+ ch.exec(wrapper) do |channel, _, _|
120
+ # spit out the shell script and close stdin so sh can do its magic
85
121
  channel.send_data(content)
86
122
  channel.eof!
87
123
 
88
- # then we just wait for sweet, sweet output.
89
- channel.on_data do |ch2, data|
124
+ # then we just wait for sweet, sweet output
125
+ channel.on_data do |_, data|
90
126
  result << data
91
127
  end
92
128
 
93
- channel.on_request("exit-status") do |ch2, data|
129
+ channel.on_request("exit-status") do |_, data|
94
130
  exit_status = data.read_long
95
131
  end
96
132
  end
@@ -101,7 +137,7 @@ module Knife
101
137
  ssh.loop
102
138
  end
103
139
 
104
- return result, exit_status
140
+ [result, exit_status]
105
141
  end
106
142
  end
107
143
  end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  #
2
3
  # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
4
  # Copyright:: Copyright (c) 2012 Fletcher Nichol
@@ -18,6 +19,6 @@
18
19
 
19
20
  module Knife
20
21
  module Server
21
- VERSION = "1.1.0"
22
+ VERSION = "1.2.0"
22
23
  end
23
24
  end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  #
2
3
  # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
4
  # Copyright:: Copyright (c) 2012 Fletcher Nichol
@@ -16,9 +17,9 @@
16
17
  # limitations under the License.
17
18
  #
18
19
 
19
- require 'chef/knife/server_backup'
20
- require 'fakefs/spec_helpers'
21
- require 'timecop'
20
+ require "chef/knife/server_backup"
21
+ require "fakefs/spec_helpers"
22
+ require "timecop"
22
23
 
23
24
  describe Chef::Knife::ServerBackup do
24
25
  include FakeFS::SpecHelpers
@@ -27,10 +28,10 @@ describe Chef::Knife::ServerBackup do
27
28
  Chef::Log.logger = Logger.new(StringIO.new)
28
29
  @knife = Chef::Knife::ServerBackup.new
29
30
  @stdout = StringIO.new
30
- @knife.ui.stub!(:stdout).and_return(@stdout)
31
- @knife.ui.stub(:msg)
31
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
32
+ allow(@knife.ui).to receive(:msg)
32
33
  @stderr = StringIO.new
33
- @knife.ui.stub!(:stderr).and_return(@stderr)
34
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
34
35
  @knife.config[:backup_dir] = "/baks"
35
36
 
36
37
  Chef::Config[:chef_server_url] = "https://chef.example.com:9876"
@@ -50,7 +51,7 @@ describe Chef::Knife::ServerBackup do
50
51
  @knife.config[:backup_dir] = nil
51
52
  Chef::Config[:file_backup_path] = "/da/bomb"
52
53
 
53
- @knife.backup_dir.should eq(
54
+ expect(@knife.backup_dir).to eq(
54
55
  "/da/bomb/chef.example.com_20120102T030405-0000")
55
56
  end
56
57
  end
@@ -64,35 +65,37 @@ describe Chef::Knife::ServerBackup do
64
65
  let(:data_bag_item_list) { Hash["myitem" => "http://p/bags/mybag/myitem"] }
65
66
 
66
67
  before do
67
- Chef::Node.stub(:list) { node_list }
68
- Chef::Node.stub(:load).with("mynode") { stub_node("mynode") }
69
- Chef::Role.stub(:list) { role_list }
70
- Chef::Role.stub(:load).with("myrole") { stub_role("myrole") }
71
- Chef::Environment.stub(:list) { env_list }
72
- Chef::Environment.stub(:load).with("myenv") { stub_env("myenv") }
73
- Chef::DataBag.stub(:list) { data_bag_list }
74
- Chef::DataBag.stub(:load).with("mybag") { data_bag_item_list }
75
- Chef::DataBagItem.stub(:load).
76
- with("mybag", "myitem") { stub_bag_item("mybag", "myitem")}
68
+ allow(Chef::Node).to receive(:list) { node_list }
69
+ allow(Chef::Node).to receive(:load).with("mynode") { stub_node("mynode") }
70
+ allow(Chef::Role).to receive(:list) { role_list }
71
+ allow(Chef::Role).to receive(:load).with("myrole") { stub_role("myrole") }
72
+ allow(Chef::Environment).to receive(:list) { env_list }
73
+ allow(Chef::Environment).to receive(:load).
74
+ with("myenv") { stub_env("myenv") }
75
+ allow(Chef::DataBag).to receive(:list) { data_bag_list }
76
+ allow(Chef::DataBag).to receive(:load).
77
+ with("mybag") { data_bag_item_list }
78
+ allow(Chef::DataBagItem).to receive(:load).
79
+ with("mybag", "myitem") { stub_bag_item("mybag", "myitem") }
77
80
  end
78
81
 
79
82
  it "exits if component type is invalid" do
80
- @knife.name_args = %w{nodes toasterovens}
83
+ @knife.name_args = %w[nodes toasterovens]
81
84
 
82
- lambda { @knife.run }.should raise_error SystemExit
85
+ expect { @knife.run }.to raise_error SystemExit
83
86
  end
84
87
 
85
88
  context "for nodes" do
86
- before { @knife.name_args = %w{nodes} }
89
+ before { @knife.name_args = %w[nodes] }
87
90
 
88
91
  it "creates the backup nodes dir" do
89
92
  @knife.run
90
93
 
91
- File.directory?(["/baks", "nodes"].join("/")).should be_true
94
+ expect(File.directory?(["/baks", "nodes"].join("/"))).to be_truthy
92
95
  end
93
96
 
94
97
  it "sends a message to the ui" do
95
- @knife.ui.should_receive(:msg).with(/mynode/)
98
+ expect(@knife.ui).to receive(:msg).with(/mynode/)
96
99
 
97
100
  @knife.run
98
101
  end
@@ -102,21 +105,22 @@ describe Chef::Knife::ServerBackup do
102
105
  json_str = File.open("/baks/nodes/mynode.json", "rb") { |f| f.read }
103
106
  json = JSON.parse(json_str, :create_additions => false)
104
107
 
105
- json["name"].should eq("mynode")
108
+ expect(json["name"]).to eq("mynode")
106
109
  end
107
110
  end
108
111
 
109
112
  context "for roles" do
110
- before { @knife.name_args = %w{roles} }
113
+ before { @knife.name_args = %w[roles] }
111
114
 
112
115
  it "creates the backup roles dir" do
113
116
  @knife.run
117
+ dir = File.join("/baks", "roles")
114
118
 
115
- File.directory?(["/baks", "roles"].join("/")).should be_true
119
+ expect(File.directory?(dir)).to be_truthy
116
120
  end
117
121
 
118
122
  it "sends a message to the ui" do
119
- @knife.ui.should_receive(:msg).with(/myrole/)
123
+ expect(@knife.ui).to receive(:msg).with(/myrole/)
120
124
 
121
125
  @knife.run
122
126
  end
@@ -126,63 +130,73 @@ describe Chef::Knife::ServerBackup do
126
130
  json_str = File.open("/baks/roles/myrole.json", "rb") { |f| f.read }
127
131
  json = JSON.parse(json_str, :create_additions => false)
128
132
 
129
- json["name"].should eq("myrole")
133
+ expect(json["name"]).to eq("myrole")
130
134
  end
131
135
  end
132
136
 
133
137
  context "for environments" do
134
- before { @knife.name_args = %w{environments} }
138
+ before { @knife.name_args = %w[environments] }
135
139
 
136
140
  it "creates the backup environments dir" do
137
141
  @knife.run
142
+ dir = File.join("/baks", "environments")
138
143
 
139
- File.directory?(["/baks", "environments"].join("/")).should be_true
144
+ expect(File.directory?(dir)).to be_truthy
140
145
  end
141
146
 
142
147
  it "sends a message to the ui" do
143
- @knife.ui.should_receive(:msg).with(/myenv/)
148
+ expect(@knife.ui).to receive(:msg).with(/myenv/)
144
149
 
145
150
  @knife.run
146
151
  end
147
152
 
148
153
  it "writes out each environment to a json file" do
149
154
  @knife.run
150
- json_str = File.open("/baks/environments/myenv.json", "rb") { |f| f.read }
155
+ json_str = File.open("/baks/environments/myenv.json", "rb") do |f|
156
+ f.read
157
+ end
151
158
  json = JSON.parse(json_str, :create_additions => false)
152
159
 
153
- json["name"].should eq("myenv")
160
+ expect(json["name"]).to eq("myenv")
154
161
  end
155
162
 
156
163
  it "skips the _default environment" do
157
- Chef::Environment.stub(:list) { Hash["_default" => "http://url"] }
158
- Chef::Environment.stub(:load).with("_default") { stub_env("_default") }
164
+ allow(Chef::Environment).to receive(:list) do
165
+ Hash["_default" => "http://url"]
166
+ end
167
+ allow(Chef::Environment).to receive(:load).with("_default") do
168
+ stub_env("_default")
169
+ end
159
170
  @knife.run
160
171
 
161
- File.exists?("/baks/environments/_default.json").should_not be_true
172
+ expect(File.exist?("/baks/environments/_default.json")).to_not be_truthy
162
173
  end
163
174
  end
164
175
 
165
176
  context "for data_bags" do
166
- before { @knife.name_args = %w{data_bags} }
177
+ before { @knife.name_args = %w[data_bags] }
167
178
 
168
179
  it "creates the backup data_bags dir" do
169
180
  @knife.run
181
+ dir = File.join("/baks", "data_bags")
170
182
 
171
- File.directory?(["/baks", "data_bags"].join("/")).should be_true
183
+ expect(File.directory?(dir)).to be_truthy
172
184
  end
173
185
 
174
186
  it "sends messages to the ui" do
175
- @knife.ui.should_receive(:msg).with(/myitem/)
187
+ expect(@knife.ui).to receive(:msg).with(/myitem/)
176
188
 
177
189
  @knife.run
178
190
  end
179
191
 
180
192
  it "writes out each data bag item to a json file" do
181
193
  @knife.run
182
- json_str = File.open("/baks/data_bags/mybag/myitem.json", "rb") { |f| f.read }
194
+ json_str = File.open("/baks/data_bags/mybag/myitem.json", "rb") do |f|
195
+ f.read
196
+ end
183
197
  json = JSON.parse(json_str, :create_additions => false)
184
198
 
185
- json["name"].should eq("data_bag_item_mybag_myitem")
199
+ expect(json["name"]).to eq("data_bag_item_mybag_myitem")
186
200
  end
187
201
  end
188
202
 
@@ -190,25 +204,25 @@ describe Chef::Knife::ServerBackup do
190
204
  it "writes a node file" do
191
205
  @knife.run
192
206
 
193
- File.exists?("/baks/nodes/mynode.json").should be_true
207
+ expect(File.exist?("/baks/nodes/mynode.json")).to be_truthy
194
208
  end
195
209
 
196
210
  it "writes a role file" do
197
211
  @knife.run
198
212
 
199
- File.exists?("/baks/roles/myrole.json").should be_true
213
+ expect(File.exist?("/baks/roles/myrole.json")).to be_truthy
200
214
  end
201
215
 
202
216
  it "writes an environment file" do
203
217
  @knife.run
204
218
 
205
- File.exists?("/baks/environments/myenv.json").should be_true
219
+ expect(File.exist?("/baks/environments/myenv.json")).to be_truthy
206
220
  end
207
221
 
208
222
  it "writes a data bag item file" do
209
223
  @knife.run
210
224
 
211
- File.exists?("/baks/data_bags/mybag/myitem.json").should be_true
225
+ expect(File.exist?("/baks/data_bags/mybag/myitem.json")).to be_truthy
212
226
  end
213
227
  end
214
228
  end