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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +2 -2
- data/Guardfile +10 -0
- data/README.md +1 -1
- data/Rakefile +2 -0
- data/bin/curate +5 -0
- data/config_curator.gemspec +3 -0
- data/lib/config_curator.rb +8 -0
- data/lib/config_curator/cli.rb +71 -0
- data/lib/config_curator/collection.rb +137 -0
- data/lib/config_curator/package_lookup.rb +77 -0
- data/lib/config_curator/unit.rb +122 -0
- data/lib/config_curator/units/component.rb +76 -0
- data/lib/config_curator/units/config_file.rb +76 -0
- data/lib/config_curator/units/symlink.rb +31 -0
- data/lib/config_curator/version.rb +2 -1
- data/spec/cli_spec.rb +69 -0
- data/spec/collection_spec.rb +280 -0
- data/spec/package_lookup_spec.rb +59 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/unit_spec.rb +190 -0
- data/spec/units/component_spec.rb +59 -0
- data/spec/units/config_file_spec.rb +84 -0
- data/spec/units/symlink_spec.rb +46 -0
- metadata +55 -3
@@ -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
|
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
|