rsync_config 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +8 -0
- data/lib/rsync_config/config.rb +112 -0
- data/lib/rsync_config/module.rb +32 -0
- data/lib/rsync_config/parser/config_file.treetop +98 -0
- data/lib/rsync_config/parser/node_extension.rb +28 -0
- data/lib/rsync_config/parser/secrets_file.treetop +59 -0
- data/lib/rsync_config/parser.rb +1 -0
- data/lib/rsync_config/propertiable.rb +68 -0
- data/lib/rsync_config/user_management.rb +86 -0
- data/lib/rsync_config/version.rb +3 -0
- data/lib/rsync_config.rb +20 -0
- data/rsync_config.gemspec +26 -0
- data/test/etc/out/.keep +0 -0
- data/test/etc/rsyncd.conf +10 -0
- data/test/etc/rsyncd_with_secrets.conf +12 -0
- data/test/etc/secrets_ftp.conf +4 -0
- data/test/etc/secrets_top.conf +2 -0
- data/test/test_rsync_config.rb +233 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2eec2099c07ff331d789ae499f3487d9cbbf2371
|
4
|
+
data.tar.gz: d0af2a1154084f0fff7aab650f68cbdf0a0cff9c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 44661fc4d9f091e2760e11a16e85219cb779b21c769728006e3b6fd7e74a7ab6c005db042aa8a3e04f61753c6b7617890173762b0b2423ed1e6e137c23556314
|
7
|
+
data.tar.gz: 8bb05a02d50ed6e99acfdefb4b873677cdc57a268f584e5d8df551fafb212b806ed7933cc8f78171f0adccaf25f7a40bcd91cc5ad03d58ecd5916b3f58e8a1ce
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Guillaume Bodi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# RsyncConfig
|
2
|
+
|
3
|
+
This is a little utility gem to help manage the rsyncd config files on GNU/Linux systems.
|
4
|
+
It is still in infancy but is usable as is.
|
5
|
+
|
6
|
+
[](https://codeclimate.com/github/kuma-giyomu/rsync_config)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'rsync_config'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install rsync_config
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
You can load a config file using `RsyncConfig::load_config <file_path>` or create a config from scratch with `RsyncConfig::Config.new`.
|
25
|
+
Alternatively, you can use `RsyncConfig::parse <content>` if you prefer to provide the input from a random source.
|
26
|
+
|
27
|
+
### Properties
|
28
|
+
|
29
|
+
The `RsyncConfig::Config` instance provides access to properties via the `#[]` and `#[]=` methods. So for instance
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
config = RsyncConfig::Config.new
|
33
|
+
config['uid'] = 'alice' # setter
|
34
|
+
puts config['uid'] # getter
|
35
|
+
```
|
36
|
+
|
37
|
+
Note that the class accepts symbols as alternative `config[:uid]` is valid too.
|
38
|
+
But I felt it was best to keep as strings for the config options with spaces such as `secrets file`.
|
39
|
+
|
40
|
+
### Modules
|
41
|
+
|
42
|
+
The `RsyncConfig::Config` instance provides access to modules via the `#module` method
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
config = RsyncConfig::Config.new
|
46
|
+
foo_module = config.module :foo
|
47
|
+
bar_module = config.module 'bar'
|
48
|
+
```
|
49
|
+
|
50
|
+
Here again, symbols are converted to strings (rsync allows modules with spaces and whatnot)
|
51
|
+
|
52
|
+
Properties can be assigned to modules in the same way they are added to the `RsyncConfig::Config` object.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
foo_module['uid'] = 'alice'
|
56
|
+
```
|
57
|
+
|
58
|
+
`RsyncConfig::Module` instances do not have modules themselves.
|
59
|
+
|
60
|
+
### Users
|
61
|
+
|
62
|
+
Both `RsyncConfig::Config` and `RsyncConfig::Module` instances can define a list of users that have access to the modules.
|
63
|
+
They are defined using the `#users` hash accessor method.
|
64
|
+
Additionally a test method `#user?` can be used to probe for a give user's existence.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
config = RsyncConfig::Config.new
|
68
|
+
config.users = {'alice' => 'wonder'}
|
69
|
+
puts config.user?('alice') # true
|
70
|
+
config.users['bob'] = 'march'
|
71
|
+
```
|
72
|
+
|
73
|
+
### Writing to disk
|
74
|
+
|
75
|
+
Use `RsyncConfig::Config#write_to <main_output_file>`.
|
76
|
+
The gem assumes that you have write permissions on the file and a `RuntimeError` will be raised otherwise.
|
77
|
+
This method will automatically try to write the secrets files when defined.
|
78
|
+
|
79
|
+
For a raw String output, just call `RsyncConfig::Config#to_config_file` instead.
|
80
|
+
Note that this method will not return the content of the secrets file (not sure if this will be useful).
|
81
|
+
|
82
|
+
### Limitations
|
83
|
+
|
84
|
+
- currently the gem does not process the directives `&include` and `&merge`.
|
85
|
+
- the values for the properties are not checked/validated in any way.
|
86
|
+
- the way secrets files are handled is simplistic since the files will be written/read multiple times if specified more than once
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
class Config
|
4
|
+
|
5
|
+
include Propertiable
|
6
|
+
include UserManagement
|
7
|
+
|
8
|
+
# global properties
|
9
|
+
allow_properties :motd_file, :pid_file, :port, :address, :socket_options, :listen_backlog
|
10
|
+
|
11
|
+
# module properties
|
12
|
+
allow_properties :comment, :path, :use_chroot, :numeric_ids, :munge_symlinks, :charset
|
13
|
+
allow_properties :max_connections, :log_file, :syslog_facility, :max_verbosity, :lock_file
|
14
|
+
allow_properties :read_only, :write_only, :list, :uid, :gid, :fake_super, :filter, :exclude, :include
|
15
|
+
allow_properties :exclude_from, :include_from, :incoming_chmod, :outgoing_chmod, :auth_users
|
16
|
+
allow_properties :secrets_file, :strict_modes, :hosts_allow, :hosts_deny, :reverse_lookup
|
17
|
+
allow_properties :forward_lookup, :ignore_errors, :ignore_nonreadable, :transfer_logging
|
18
|
+
allow_properties :log_format, :timeout, :refuse_options, :dont_compress, 'pre-xfer exec', 'post-xfer exec'
|
19
|
+
|
20
|
+
def self.load_file(config_file)
|
21
|
+
raise 'File does not exist' unless File.exists? config_file
|
22
|
+
raise 'File is not readable' unless File.readable? config_file
|
23
|
+
|
24
|
+
content = nil
|
25
|
+
|
26
|
+
File.open(config_file, 'r') do |file|
|
27
|
+
content = file.read
|
28
|
+
end
|
29
|
+
|
30
|
+
Config.parse content
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.parse(content)
|
34
|
+
raise 'Cannot process nil' if content.nil?
|
35
|
+
|
36
|
+
Treetop.load 'lib/rsync_config/parser/config_file'
|
37
|
+
parser = RsyncConfigFileParser.new
|
38
|
+
p = parser.parse content
|
39
|
+
|
40
|
+
unless p.nil?
|
41
|
+
return p.to_config.after_parse
|
42
|
+
else
|
43
|
+
raise RuntimeError, parser.failure_reason
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def module(key)
|
48
|
+
raise ArgumentError if key.nil?
|
49
|
+
key = key.to_s if key.is_a? Symbol
|
50
|
+
key = key.strip
|
51
|
+
raise ArgumentError if key.length == 0
|
52
|
+
|
53
|
+
modules[key] ||= Module.new(self, key)
|
54
|
+
|
55
|
+
modules[key]
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
to_config_file
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_config_file
|
63
|
+
out = []
|
64
|
+
properties_to_s = properties_to_a.join("\n")
|
65
|
+
out << properties_to_s if properties_to_s.length > 0
|
66
|
+
|
67
|
+
modules.each do |key, mod|
|
68
|
+
out << mod.to_config_file
|
69
|
+
end
|
70
|
+
|
71
|
+
out.join "\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
def write_to(file_path)
|
75
|
+
File.open(file_path, 'w') do |file|
|
76
|
+
file.write to_config_file
|
77
|
+
end
|
78
|
+
|
79
|
+
write_secrets_files
|
80
|
+
end
|
81
|
+
|
82
|
+
def module_names
|
83
|
+
modules.keys
|
84
|
+
end
|
85
|
+
|
86
|
+
def after_parse
|
87
|
+
batch_process_secrets_files :load_secrets_file
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def modules
|
93
|
+
@modules ||= {}
|
94
|
+
end
|
95
|
+
|
96
|
+
def write_secrets_files
|
97
|
+
batch_process_secrets_files :write_secrets_file
|
98
|
+
end
|
99
|
+
|
100
|
+
def batch_process_secrets_files(method_name)
|
101
|
+
send method_name, self.[]('secrets file', true)
|
102
|
+
|
103
|
+
modules.each_value do |rmodule|
|
104
|
+
rmodule.send method_name, rmodule.[]('secrets file', true)
|
105
|
+
end
|
106
|
+
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
class Module
|
4
|
+
|
5
|
+
include Propertiable
|
6
|
+
include UserManagement
|
7
|
+
|
8
|
+
# module properties
|
9
|
+
allow_properties :comment, :path, :use_chroot, :numeric_ids, :munge_symlinks, :charset
|
10
|
+
allow_properties :max_connections, :log_file, :syslog_facility, :max_verbosity, :lock_file
|
11
|
+
allow_properties :read_only, :write_only, :list, :uid, :gid, :fake_super, :filter, :exclude, :include
|
12
|
+
allow_properties :exclude_from, :include_from, :incoming_chmod, :outgoing_chmod, :auth_users
|
13
|
+
allow_properties :secrets_file, :strict_modes, :hosts_allow, :hosts_deny, :reverse_lookup
|
14
|
+
allow_properties :forward_lookup, :ignore_errors, :ignore_nonreadable, :transfer_logging
|
15
|
+
allow_properties :log_format, :timeout, :refuse_options, :dont_compress, 'pre-xfer exec', 'post-xfer exec'
|
16
|
+
|
17
|
+
def initialize(parent_config, name)
|
18
|
+
@parent_config = parent_config
|
19
|
+
@name = name
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
to_config_file
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_config_file
|
27
|
+
(["[#{@name}]"] + properties_to_a.map {|p| " #{p}"}).join "\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'rsync_config/config'
|
2
|
+
|
3
|
+
module RsyncConfig
|
4
|
+
|
5
|
+
grammar RsyncConfigFile
|
6
|
+
|
7
|
+
rule start
|
8
|
+
global_section? module_section? EOF
|
9
|
+
{
|
10
|
+
attr_accessor :config, :active_module
|
11
|
+
|
12
|
+
def to_config
|
13
|
+
@config = ::RsyncConfig::Config.new
|
14
|
+
@active_module = nil
|
15
|
+
|
16
|
+
crawl self
|
17
|
+
|
18
|
+
@config
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
rule global_section
|
24
|
+
((comment / space? option?) EOL)+
|
25
|
+
end
|
26
|
+
|
27
|
+
rule module_section
|
28
|
+
(module_header module_body*)+
|
29
|
+
end
|
30
|
+
|
31
|
+
rule module_body
|
32
|
+
(comment / space? option?) EOL
|
33
|
+
end
|
34
|
+
|
35
|
+
rule option
|
36
|
+
property space? '=' space? value space?
|
37
|
+
{
|
38
|
+
def action(top_node)
|
39
|
+
receiver = top_node.active_module.nil? ? top_node.config : top_node.active_module
|
40
|
+
receiver[property.text_value] = value.text_value
|
41
|
+
|
42
|
+
# interrupt subtree crawling
|
43
|
+
false
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
rule property
|
49
|
+
[^=\n]+
|
50
|
+
end
|
51
|
+
|
52
|
+
rule value
|
53
|
+
[^\n]* ('\\' space? EOL value)?
|
54
|
+
{
|
55
|
+
def value
|
56
|
+
text_value.gsub(/\\\s*\n/, ' ')
|
57
|
+
end
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
rule module_header
|
62
|
+
'[' module_label:([^/\]]+) ']' space?
|
63
|
+
{
|
64
|
+
def value
|
65
|
+
module_label.text_value.gsub /\s+/, ' '
|
66
|
+
end
|
67
|
+
|
68
|
+
def action(top_node)
|
69
|
+
top_node.active_module = top_node.config.module(module_label.text_value)
|
70
|
+
|
71
|
+
# interrupt subtree crawling
|
72
|
+
false
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
rule comment
|
78
|
+
space? '#' [^"\n"]*
|
79
|
+
end
|
80
|
+
|
81
|
+
rule space
|
82
|
+
[ \t]+
|
83
|
+
end
|
84
|
+
|
85
|
+
rule EOL
|
86
|
+
"\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
rule EOF
|
90
|
+
!.
|
91
|
+
end
|
92
|
+
|
93
|
+
rule EOS
|
94
|
+
EOL / EOF
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
module Node
|
4
|
+
|
5
|
+
module Crawlable
|
6
|
+
|
7
|
+
def crawl *args
|
8
|
+
continue = true
|
9
|
+
continue = action(*args) if respond_to? :action
|
10
|
+
|
11
|
+
return if !continue || elements.nil?
|
12
|
+
|
13
|
+
elements.each do |elt|
|
14
|
+
elt.crawl(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class Treetop::Runtime::SyntaxNode
|
21
|
+
|
22
|
+
include ::RsyncConfig::Node::Crawlable
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
module RsyncConfig
|
3
|
+
|
4
|
+
grammar RsyncSecretsFile
|
5
|
+
|
6
|
+
rule start
|
7
|
+
lines
|
8
|
+
{
|
9
|
+
def to_hash
|
10
|
+
users = {}
|
11
|
+
crawl users
|
12
|
+
|
13
|
+
users
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
rule lines
|
19
|
+
line? (EOL+ lines)?
|
20
|
+
end
|
21
|
+
|
22
|
+
rule line
|
23
|
+
comment
|
24
|
+
/ user_record
|
25
|
+
/ whitespace+
|
26
|
+
end
|
27
|
+
|
28
|
+
rule comment
|
29
|
+
'#' [^\n]*
|
30
|
+
end
|
31
|
+
|
32
|
+
rule user_record
|
33
|
+
user:([^:]+) ':' password:([^\t\r\n\ ]+)
|
34
|
+
{
|
35
|
+
def action users
|
36
|
+
users[user_value] = password_value
|
37
|
+
end
|
38
|
+
|
39
|
+
def user_value
|
40
|
+
user.text_value
|
41
|
+
end
|
42
|
+
|
43
|
+
def password_value
|
44
|
+
password.text_value
|
45
|
+
end
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
rule whitespace
|
50
|
+
[ \t\r\n]
|
51
|
+
end
|
52
|
+
|
53
|
+
rule EOL
|
54
|
+
"\r\n" / "\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rsync_config/parser/node_extension'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
module Propertiable
|
4
|
+
|
5
|
+
def self.included base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def allow_properties *properties
|
12
|
+
properties.each do |property|
|
13
|
+
property = property.to_s if property.is_a? Symbol
|
14
|
+
property = property.downcase.strip.gsub(/_/, ' ')
|
15
|
+
allowed_properties.push property
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def allowed_properties
|
20
|
+
@allowed_properties ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def allowed_property? property
|
24
|
+
allowed_properties.include? property
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key, local_only = false)
|
30
|
+
key = sanitize_key(key)
|
31
|
+
return nil if key.nil?
|
32
|
+
|
33
|
+
value = properties[key]
|
34
|
+
return @parent_config[key] if !local_only && value.nil? && @parent_config.respond_to?(:[])
|
35
|
+
|
36
|
+
value
|
37
|
+
end
|
38
|
+
|
39
|
+
def sanitize_key(key)
|
40
|
+
key = key.to_s unless key.is_a? String
|
41
|
+
key = key.strip.downcase
|
42
|
+
return nil if key.length == 0
|
43
|
+
return nil unless self.class.allowed_property? key
|
44
|
+
key
|
45
|
+
end
|
46
|
+
|
47
|
+
def []=(key, value)
|
48
|
+
key = sanitize_key(key)
|
49
|
+
if value.nil?
|
50
|
+
properties.delete key
|
51
|
+
else
|
52
|
+
properties[key] = value.to_s unless value.nil?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def properties_to_a
|
57
|
+
properties.map { |key, value| "#{key} = #{value}" }
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def properties
|
63
|
+
@properties ||= {}
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
module UserManagement
|
4
|
+
|
5
|
+
def self.included base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def parse_secrets_file(content)
|
12
|
+
raise 'Cannot process nil' if content.nil?
|
13
|
+
|
14
|
+
Treetop.load 'lib/rsync_config/parser/secrets_file'
|
15
|
+
parser = RsyncSecretsFileParser.new
|
16
|
+
p = parser.parse content
|
17
|
+
|
18
|
+
unless p.nil?
|
19
|
+
return p.to_hash
|
20
|
+
else
|
21
|
+
raise RuntimeError, parser.failure_reason
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def users
|
28
|
+
return @parent_config.users if @parent_config && @users.nil?
|
29
|
+
local_users
|
30
|
+
end
|
31
|
+
|
32
|
+
def local_users
|
33
|
+
@users ||= {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def user? identifier
|
37
|
+
users.include? identifier
|
38
|
+
end
|
39
|
+
|
40
|
+
def users= (users_list)
|
41
|
+
@users = users_list if users_list.is_a?(Hash) || users_list.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def local_user_list
|
45
|
+
# we need to output only what is related to this specific node
|
46
|
+
local_users.keys.map do |user|
|
47
|
+
"#{user}:#{local_users[user]}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_secrets_file(file)
|
52
|
+
return if file.nil?
|
53
|
+
raise "Cannot write secrets file #{file}" if File.exists?(file) && ! File.writable?(file)
|
54
|
+
raise "Cannot create secrets file #{file}" if !File.exist?(file) && ! ( Dir.exists?(File.dirname(file)) && File.writable?(File.dirname(file)))
|
55
|
+
|
56
|
+
File.open(file, 'w') do |file|
|
57
|
+
file.write local_user_list.join "\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
correct_file_permissions file
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_secrets_file(file)
|
64
|
+
# fails silently if no parameter is found OR the file does not exist on disk
|
65
|
+
return if file.nil? || !(File.exist? file)
|
66
|
+
|
67
|
+
# fails is the file exists but cannot be read, since it's suspicious
|
68
|
+
raise "Cannot load secrets file #{file}" unless File.readable?(file)
|
69
|
+
|
70
|
+
File.open(file, 'r') do |file|
|
71
|
+
self.users = self.class.parse_secrets_file(file.read)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def correct_file_permissions(file)
|
78
|
+
if File.world_readable?(file) || File.world_writable?(file)
|
79
|
+
# reuse existing permissions but remove everything from world
|
80
|
+
File.chmod (File.stat(file).mode & 0770), file
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/lib/rsync_config.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
require 'rsync_config/version'
|
4
|
+
require 'rsync_config/parser'
|
5
|
+
require 'rsync_config/propertiable'
|
6
|
+
require 'rsync_config/user_management'
|
7
|
+
require 'rsync_config/module'
|
8
|
+
require 'rsync_config/config'
|
9
|
+
|
10
|
+
module RsyncConfig
|
11
|
+
|
12
|
+
def self.load_file(config_file)
|
13
|
+
Config.load_file config_file
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse(content)
|
17
|
+
Config.parse content
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rsync_config/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rsync_config"
|
8
|
+
spec.version = RsyncConfig::VERSION
|
9
|
+
spec.authors = ["Guillaume Bodi"]
|
10
|
+
spec.email = ["bodi.giyomu@gmail.com"]
|
11
|
+
spec.description = %q{Utility gem to manage rsyncd config files}
|
12
|
+
spec.summary = %q{Utility gem to manage rsyncd config files}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "GPLv3"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "test-unit"
|
24
|
+
|
25
|
+
spec.add_dependency 'treetop', '~>1.4.14'
|
26
|
+
end
|
data/test/etc/out/.keep
ADDED
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
uid = nobody
|
2
|
+
gid = nobody
|
3
|
+
use chroot = no
|
4
|
+
max connections = 4
|
5
|
+
syslog facility = local5
|
6
|
+
pid file = /run/rsyncd.pid
|
7
|
+
secrets file = test/etc/secrets_top.conf
|
8
|
+
|
9
|
+
[ftp]
|
10
|
+
path = /srv/ftp
|
11
|
+
comment = ftp area
|
12
|
+
secrets file = test/etc/secrets_ftp.conf
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'test-unit'
|
2
|
+
require 'rsync_config'
|
3
|
+
|
4
|
+
class RsyncConfigTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
TEST_INPUT_FILE = File.join(__dir__, 'etc', 'rsyncd.conf')
|
7
|
+
|
8
|
+
TEST_INPUT_FILE_WITH_SECRETS = File.join(__dir__, 'etc', 'rsyncd_with_secrets.conf')
|
9
|
+
|
10
|
+
TEST_OUTPUT_FILE = File.join(__dir__, 'etc', 'out', 'rsyncd.conf')
|
11
|
+
|
12
|
+
TEST_SECRETS_FILE = File.join(__dir__, 'etc', 'out', 'secrets.conf')
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
File.delete TEST_OUTPUT_FILE if File.exists? TEST_OUTPUT_FILE
|
16
|
+
File.delete TEST_SECRETS_FILE if File.exists? TEST_SECRETS_FILE
|
17
|
+
end
|
18
|
+
|
19
|
+
def make_new_config
|
20
|
+
RsyncConfig::Config.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def make_simple_config
|
24
|
+
RsyncConfig.load_file TEST_INPUT_FILE
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_secrets_config
|
28
|
+
RsyncConfig.load_file TEST_INPUT_FILE_WITH_SECRETS
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_load_missing_file
|
32
|
+
assert_raise RuntimeError, 'Did not throw an error for missing file' do
|
33
|
+
RsyncConfig.load_file 'idontexist.conf'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_load_file_does_not_crash
|
38
|
+
config = nil
|
39
|
+
assert_nothing_raised RuntimeError do
|
40
|
+
config = make_simple_config
|
41
|
+
end
|
42
|
+
|
43
|
+
assert_equal config.class, RsyncConfig::Config, 'Did not return a config object' end
|
44
|
+
|
45
|
+
def test_accessing_missing_properties_returns_nil
|
46
|
+
config = make_new_config
|
47
|
+
|
48
|
+
assert_nil config[:i_dont_exist]
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_accessing_a_property_returns_a_string
|
52
|
+
config = make_new_config
|
53
|
+
config[:uid] = 'nobody'
|
54
|
+
|
55
|
+
assert_equal 'nobody', config[:uid]
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_one_module_output
|
59
|
+
config = make_new_config
|
60
|
+
ftp_module = config.module :ftp
|
61
|
+
ftp_module[:path] = 'local'
|
62
|
+
expected = <<EOS
|
63
|
+
[ftp]
|
64
|
+
path = local
|
65
|
+
EOS
|
66
|
+
|
67
|
+
assert_equal expected.strip, config.to_config_file
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_module_listing
|
71
|
+
config = make_new_config
|
72
|
+
config.module :ftp
|
73
|
+
config.module :smb
|
74
|
+
config.module :something
|
75
|
+
|
76
|
+
assert_equal ['ftp', 'smb', 'something'], config.module_names
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_easy_accessor_complete
|
80
|
+
config = make_new_config
|
81
|
+
config[:uid] = 'nut'
|
82
|
+
config[:gid] = 'kiwi'
|
83
|
+
config[:uid] = config[:gid]
|
84
|
+
|
85
|
+
assert_equal 'kiwi', config[:uid]
|
86
|
+
assert_equal config[:gid], config[:uid]
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_remove_property
|
90
|
+
config = make_new_config
|
91
|
+
config[:uid] = 'true'
|
92
|
+
config[:comment] = 'false'
|
93
|
+
|
94
|
+
# remove the comment config
|
95
|
+
config[:comment] = nil
|
96
|
+
expected = <<EOS
|
97
|
+
uid = true
|
98
|
+
EOS
|
99
|
+
assert_equal expected.strip, config.to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_module_inherits_config
|
103
|
+
config = make_new_config
|
104
|
+
config[:uid] = 'true'
|
105
|
+
ftp_module = config.module :ftp
|
106
|
+
|
107
|
+
assert_equal 'true', ftp_module[:uid]
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_global_users_accessor_exists
|
111
|
+
config = make_new_config
|
112
|
+
|
113
|
+
assert_not_nil config.users
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_global_users_assignment
|
117
|
+
config = make_new_config
|
118
|
+
config.users = {'john' => 'password'}
|
119
|
+
|
120
|
+
assert_true config.user?('john')
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_global_user_existence
|
124
|
+
config = make_new_config
|
125
|
+
config.users = {'john' => 'password'}
|
126
|
+
|
127
|
+
assert_true config.user?('john')
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_global_user_inexistence
|
131
|
+
config = make_new_config
|
132
|
+
|
133
|
+
assert_false config.user?('georges')
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_module_user_existence
|
137
|
+
config = make_new_config
|
138
|
+
ftp_module = config.module :ftp
|
139
|
+
ftp_module.users = {'john' => 'password'}
|
140
|
+
|
141
|
+
assert_true ftp_module.user? 'john'
|
142
|
+
assert_false ftp_module.user? 'george'
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_module_user_inexistence
|
146
|
+
config = make_new_config
|
147
|
+
ftp_module = config.module :ftp
|
148
|
+
|
149
|
+
assert_false ftp_module.user? 'george'
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_module_user_inheritance
|
153
|
+
config = make_new_config
|
154
|
+
config.users = {'john' => 'password'}
|
155
|
+
ftp_module = config.module :ftp
|
156
|
+
ftp_module.users = {'george' => 'password'}
|
157
|
+
|
158
|
+
assert_false ftp_module.user? 'john'
|
159
|
+
assert_true ftp_module.user? 'george'
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_write_to_file_succeeds
|
163
|
+
config = make_new_config
|
164
|
+
config.write_to TEST_OUTPUT_FILE
|
165
|
+
|
166
|
+
assert_true File.exists? TEST_OUTPUT_FILE
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_write_to_file_has_correct_content
|
170
|
+
config = make_new_config
|
171
|
+
config[:uid] = 'test'
|
172
|
+
|
173
|
+
config.write_to TEST_OUTPUT_FILE
|
174
|
+
expected = <<EOL
|
175
|
+
uid = test
|
176
|
+
EOL
|
177
|
+
|
178
|
+
assert_equal expected.strip, File.read(TEST_OUTPUT_FILE).strip
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_write_to_secrets_file_succeeds
|
182
|
+
config = make_simple_config
|
183
|
+
config['secrets file'] = TEST_SECRETS_FILE
|
184
|
+
config.users['john'] = 'doe'
|
185
|
+
|
186
|
+
config.write_to TEST_OUTPUT_FILE
|
187
|
+
|
188
|
+
assert_true File.exists? TEST_SECRETS_FILE
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_write_to_secrets_file_has_correct_permissions
|
192
|
+
config = make_new_config
|
193
|
+
config['secrets file'] = TEST_SECRETS_FILE
|
194
|
+
config.users['john'] = 'doe'
|
195
|
+
|
196
|
+
config.write_to TEST_OUTPUT_FILE
|
197
|
+
|
198
|
+
assert_nil File.world_readable? TEST_SECRETS_FILE
|
199
|
+
assert_nil File.world_writable? TEST_SECRETS_FILE
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_write_correct_content_to_secrets_file
|
203
|
+
config = make_new_config
|
204
|
+
config['secrets file'] = TEST_SECRETS_FILE
|
205
|
+
config.users['john'] = 'doe'
|
206
|
+
|
207
|
+
config.write_to TEST_OUTPUT_FILE
|
208
|
+
|
209
|
+
secrets_expected = <<EOL
|
210
|
+
john:doe
|
211
|
+
EOL
|
212
|
+
assert_equal secrets_expected.strip, File.read(TEST_SECRETS_FILE).strip
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_module_secrets_user_exists
|
216
|
+
config = make_secrets_config
|
217
|
+
|
218
|
+
assert_true config.module(:ftp).user? 'john'
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_global_secrets_user_exists
|
222
|
+
config = make_secrets_config
|
223
|
+
|
224
|
+
assert_true config.user? 'bob'
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_module_overriden_secrets_does_not_include_global_users
|
228
|
+
config = make_secrets_config
|
229
|
+
|
230
|
+
assert_false config.module(:ftp).user? 'bob'
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rsync_config
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guillaume Bodi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: treetop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.4.14
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.4.14
|
69
|
+
description: Utility gem to manage rsyncd config files
|
70
|
+
email:
|
71
|
+
- bodi.giyomu@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/rsync_config.rb
|
82
|
+
- lib/rsync_config/config.rb
|
83
|
+
- lib/rsync_config/module.rb
|
84
|
+
- lib/rsync_config/parser.rb
|
85
|
+
- lib/rsync_config/parser/config_file.treetop
|
86
|
+
- lib/rsync_config/parser/node_extension.rb
|
87
|
+
- lib/rsync_config/parser/secrets_file.treetop
|
88
|
+
- lib/rsync_config/propertiable.rb
|
89
|
+
- lib/rsync_config/user_management.rb
|
90
|
+
- lib/rsync_config/version.rb
|
91
|
+
- rsync_config.gemspec
|
92
|
+
- test/etc/out/.keep
|
93
|
+
- test/etc/rsyncd.conf
|
94
|
+
- test/etc/rsyncd_with_secrets.conf
|
95
|
+
- test/etc/secrets_ftp.conf
|
96
|
+
- test/etc/secrets_top.conf
|
97
|
+
- test/test_rsync_config.rb
|
98
|
+
homepage: ''
|
99
|
+
licenses:
|
100
|
+
- GPLv3
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.0.5
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Utility gem to manage rsyncd config files
|
122
|
+
test_files:
|
123
|
+
- test/etc/out/.keep
|
124
|
+
- test/etc/rsyncd.conf
|
125
|
+
- test/etc/rsyncd_with_secrets.conf
|
126
|
+
- test/etc/secrets_ftp.conf
|
127
|
+
- test/etc/secrets_top.conf
|
128
|
+
- test/test_rsync_config.rb
|