knife-server 0.2.2 → 0.3.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.
@@ -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