knife-server 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,13 @@
1
- ## 0.2.3.dev (unreleased)
1
+ ## 0.3.1.dev (unreleased)
2
+
3
+
4
+ ## 0.3.0 (July 7, 2012)
5
+
6
+ ### New features
7
+
8
+ * Add `knife server restore` subcommand to restore data components (nodes,
9
+ roles, environments, data bags) from the workstation's file system.
10
+ ([@fnichol][])
2
11
 
3
12
 
4
13
  ## 0.2.2 (July 4, 2012)
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # <a name="title"></a> Knife::Server [![Build Status](https://secure.travis-ci.org/fnichol/knife-server.png?branch=master)](http://travis-ci.org/fnichol/knife-server) [![Dependency Status](https://gemnasium.com/fnichol/knife-server.png)](https://gemnasium.com/fnichol/knife-server) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/fnichol/knife-server)
2
2
 
3
- An Opscode Chef knife plugin to manage Chef Servers. Bootstrapping new Chef
4
- Servers (currently on Amazon's EC2) and node data backup is supported.
3
+ An Opscode Chef knife plugin to manage Chef Servers. Bootstrap a new Chef
4
+ Server on Amazon's EC2 or a standalone server. Backup and restore your Chef
5
+ Server or Hosted Chef's node, role, data bag, and environment JSON data.
5
6
 
6
7
  ## <a name="usage"></a> Usage
7
8
 
@@ -36,6 +37,12 @@ snap:
36
37
  $ knife server backup
37
38
  ```
38
39
 
40
+ Restoring all that data isn't too hard either:
41
+
42
+ ```bash
43
+ $ knife server restore
44
+ ```
45
+
39
46
  See [below](#subcommands) for more details.
40
47
 
41
48
  ## <a name="installation"></a> Installation
@@ -282,6 +289,14 @@ Host name or IP address of the host to bootstrap.
282
289
 
283
290
  This option is **required**.
284
291
 
292
+ ##### --ssh-password PASSWORD (-P)
293
+
294
+ The SSH password used when bootstrapping the Chef Server node. If no password
295
+ is provided but an SSH key-based authentication fails, then you will be
296
+ prompted interactively for a password. In other words, if your server
297
+ requires password authentication you can skip this option and type it in after
298
+ the plugin starts.
299
+
285
300
  ### <a name="knife-server-backup"></a> knife server backup
286
301
 
287
302
  Pulls Chef data primitives from a Chef Server as JSON for backup. Backups can
@@ -335,22 +350,52 @@ chef_server_url = "https://api.opscode.com/organizations/coolinc"
335
350
  then a backup directory of
336
351
  `/var/chef/backups/api.opscode.com_20120401T084711-0000` would be created.
337
352
 
338
- ##### --ssh-password PASSWORD (-P)
353
+ ### <a name="knife-server-restore"></a> knife server restore
354
+
355
+ Restores Chef data primitives from JSON backups to a Chef Server. You can
356
+ restore some or all of:
357
+
358
+ * nodes
359
+ * roles
360
+ * environments
361
+ * data bags
362
+
363
+ A big thanks to [Steven Danna][stevendanna] and [Joshua Timberman][jtimberman]
364
+ for the [BackupRestore][backup_restore] knife plugin which was the inspiration
365
+ for this implementation.
366
+
367
+ #### Configuration
339
368
 
340
- The SSH password used (if needed) when bootstrapping the Chef Server node. If
341
- this option is not explicitly set and key based authentication fails, you will
342
- be prompted to enter a password in an interactive prompt. In other words,
343
- you may omit typing your password on the command line and defer to a prompt.
369
+ ##### COMPONENT[ COMPONENT ...]
370
+
371
+ The following component types are valid:
372
+
373
+ * `nodes`
374
+ * `roles`
375
+ * `environments`
376
+ * `data_bags` (note the underscore character)
377
+
378
+ When no component types are specified, all will be selected for restore.
379
+ This is equivalent to invoking:
380
+
381
+ ```bash
382
+ $ knife server restore nodes roles environments data_bags
383
+ ```
384
+
385
+ ##### --backup-dir DIR (-D)
386
+
387
+ The directory to containing backup JSON files. A sub-directory for each data
388
+ primitive type is expected (the `knife server backup` subcommand provides
389
+ this format).
390
+
391
+ This option is **required**.
344
392
 
345
393
  ## <a name="roadmap"></a> Roadmap
346
394
 
347
395
  * Support for other platforms (alternative bootstrap templates)
348
396
  * Support for Rackspace provisioning (use knife-rackspace gem)
349
- * Support for standalone server provisioning
350
397
  * knife server backup backed by s3 (fog api)
351
- * knife server restore {nodes,roles,environments,data bags,all}
352
398
  * knife server restore from s3 archive (fog api)
353
- * knife server restore from local filesystem
354
399
 
355
400
  ## <a name="development"></a> Development
356
401
 
@@ -382,6 +427,7 @@ Apache License, Version 2.0 (see [LICENSE][license])
382
427
  [contributors]: https://github.com/fnichol/knife-server/contributors
383
428
 
384
429
  [backup_export]: https://github.com/stevendanna/knife-hacks/blob/master/plugins/backup_export.rb
430
+ [backup_restore]: https://github.com/stevendanna/knife-hacks/blob/master/plugins/backup_restore.rb
385
431
  [chef_bootstrap_knife_rb]: https://github.com/fnichol/chef-bootstrap-repo/blob/master/.chef/knife.rb
386
432
  [chef_bootstrap_repo]: https://github.com/fnichol/chef-bootstrap-repo/
387
433
  [jtimberman]: https://github.com/jtimberman
@@ -0,0 +1,101 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ # Copyright:: Copyright (c) 2012 Fletcher Nichol
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife'
20
+
21
+ class Chef
22
+ class Knife
23
+ class ServerRestore < Knife
24
+
25
+ deps do
26
+ require 'chef/json_compat'
27
+ end
28
+
29
+ banner "knife server restore COMPONENT[ COMPONENT ...] (options)"
30
+
31
+ option :backup_dir,
32
+ :short => "-D DIR",
33
+ :long => "--backup-dir DIR",
34
+ :description => "The directory containing backup files"
35
+
36
+ def run
37
+ validate!
38
+ components = name_args.empty? ? COMPONENTS.keys : name_args
39
+
40
+ Array(components).each { |type| restore_components(type) }
41
+ end
42
+
43
+ private
44
+
45
+ COMPONENTS = {
46
+ "nodes" => { :singular => "node", :klass => Chef::Node },
47
+ "roles" => { :singular => "role", :klass => Chef::Role },
48
+ "environments" => { :singular => "environment", :klass => Chef::Environment },
49
+ "data_bags" => { :singular => "data_bag", :klass => Chef::DataBag },
50
+ }
51
+
52
+ def validate!
53
+ bad_names = name_args.reject { |c| COMPONENTS.keys.include?(c) }
54
+ unless bad_names.empty?
55
+ ui.error "Component types #{bad_names.inspect} are not valid."
56
+ exit 1
57
+ end
58
+ if config[:backup_dir].nil?
59
+ ui.error "You did not provide a valid --backup-dir value."
60
+ exit 1
61
+ end
62
+ end
63
+
64
+ def restore_components(type)
65
+ c = COMPONENTS[type]
66
+ dir_path = ::File.join(config[:backup_dir], type)
67
+
68
+ Array(Dir.glob(::File.join(dir_path, "**/*.json"))).each do |json_file|
69
+ restore_component(c, json_file)
70
+ end
71
+ end
72
+
73
+ def restore_component(c, json_file)
74
+ obj = Chef::JSONCompat.from_json(
75
+ ::File.open(json_file, "rb") { |f| f.read }
76
+ )
77
+
78
+ if c[:klass] == Chef::DataBag
79
+ create_data_bag(::File.basename(::File.dirname(json_file)))
80
+ msg = "Restoring #{c[:singular]}" +
81
+ "[#{obj.data_bag}][#{obj.raw_data[:id]}]"
82
+ else
83
+ msg = "Restoring #{c[:singular]}[#{obj.name}]"
84
+ end
85
+
86
+ ui.msg msg
87
+ obj.save
88
+ end
89
+
90
+ def create_data_bag(name)
91
+ @created_data_bags ||= []
92
+
93
+ unless @created_data_bags.include?(name)
94
+ ui.msg "Restoring data_bag[#{name}]"
95
+ rest.post_rest("data", { "name" => name })
96
+ @created_data_bags << name
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -18,6 +18,6 @@
18
18
 
19
19
  module Knife
20
20
  module Server
21
- VERSION = "0.2.2"
21
+ VERSION = "0.3.0"
22
22
  end
23
23
  end
@@ -0,0 +1,230 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ # Copyright:: Copyright (c) 2012 Fletcher Nichol
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife/server_restore'
20
+ require 'fakefs/spec_helpers'
21
+ require 'fileutils'
22
+ Chef::Knife::ServerRestore.load_deps
23
+
24
+ describe Chef::Knife::ServerRestore do
25
+ include FakeFS::SpecHelpers
26
+
27
+ before do
28
+ Chef::Log.logger = Logger.new(StringIO.new)
29
+ @knife = Chef::Knife::ServerRestore.new
30
+ @stdout = StringIO.new
31
+ @knife.ui.stub!(:stdout).and_return(@stdout)
32
+ @knife.ui.stub(:msg)
33
+ @stderr = StringIO.new
34
+ @knife.ui.stub!(:stderr).and_return(@stderr)
35
+ @knife.config[:backup_dir] = "/baks"
36
+ end
37
+
38
+ describe "#run" do
39
+ let(:rest_client) { stub(:post_rest => true) }
40
+
41
+ before do
42
+ Chef::Node.any_instance.stub(:save) { true }
43
+ Chef::Role.any_instance.stub(:save) { true }
44
+ Chef::Environment.any_instance.stub(:save) { true }
45
+ Chef::DataBagItem.any_instance.stub(:save) { true }
46
+ @knife.stub(:rest) { rest_client }
47
+ end
48
+
49
+ it "exists if component type is invalid" do
50
+ @knife.name_args = %w{nodes hovercraft}
51
+
52
+ lambda { @knife.run }.should raise_error SystemExit
53
+ end
54
+
55
+ it "exists if backup_dir is missing" do
56
+ @knife.config.delete(:backup_dir)
57
+
58
+ lambda { @knife.run }.should raise_error SystemExit
59
+ end
60
+
61
+ context "for nodes" do
62
+ before do
63
+ @knife.name_args = %w{nodes}
64
+
65
+ stub_json_node!("mynode")
66
+ end
67
+
68
+ it "sends a message to the ui" do
69
+ @knife.ui.should_receive(:msg).with(/mynode/)
70
+
71
+ @knife.run
72
+ end
73
+
74
+ it "saves the node" do
75
+ Chef::Node.any_instance.should_receive(:save).once
76
+
77
+ @knife.run
78
+ end
79
+ end
80
+
81
+ context "for roles" do
82
+ before do
83
+ @knife.name_args = %w{roles}
84
+
85
+ stub_json_role!("myrole")
86
+ end
87
+
88
+ it "sends a message to the ui" do
89
+ @knife.ui.should_receive(:msg).with(/myrole/)
90
+
91
+ @knife.run
92
+ end
93
+
94
+ it "saves the role" do
95
+ Chef::Role.any_instance.should_receive(:save).once
96
+
97
+ @knife.run
98
+ end
99
+ end
100
+
101
+ context "for environments" do
102
+ before do
103
+ @knife.name_args = %w{environments}
104
+
105
+ stub_json_env!("myenv")
106
+ end
107
+
108
+ it "sends a message to the ui" do
109
+ @knife.ui.should_receive(:msg).with(/myenv/)
110
+
111
+ @knife.run
112
+ end
113
+
114
+ it "saves the environment" do
115
+ Chef::Environment.any_instance.should_receive(:save).once
116
+
117
+ @knife.run
118
+ end
119
+ end
120
+
121
+ context "for data_bags" do
122
+ before do
123
+ @knife.name_args = %w{data_bags}
124
+
125
+ stub_json_data_bag_item!("mybag", "myitem")
126
+ end
127
+
128
+ it "sends a message to the ui" do
129
+ @knife.ui.should_receive(:msg).with(/myitem/)
130
+
131
+ @knife.run
132
+ end
133
+
134
+ it "creates the data bag" do
135
+ rest_client.should_receive(:post_rest).
136
+ with("data", { "name" => "mybag" })
137
+
138
+ @knife.run
139
+ end
140
+
141
+ it "only creates the data bag once for multiple items" do
142
+ stub_json_data_bag_item!("mybag", "anotheritem")
143
+ rest_client.should_receive(:post_rest).
144
+ with("data", { "name" => "mybag" }).once
145
+
146
+ @knife.run
147
+ end
148
+
149
+ it "saves the data bag item" do
150
+ Chef::DataBagItem.any_instance.should_receive(:save).once
151
+
152
+ @knife.run
153
+ end
154
+ end
155
+
156
+ context "for all" do
157
+ before do
158
+ stub_json_node!("nodey")
159
+ stub_json_role!("roley")
160
+ stub_json_env!("envey")
161
+ stub_json_data_bag_item!("bagey", "itemey")
162
+ end
163
+
164
+ it "saves nodes" do
165
+ Chef::Node.any_instance.should_receive(:save)
166
+
167
+ @knife.run
168
+ end
169
+
170
+ it "saves roles" do
171
+ Chef::Role.any_instance.should_receive(:save)
172
+
173
+ @knife.run
174
+ end
175
+
176
+ it "saves environments" do
177
+ Chef::Environment.any_instance.should_receive(:save)
178
+
179
+ @knife.run
180
+ end
181
+
182
+ it "creates data bags" do
183
+ rest_client.should_receive(:post_rest).
184
+ with("data", { "name" => "bagey" })
185
+
186
+ @knife.run
187
+ end
188
+
189
+ it "saves data bag items" do
190
+ Chef::DataBagItem.any_instance.should_receive(:save)
191
+
192
+ @knife.run
193
+ end
194
+ end
195
+ end
196
+
197
+ private
198
+
199
+ def stub_json_node!(name)
200
+ stub_json_component!(Chef::Node, "nodes", name)
201
+ end
202
+
203
+ def stub_json_role!(name)
204
+ stub_json_component!(Chef::Role, "roles", name)
205
+ end
206
+
207
+ def stub_json_env!(name)
208
+ stub_json_component!(Chef::Environment, "environments", name)
209
+ end
210
+
211
+ def stub_json_data_bag_item!(bag, name)
212
+ dir = File.join(@knife.config[:backup_dir], "data_bags", bag)
213
+ obj = Chef::DataBagItem.new
214
+ obj.data_bag(bag)
215
+ obj.raw_data[:id] = name
216
+ serialize_component(obj, File.join(dir, "#{name}.json"))
217
+ end
218
+
219
+ def stub_json_component!(klass, plural, name)
220
+ dir = File.join(@knife.config[:backup_dir], plural)
221
+ obj = klass.new
222
+ obj.name(name)
223
+ serialize_component(obj, File.join(dir, "#{name}.json"))
224
+ end
225
+
226
+ def serialize_component(obj, file_path)
227
+ FileUtils.mkdir_p(File.dirname(file_path))
228
+ File.open(file_path, "wb") { |f| f.write(obj.to_json) }
229
+ end
230
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-05 00:00:00.000000000 Z
12
+ date: 2012-07-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
16
- requirement: &2164645740 !ruby/object:Gem::Requirement
16
+ requirement: &2164645600 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '1.3'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2164645740
24
+ version_requirements: *2164645600
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: net-ssh
27
- requirement: &2164645080 !ruby/object:Gem::Requirement
27
+ requirement: &2164644960 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2164645080
35
+ version_requirements: *2164644960
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: chef
38
- requirement: &2164644240 !ruby/object:Gem::Requirement
38
+ requirement: &2164644040 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.10.10
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *2164644240
46
+ version_requirements: *2164644040
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: knife-ec2
49
- requirement: &2164643440 !ruby/object:Gem::Requirement
49
+ requirement: &2164643320 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 0.5.12
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *2164643440
57
+ version_requirements: *2164643320
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rspec
60
- requirement: &2164642700 !ruby/object:Gem::Requirement
60
+ requirement: &2164642520 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '2.10'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2164642700
68
+ version_requirements: *2164642520
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: fakefs
71
- requirement: &2164642100 !ruby/object:Gem::Requirement
71
+ requirement: &2164642000 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 0.4.0
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2164642100
79
+ version_requirements: *2164642000
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: timecop
82
- requirement: &2164641600 !ruby/object:Gem::Requirement
82
+ requirement: &2164641520 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: 0.3.5
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *2164641600
90
+ version_requirements: *2164641520
91
91
  description: Chef Knife plugin to bootstrap Chef Servers
92
92
  email:
93
93
  - fnichol@nichol.ca
@@ -109,6 +109,7 @@ files:
109
109
  - lib/chef/knife/server_bootstrap_base.rb
110
110
  - lib/chef/knife/server_bootstrap_ec2.rb
111
111
  - lib/chef/knife/server_bootstrap_standalone.rb
112
+ - lib/chef/knife/server_restore.rb
112
113
  - lib/knife-server.rb
113
114
  - lib/knife/server/credentials.rb
114
115
  - lib/knife/server/ec2_security_group.rb
@@ -117,6 +118,7 @@ files:
117
118
  - spec/chef/knife/server_backup_spec.rb
118
119
  - spec/chef/knife/server_bootstrap_ec2_spec.rb
119
120
  - spec/chef/knife/server_bootstrap_standalone_spec.rb
121
+ - spec/chef/knife/server_restore_spec.rb
120
122
  - spec/knife/server/credientials_spec.rb
121
123
  - spec/knife/server/ec2_security_group_spec.rb
122
124
  - spec/knife/server/ssh_spec.rb
@@ -148,6 +150,7 @@ test_files:
148
150
  - spec/chef/knife/server_backup_spec.rb
149
151
  - spec/chef/knife/server_bootstrap_ec2_spec.rb
150
152
  - spec/chef/knife/server_bootstrap_standalone_spec.rb
153
+ - spec/chef/knife/server_restore_spec.rb
151
154
  - spec/knife/server/credientials_spec.rb
152
155
  - spec/knife/server/ec2_security_group_spec.rb
153
156
  - spec/knife/server/ssh_spec.rb