config_curator 0.0.0 → 0.0.1

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.
@@ -0,0 +1,76 @@
1
+ require 'mkmf'
2
+
3
+ module ConfigCurator
4
+
5
+ class Component < Unit
6
+
7
+ attr_accessor :fmode, :dmode, :owner, :group
8
+
9
+ # @see Unit#install
10
+ def install
11
+ s = super
12
+ return s unless s
13
+ install_component
14
+ set_mode
15
+ set_owner
16
+ true
17
+ end
18
+
19
+ # @see Unit#install?
20
+ def install?
21
+ s = super
22
+ return s unless s
23
+ fail InstallFailed, "No component source path specified." if source_path.nil?
24
+ fail InstallFailed, "No component install path specified." if destination_path.nil?
25
+ fail InstallFailed, "Source path does not exist: #{source}" unless Dir.exists? source_path
26
+ true
27
+ end
28
+
29
+ private
30
+
31
+ # Recursively creates the necessary directories and install the component.
32
+ # Any files in the install directory not in the source directory are removed.
33
+ # Use rsync if available.
34
+ def install_component
35
+ if command? 'rsync'
36
+ FileUtils.mkdir_p destination_path
37
+ cmd = [command?('rsync'), '-rt', '--del', "#{source_path}/", destination_path]
38
+ logger.debug { "Running command: #{cmd.join ' '}" }
39
+ system *cmd
40
+ else
41
+ FileUtils.remove_entry_secure destination_path
42
+ FileUtils.mkdir_p destination_path
43
+ FileUtils.cp_r "#{source_path}/.", destination_path, preserve: true
44
+ end
45
+ end
46
+
47
+ # Recursively sets file mode.
48
+ # @todo Make this more platform independent.
49
+ def set_mode
50
+ chmod = command? 'chmod'
51
+ find = command? 'find'
52
+ if chmod && find
53
+ {fmode: 'f', dmode: 'd'}.each do |k, v|
54
+ next if self.send(k).nil?
55
+ cmd = [find, destination_path, '-type', v, '-exec']
56
+ cmd.concat [chmod, self.send(k).to_s(8), '{}', '+']
57
+ logger.debug { "Running command: #{cmd.join ' '}" }
58
+ system *cmd
59
+ end
60
+ end
61
+ end
62
+
63
+ # Recursively sets file owner and group.
64
+ # @todo Make this more platform independent.
65
+ def set_owner
66
+ return unless owner || group
67
+ chown = command? 'chown'
68
+ if chown
69
+ cmd = [chown, '-R', "#{owner}:#{group}", destination_path]
70
+ logger.debug { "Running command: #{cmd.join ' '}" }
71
+ system *cmd
72
+ end
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,76 @@
1
+ module ConfigCurator
2
+
3
+ class ConfigFile < Unit
4
+
5
+ attr_accessor :fmode, :owner, :group
6
+
7
+ # Will use files of the form `filename.hostname.ext` if found.
8
+ # @see Unit#source
9
+ def source
10
+ path = super
11
+ host_specific_file = search_for_host_specific_file path if path
12
+ if host_specific_file then return host_specific_file else return path end
13
+ end
14
+
15
+ # Use {#source} by default.
16
+ # @see Unit#destination
17
+ def destination
18
+ super
19
+ @destination ||= source
20
+ end
21
+
22
+ # @see Unit#install
23
+ def install
24
+ s = super
25
+ return s unless s
26
+ install_file
27
+ set_mode
28
+ set_owner
29
+ true
30
+ end
31
+
32
+ # @see Unit#install?
33
+ def install?
34
+ s = super
35
+ return s unless s
36
+ fail InstallFailed, "No file source path specified." if source_path.nil?
37
+ fail InstallFailed, "Source path does not exist: #{source}" unless File.exists? source_path
38
+ true
39
+ end
40
+
41
+ private
42
+
43
+ # Recursively creates the necessary directories and install the file.
44
+ def install_file
45
+ FileUtils.mkdir_p File.dirname(destination_path)
46
+ FileUtils.copy source_path, destination_path, preserve: true
47
+ end
48
+
49
+ # Sets file mode.
50
+ def set_mode
51
+ FileUtils.chmod fmode, destination_path unless fmode.nil?
52
+ end
53
+
54
+ # Sets file owner and group.
55
+ def set_owner
56
+ FileUtils.chown owner, group, destination_path
57
+ end
58
+
59
+ private
60
+
61
+ # Will look for files with the naming pattern `filename.hostname.ext`.
62
+ # @param path [String] path to the non-host-specific file
63
+ def search_for_host_specific_file path
64
+ directory = File.dirname path
65
+ extension = File.extname path
66
+ basename = File.basename path.chomp(extension)
67
+ if Dir.exists? directory
68
+ file = Dir.entries(directory).grep(/^#{basename}.#{hostname.downcase}/).first
69
+ File.join directory, file unless file.nil?
70
+ else
71
+ nil
72
+ end
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,31 @@
1
+ module ConfigCurator
2
+
3
+ class Symlink < Unit
4
+
5
+ # @see Unit#install
6
+ def install
7
+ s = super
8
+ return s unless s
9
+ install_symlink
10
+ true
11
+ end
12
+
13
+ # @see Unit#install?
14
+ def install?
15
+ s = super
16
+ return s unless s
17
+ fail InstallFailed, "No source file specified." if source_path.nil?
18
+ fail InstallFailed, "No destination specified." if destination_path.nil?
19
+ true
20
+ end
21
+
22
+ private
23
+
24
+ # Recursively creates the necessary directories and make the symlink.
25
+ def install_symlink
26
+ FileUtils.mkdir_p File.dirname(destination_path)
27
+ FileUtils.symlink source_path, destination_path, force: true
28
+ end
29
+ end
30
+
31
+ end
@@ -1,3 +1,4 @@
1
1
  module ConfigCurator
2
- VERSION = '0.0.0'
2
+ # Config Curator version.
3
+ VERSION = '0.0.1'
3
4
  end
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe ConfigCurator::CLI do
4
+
5
+ subject(:cli) { ConfigCurator::CLI.new }
6
+
7
+ describe "#collection" do
8
+
9
+ it "makes a new collection" do
10
+ expect(cli.collection).to be_a ConfigCurator::Collection
11
+ end
12
+
13
+ it "sets the logger for the collection" do
14
+ expect(cli.collection.logger).to be cli.logger
15
+ end
16
+ end
17
+
18
+ describe "#logger" do
19
+
20
+ it "makes a new logger" do
21
+ expect(cli.logger).to be_a Logger
22
+ end
23
+ end
24
+
25
+ describe "#install" do
26
+
27
+ it "installs the collection" do
28
+ allow(File).to receive(:exists?).with('manifest.yml').and_return(true)
29
+ expect(cli.collection).to receive(:load_manifest).with('manifest.yml')
30
+ expect(cli.collection).to receive(:install).and_return(true)
31
+ expect(cli.install 'manifest.yml').to be true
32
+ end
33
+
34
+ context "when --dryrun is set" do
35
+
36
+ before(:each) { allow(cli).to receive(:options).and_return({dryrun: true}) }
37
+
38
+ it "only checks if install would succeed" do
39
+ allow(File).to receive(:exists?).with('manifest.yml').and_return(true)
40
+ expect(cli.collection).to receive(:load_manifest).with('manifest.yml')
41
+ expect(cli.collection).to receive(:install?).and_return(true)
42
+ expect(cli.collection).to_not receive(:install)
43
+ expect(cli.install 'manifest.yml').to be true
44
+ end
45
+ end
46
+
47
+ context "manifest not found" do
48
+
49
+ it "returns false and doesn't do anything else" do
50
+ allow(File).to receive(:exists?).with('manifest.yml').and_return(false)
51
+ expect(cli.collection).to_not receive(:install)
52
+ expect(cli.collection).to_not receive(:install?)
53
+ expect(cli.install 'manifest.yml').to be false
54
+ end
55
+
56
+ context "when --dryrun is set" do
57
+
58
+ before(:each) { allow(cli).to receive(:options).and_return({dryrun: true}) }
59
+
60
+ it "returns false and doesn't do anything else" do
61
+ allow(File).to receive(:exists?).with('manifest.yml').and_return(false)
62
+ expect(cli.collection).to_not receive(:install)
63
+ expect(cli.collection).to_not receive(:install?)
64
+ expect(cli.install 'manifest.yml').to be false
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,280 @@
1
+ require 'spec_helper'
2
+
3
+ describe ConfigCurator::Collection do
4
+
5
+ subject(:collection) { ConfigCurator::Collection.new }
6
+
7
+ describe ".new" do
8
+
9
+ it "sets the logger" do
10
+ logger = Logger.new(STDOUT)
11
+ collection = ConfigCurator::Collection.new logger: logger
12
+ expect(collection.logger).to be logger
13
+ end
14
+
15
+ it "loads the manifest" do
16
+ expect(YAML).to receive(:load_file).with('path')
17
+ ConfigCurator::Collection.new manifest_path: 'path'
18
+ end
19
+ end
20
+
21
+ describe "#logger" do
22
+
23
+ it "makes a new logger" do
24
+ expect(collection.logger).to be_a Logger
25
+ end
26
+ end
27
+
28
+ describe "#load_manifest" do
29
+
30
+ let(:manifest) { {root: 'tmp'} }
31
+
32
+ it "loads the manifest" do
33
+ path = 'path/to/manifest'
34
+ allow(YAML).to receive(:load_file).with(path).and_return(manifest)
35
+ expect(collection.load_manifest path).to eq manifest
36
+ expect(collection.manifest).to eq manifest
37
+ end
38
+ end
39
+
40
+ describe "#create_unit" do
41
+
42
+ subject(:unit) { collection.create_unit :unit }
43
+
44
+ it "makes a new unit" do
45
+ expect(unit).to be_a ConfigCurator::Unit
46
+ end
47
+
48
+ it "sets the unit's logger" do
49
+ expect(unit.logger).to be collection.logger
50
+ end
51
+
52
+ context "with basic attributes" do
53
+
54
+ let(:attributes) { {src: 'src', dst: 'dest'} }
55
+ subject(:unit) { collection.create_unit :unit, attributes: attributes }
56
+
57
+ it "sets the source" do
58
+ expect(unit.source).to eq 'src'
59
+ end
60
+
61
+ it "sets the destination" do
62
+ expect(unit.destination).to eq 'dest'
63
+ end
64
+ end
65
+
66
+ context "with unit specific attributes" do
67
+
68
+ let(:attributes) { {src: 'src', dst: 'dest', fmode: '0600', owner: 'username'} }
69
+ subject(:unit) { collection.create_unit :component, attributes: attributes }
70
+
71
+ it "sets the generic attributes" do
72
+ expect(unit.destination).to eq 'dest'
73
+ expect(unit.source).to eq 'src'
74
+ end
75
+
76
+ it "sets the unit specific attributes" do
77
+ expect(unit.fmode).to eq '0600'
78
+ expect(unit.owner).to eq 'username'
79
+ end
80
+ end
81
+
82
+ context "with manifest" do
83
+
84
+ let(:manifest) do
85
+ YAML.load <<-EOF
86
+ :root: /tmp
87
+ :defaults:
88
+ :fmode: '0640'
89
+ :dmode: '0750'
90
+ :owner: username
91
+ :group: groupname
92
+ EOF
93
+ end
94
+ let(:attributes) { {src: 'src', dst: 'dest', dmode: '0700'} }
95
+ subject(:unit) { collection.create_unit :component, attributes: attributes }
96
+
97
+ before(:each) { collection.manifest = manifest }
98
+
99
+ it "sets the unit's root path" do
100
+ expect(unit.options[:root]).to eq manifest[:root]
101
+ end
102
+
103
+ it "sets the unit specific attributes" do
104
+ expect(unit.dmode).to eq '0700'
105
+ end
106
+
107
+ it "sets attribute defaults from the manifest" do
108
+ expect(unit.owner).to eq 'username'
109
+ expect(unit.group).to eq 'groupname'
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "#units" do
115
+
116
+ let(:types) do
117
+ types = ConfigCurator::Collection::UNIT_TYPES
118
+ types.map { |t| "#{t}s".to_sym }
119
+ end
120
+
121
+ context "no manifest" do
122
+
123
+ it "has a key for each supported unit type" do
124
+ types.each do |type|
125
+ expect(collection.units.key? type).to be true
126
+ end
127
+ end
128
+
129
+ it "sets each entry empty" do
130
+ types.each do |type|
131
+ expect(collection.units[type]).to be_a Array
132
+ expect(collection.units[type]).to be_empty
133
+ end
134
+ end
135
+ end
136
+
137
+ context "with manifest" do
138
+
139
+ let(:manifest) do
140
+ YAML.load <<-EOF
141
+ :defaults:
142
+ :dmode: '0700'
143
+ :components:
144
+ - :src: components/component_1
145
+ :dst: inst/component_1
146
+ :fmode: '0600'
147
+ - :src: components/component_2
148
+ :config_files:
149
+ - :src: conf_file
150
+ EOF
151
+ end
152
+
153
+ before(:each) { collection.manifest = manifest }
154
+
155
+ it "has a key for each supported unit type" do
156
+ types.each do |type|
157
+ expect(collection.units.key? type).to be true
158
+ end
159
+ end
160
+
161
+ it "sets entries for missing unit types empty" do
162
+ %i(units symlinks).each do |type|
163
+ expect(collection.units[type]).to be_empty
164
+ end
165
+ end
166
+
167
+ it "contains units" do
168
+ expect(collection.units[:components]).to_not be_empty
169
+ collection.units[:components].each do |unit|
170
+ expect(unit).to be_a ConfigCurator::Component
171
+ end
172
+
173
+ expect(collection.units[:config_files]).to_not be_empty
174
+ collection.units[:config_files].each do |unit|
175
+ expect(unit).to be_a ConfigCurator::ConfigFile
176
+ end
177
+ end
178
+
179
+ it "creates units with correct attributes" do
180
+ component = collection.units[:components].first
181
+ expect(component.source).to eq 'components/component_1'
182
+ expect(component.destination).to eq 'inst/component_1'
183
+ expect(component.fmode).to eq '0600'
184
+ expect(component.dmode).to eq '0700'
185
+ end
186
+ end
187
+ end
188
+
189
+ describe "#install" do
190
+
191
+ let(:manifest) do
192
+ YAML.load <<-EOF
193
+ :components:
194
+ - :src: components/component_1
195
+ :dst: inst/component_1
196
+ - :src: components/component_2
197
+ :dst: inst/component_2
198
+ :config_files:
199
+ - :src: conf_file
200
+ EOF
201
+ end
202
+
203
+ before(:each) { collection.manifest = manifest }
204
+
205
+ let(:units) { collection.units }
206
+
207
+ context "when #install? is true" do
208
+
209
+ it "calls #install on each unit and returns true" do
210
+ allow(collection).to receive(:install?).and_return(true)
211
+ expect(units[:components][0]).to receive(:install)
212
+ expect(units[:components][1]).to receive(:install)
213
+ expect(units[:config_files][0]).to receive(:install)
214
+ expect(collection.install).to be true
215
+ end
216
+ end
217
+
218
+ context "when #install? is false" do
219
+
220
+ it "does not call #install on each unit and returns false" do
221
+ allow(collection).to receive(:install?).and_return(false)
222
+ expect(units[:components][0]).to_not receive(:install)
223
+ expect(units[:components][1]).to_not receive(:install)
224
+ expect(units[:config_files][0]).to_not receive(:install)
225
+ expect(collection.install).to be false
226
+ end
227
+ end
228
+
229
+ context "when component install fails" do
230
+
231
+ it "stops installing and returns nil" do
232
+ allow(collection).to receive(:install?).and_return(true)
233
+ expect(units[:components][0]).to receive(:install)
234
+ expect(units[:components][1]).to receive(:install).and_raise ConfigCurator::Unit::InstallFailed
235
+ expect(units[:config_files][0]).to_not receive(:install)
236
+ expect(collection.install).to be nil
237
+ end
238
+ end
239
+ end
240
+
241
+ describe "#install?" do
242
+
243
+ let(:manifest) do
244
+ YAML.load <<-EOF
245
+ :components:
246
+ - :src: components/component_1
247
+ :dst: inst/component_1
248
+ - :src: components/component_2
249
+ :dst: inst/component_2
250
+ :config_files:
251
+ - :src: conf_file
252
+ EOF
253
+ end
254
+
255
+ before(:each) { collection.manifest = manifest }
256
+
257
+ let(:units) { collection.units }
258
+
259
+ context "when no errors" do
260
+
261
+ it "calls #install? on each unit and returns true" do
262
+ expect(units[:components][0]).to receive(:install?)
263
+ expect(units[:components][1]).to receive(:install?)
264
+ expect(units[:config_files][0]).to receive(:install?)
265
+ expect(collection.install?).to be true
266
+ end
267
+ end
268
+
269
+ context "when install error" do
270
+
271
+ it "calls #install? on each unit and returns false" do
272
+ allow(units[:components][0]).to receive(:install?)
273
+ .and_raise(ConfigCurator::Unit::InstallFailed)
274
+ allow(units[:components][1]).to receive(:install?)
275
+ allow(units[:config_files][0]).to receive(:install?)
276
+ expect(collection.install?).to be false
277
+ end
278
+ end
279
+ end
280
+ end