inspec 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (247) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +65 -0
  4. data/.travis.yml +23 -0
  5. data/CHANGELOG.md +38 -0
  6. data/Gemfile +33 -0
  7. data/LICENSE +201 -0
  8. data/MAINTAINERS.md +28 -0
  9. data/MAINTAINERS.toml +42 -0
  10. data/README.md +257 -0
  11. data/Rakefile +47 -0
  12. data/bin/inspec +109 -0
  13. data/docs/ctl_inspec.rst +195 -0
  14. data/docs/dsl_inspec.rst +182 -0
  15. data/docs/readme.rst +100 -0
  16. data/docs/resources.rst +4319 -0
  17. data/docs/template.rst +51 -0
  18. data/examples/test-kitchen/.kitchen.yml +20 -0
  19. data/examples/test-kitchen/Berksfile +3 -0
  20. data/examples/test-kitchen/Gemfile +21 -0
  21. data/examples/test-kitchen/README.md +27 -0
  22. data/examples/test-kitchen/metadata.rb +7 -0
  23. data/examples/test-kitchen/recipes/default.rb +6 -0
  24. data/examples/test-kitchen/recipes/nginx.rb +30 -0
  25. data/examples/test-kitchen/test/integration/default/web_spec.rb +28 -0
  26. data/inspec.gemspec +30 -0
  27. data/lib/inspec.rb +20 -0
  28. data/lib/inspec/backend.rb +42 -0
  29. data/lib/inspec/dsl.rb +151 -0
  30. data/lib/inspec/log.rb +34 -0
  31. data/lib/inspec/metadata.rb +79 -0
  32. data/lib/inspec/plugins.rb +9 -0
  33. data/lib/inspec/plugins/resource.rb +62 -0
  34. data/lib/inspec/profile.rb +138 -0
  35. data/lib/inspec/profile_context.rb +170 -0
  36. data/lib/inspec/resource.rb +76 -0
  37. data/lib/inspec/rspec_json_formatter.rb +27 -0
  38. data/lib/inspec/rule.rb +170 -0
  39. data/lib/inspec/runner.rb +154 -0
  40. data/lib/inspec/shell.rb +66 -0
  41. data/lib/inspec/targets.rb +9 -0
  42. data/lib/inspec/targets/core.rb +27 -0
  43. data/lib/inspec/targets/dir.rb +67 -0
  44. data/lib/inspec/targets/file.rb +29 -0
  45. data/lib/inspec/targets/folder.rb +43 -0
  46. data/lib/inspec/targets/tar.rb +34 -0
  47. data/lib/inspec/targets/url.rb +39 -0
  48. data/lib/inspec/targets/zip.rb +47 -0
  49. data/lib/inspec/version.rb +7 -0
  50. data/lib/matchers/matchers.rb +221 -0
  51. data/lib/resources/apache.rb +29 -0
  52. data/lib/resources/apache_conf.rb +113 -0
  53. data/lib/resources/apt.rb +140 -0
  54. data/lib/resources/audit_policy.rb +63 -0
  55. data/lib/resources/auditd_conf.rb +56 -0
  56. data/lib/resources/auditd_rules.rb +53 -0
  57. data/lib/resources/bond.rb +65 -0
  58. data/lib/resources/bridge.rb +114 -0
  59. data/lib/resources/command.rb +57 -0
  60. data/lib/resources/csv.rb +32 -0
  61. data/lib/resources/directory.rb +15 -0
  62. data/lib/resources/etc_group.rb +150 -0
  63. data/lib/resources/file.rb +110 -0
  64. data/lib/resources/gem.rb +46 -0
  65. data/lib/resources/group.rb +132 -0
  66. data/lib/resources/host.rb +143 -0
  67. data/lib/resources/inetd_conf.rb +56 -0
  68. data/lib/resources/interface.rb +127 -0
  69. data/lib/resources/iptables.rb +65 -0
  70. data/lib/resources/json.rb +64 -0
  71. data/lib/resources/kernel_module.rb +40 -0
  72. data/lib/resources/kernel_parameter.rb +55 -0
  73. data/lib/resources/limits_conf.rb +55 -0
  74. data/lib/resources/login_def.rb +60 -0
  75. data/lib/resources/mysql.rb +81 -0
  76. data/lib/resources/mysql_conf.rb +116 -0
  77. data/lib/resources/mysql_session.rb +52 -0
  78. data/lib/resources/npm.rb +44 -0
  79. data/lib/resources/ntp_conf.rb +58 -0
  80. data/lib/resources/oneget.rb +63 -0
  81. data/lib/resources/os.rb +22 -0
  82. data/lib/resources/os_env.rb +34 -0
  83. data/lib/resources/package.rb +169 -0
  84. data/lib/resources/parse_config.rb +75 -0
  85. data/lib/resources/passwd.rb +93 -0
  86. data/lib/resources/pip.rb +75 -0
  87. data/lib/resources/port.rb +296 -0
  88. data/lib/resources/postgres.rb +37 -0
  89. data/lib/resources/postgres_conf.rb +87 -0
  90. data/lib/resources/postgres_session.rb +59 -0
  91. data/lib/resources/processes.rb +57 -0
  92. data/lib/resources/registry_key.rb +54 -0
  93. data/lib/resources/script.rb +34 -0
  94. data/lib/resources/security_policy.rb +73 -0
  95. data/lib/resources/service.rb +379 -0
  96. data/lib/resources/ssh_conf.rb +75 -0
  97. data/lib/resources/user.rb +374 -0
  98. data/lib/resources/windows_feature.rb +77 -0
  99. data/lib/resources/yaml.rb +23 -0
  100. data/lib/resources/yum.rb +154 -0
  101. data/lib/utils/convert.rb +12 -0
  102. data/lib/utils/detect.rb +15 -0
  103. data/lib/utils/find_files.rb +36 -0
  104. data/lib/utils/hash.rb +13 -0
  105. data/lib/utils/modulator.rb +12 -0
  106. data/lib/utils/parser.rb +61 -0
  107. data/lib/utils/simpleconfig.rb +115 -0
  108. data/tasks/maintainers.rb +213 -0
  109. data/test/docker_run.rb +156 -0
  110. data/test/docker_test.rb +51 -0
  111. data/test/helper.rb +200 -0
  112. data/test/integration/.kitchen.yml +42 -0
  113. data/test/integration/Berksfile +4 -0
  114. data/test/integration/cookbooks/os_prepare/metadata.rb +8 -0
  115. data/test/integration/cookbooks/os_prepare/recipes/apt.rb +20 -0
  116. data/test/integration/cookbooks/os_prepare/recipes/default.rb +9 -0
  117. data/test/integration/cookbooks/os_prepare/recipes/file.rb +21 -0
  118. data/test/integration/cookbooks/os_prepare/recipes/package.rb +26 -0
  119. data/test/integration/default/_debug_spec.rb +1 -0
  120. data/test/integration/default/apt_spec.rb +42 -0
  121. data/test/integration/default/file_spec.rb +109 -0
  122. data/test/integration/default/group_spec.rb +32 -0
  123. data/test/integration/default/kernel_module_spec.rb +17 -0
  124. data/test/integration/default/kernel_parameter_spec.rb +56 -0
  125. data/test/integration/default/package_spec.rb +11 -0
  126. data/test/integration/default/service_spec.rb +28 -0
  127. data/test/integration/default/user_spec.rb +44 -0
  128. data/test/resource/command_test.rb +33 -0
  129. data/test/resource/dsl_test.rb +45 -0
  130. data/test/resource/file_test.rb +130 -0
  131. data/test/resource/ssh_config.rb +9 -0
  132. data/test/resource/sshd_config.rb +9 -0
  133. data/test/test-extra.yaml +11 -0
  134. data/test/test.yaml +11 -0
  135. data/test/unit/mock/cmd/Get-NetAdapter +24 -0
  136. data/test/unit/mock/cmd/GetUserAccount +33 -0
  137. data/test/unit/mock/cmd/GetWin32Group +23 -0
  138. data/test/unit/mock/cmd/PATH +1 -0
  139. data/test/unit/mock/cmd/Resolve-DnsName +26 -0
  140. data/test/unit/mock/cmd/Test-NetConnection +4 -0
  141. data/test/unit/mock/cmd/auditctl +7 -0
  142. data/test/unit/mock/cmd/auditpol +2 -0
  143. data/test/unit/mock/cmd/brew-info-jq +1 -0
  144. data/test/unit/mock/cmd/chage-l-root +7 -0
  145. data/test/unit/mock/cmd/dpkg-s-curl +21 -0
  146. data/test/unit/mock/cmd/dscl +5 -0
  147. data/test/unit/mock/cmd/etc-apt +7 -0
  148. data/test/unit/mock/cmd/find-etc-rc-d-name-S +12 -0
  149. data/test/unit/mock/cmd/find-net-interface +9 -0
  150. data/test/unit/mock/cmd/gem-list-local-a-q-rubocop +1 -0
  151. data/test/unit/mock/cmd/get-net-tcpconnection +24 -0
  152. data/test/unit/mock/cmd/get-netadapter-binding-bridge +4 -0
  153. data/test/unit/mock/cmd/get-package-firefox +30 -0
  154. data/test/unit/mock/cmd/get-package-ruby +18 -0
  155. data/test/unit/mock/cmd/get-service-dhcp +10 -0
  156. data/test/unit/mock/cmd/get-windows-feature +7 -0
  157. data/test/unit/mock/cmd/getent-hosts-example.com +1 -0
  158. data/test/unit/mock/cmd/getent-passwd-root +1 -0
  159. data/test/unit/mock/cmd/id-chartmann +1 -0
  160. data/test/unit/mock/cmd/id-root +1 -0
  161. data/test/unit/mock/cmd/initctl-show-config-ssh +3 -0
  162. data/test/unit/mock/cmd/initctl-status-ssh +1 -0
  163. data/test/unit/mock/cmd/iptables-s +6 -0
  164. data/test/unit/mock/cmd/launchctl-list +3 -0
  165. data/test/unit/mock/cmd/ls-1-etc-init.d +2 -0
  166. data/test/unit/mock/cmd/ls-sys-class-net-br +2 -0
  167. data/test/unit/mock/cmd/lsmod +2 -0
  168. data/test/unit/mock/cmd/lsof-np-itcp +4 -0
  169. data/test/unit/mock/cmd/netstat-tulpen +5 -0
  170. data/test/unit/mock/cmd/npm-ls-g--json-bower +9 -0
  171. data/test/unit/mock/cmd/pacman-qi-curl +21 -0
  172. data/test/unit/mock/cmd/ping-example.com +6 -0
  173. data/test/unit/mock/cmd/pip-show-jinja2 +11 -0
  174. data/test/unit/mock/cmd/ps-aux +3 -0
  175. data/test/unit/mock/cmd/pw-usershow-root-7 +1 -0
  176. data/test/unit/mock/cmd/reg_schedule +1 -0
  177. data/test/unit/mock/cmd/rpm-qia-curl +24 -0
  178. data/test/unit/mock/cmd/sbin_sysctl +1 -0
  179. data/test/unit/mock/cmd/secedit-export +7 -0
  180. data/test/unit/mock/cmd/service-e +2 -0
  181. data/test/unit/mock/cmd/service-sendmail-onestatus +3 -0
  182. data/test/unit/mock/cmd/service-sshd-status +1 -0
  183. data/test/unit/mock/cmd/sockstat +5 -0
  184. data/test/unit/mock/cmd/success +0 -0
  185. data/test/unit/mock/cmd/systemctl-show-all-sshd +6 -0
  186. data/test/unit/mock/cmd/win32_product +8 -0
  187. data/test/unit/mock/cmd/yum-repolist-all +52 -0
  188. data/test/unit/mock/files/auditd.conf +4 -0
  189. data/test/unit/mock/files/bond0 +37 -0
  190. data/test/unit/mock/files/etcgroup +3 -0
  191. data/test/unit/mock/files/example.csv +6 -0
  192. data/test/unit/mock/files/inetd.conf +2 -0
  193. data/test/unit/mock/files/kitchen.yml +7 -0
  194. data/test/unit/mock/files/limits.conf +5 -0
  195. data/test/unit/mock/files/login.defs +5 -0
  196. data/test/unit/mock/files/mysql.conf +8 -0
  197. data/test/unit/mock/files/mysql2.conf +2 -0
  198. data/test/unit/mock/files/ntp.conf +5 -0
  199. data/test/unit/mock/files/passwd +2 -0
  200. data/test/unit/mock/files/policyfile.lock.json +12 -0
  201. data/test/unit/mock/files/ssh_config +5 -0
  202. data/test/unit/mock/files/sshd_config +7 -0
  203. data/test/unit/mock/profiles/empty/metadata.rb +0 -0
  204. data/test/unit/mock/profiles/metadata/metadata.rb +1 -0
  205. data/test/unit/profile_context_test.rb +140 -0
  206. data/test/unit/profile_test.rb +49 -0
  207. data/test/unit/resources/apt_test.rb +46 -0
  208. data/test/unit/resources/audit_policy_test.rb +13 -0
  209. data/test/unit/resources/auditd_conf_test.rb +15 -0
  210. data/test/unit/resources/auditd_rules_test.rb +21 -0
  211. data/test/unit/resources/bond_test.rb +24 -0
  212. data/test/unit/resources/bridge_test.rb +56 -0
  213. data/test/unit/resources/csv_test.rb +35 -0
  214. data/test/unit/resources/etc_group_test.rb +37 -0
  215. data/test/unit/resources/gem_test.rb +20 -0
  216. data/test/unit/resources/group_test.rb +96 -0
  217. data/test/unit/resources/host_test.rb +38 -0
  218. data/test/unit/resources/inetd_conf_test.rb +15 -0
  219. data/test/unit/resources/interface_test.rb +54 -0
  220. data/test/unit/resources/iptables_test.rb +30 -0
  221. data/test/unit/resources/json_test.rb +36 -0
  222. data/test/unit/resources/kernel_module_test.rb +23 -0
  223. data/test/unit/resources/kernel_parameter_test.rb +13 -0
  224. data/test/unit/resources/limits_conf_test.rb +14 -0
  225. data/test/unit/resources/login_def_test.rb +16 -0
  226. data/test/unit/resources/mysql_conf_test.rb +14 -0
  227. data/test/unit/resources/npm_test.rb +20 -0
  228. data/test/unit/resources/ntp_conf_test.rb +16 -0
  229. data/test/unit/resources/oneget_test.rb +45 -0
  230. data/test/unit/resources/os_env_test.rb +13 -0
  231. data/test/unit/resources/package_test.rb +51 -0
  232. data/test/unit/resources/passwd_test.rb +24 -0
  233. data/test/unit/resources/pip_test.rb +15 -0
  234. data/test/unit/resources/port_test.rb +46 -0
  235. data/test/unit/resources/processes_test.rb +32 -0
  236. data/test/unit/resources/registry_key_test.rb +19 -0
  237. data/test/unit/resources/script_test.rb +19 -0
  238. data/test/unit/resources/security_policy_test.rb +16 -0
  239. data/test/unit/resources/service_test.rb +116 -0
  240. data/test/unit/resources/ssh_conf_test.rb +33 -0
  241. data/test/unit/resources/user_test.rb +93 -0
  242. data/test/unit/resources/windows_feature.rb +17 -0
  243. data/test/unit/resources/yaml_test.rb +34 -0
  244. data/test/unit/resources/yum_test.rb +68 -0
  245. data/test/unit/simpleconfig_test.rb +80 -0
  246. data/test/unit/utils/content_parser_test.rb +30 -0
  247. metadata +555 -0
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ require 'yaml'
6
+
7
+ # Parses a yaml document
8
+ # Usage:
9
+ # describe yaml('.kitchen.yaml') do
10
+ # its('driver.name') { should eq('vagrant') }
11
+ # end
12
+ class YamlConfig < JsonConfig
13
+ name 'yaml'
14
+
15
+ # override file load and parse hash from yaml
16
+ def parse(content)
17
+ YAML.load(content)
18
+ end
19
+
20
+ def to_s
21
+ "YAML #{@path}"
22
+ end
23
+ end
@@ -0,0 +1,154 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ require 'resources/file'
6
+
7
+ # Usage:
8
+ # describe yum do
9
+ # its('repos') { should exist }
10
+ # end
11
+ #
12
+ # describe yum do
13
+ # its('repos') { should include 'base/7/x86_64' }
14
+ # its('epel') { should exist }
15
+ # its('epel') { should be_enabled }
16
+ # end
17
+ #
18
+ # Filter for a specific repo by name
19
+ # - use full identifier e.g. 'updates/7/x86_64'
20
+ # - use short identifier e.g. 'updates'
21
+ #
22
+ # describe yum.repo('epel') do
23
+ # it { should exist }
24
+ # it { should be_enabled }
25
+ # end
26
+ #
27
+ # deprecated:
28
+ # describe yumrepo('epel') do
29
+ # it { should exist }
30
+ # it { should be_enabled }
31
+ # end
32
+
33
+ class Yum < Inspec.resource(1)
34
+ name 'yum'
35
+
36
+ # returns all repositories
37
+ # works as following:
38
+ # search for Repo-id
39
+ # parse data in hashmap
40
+ # store data in object
41
+ # until \n
42
+ def repositories
43
+ return @cache if defined?(@cache)
44
+ # parse the repository data from yum
45
+ # we cannot use -C, because this is not reliable and may lead to errors
46
+ @command_result = inspec.command('yum -v repolist all')
47
+ @content = @command_result.stdout
48
+ @cache = []
49
+ repo = {}
50
+ in_repo = false
51
+ @content.each_line do |line|
52
+ # detect repo start
53
+ in_repo = true if line.match(/^\s*Repo-id\s*:\s*(.*)\b/)
54
+ # detect repo end
55
+ if line == "\n" && in_repo
56
+ in_repo = false
57
+ @cache.push(repo)
58
+ repo = {}
59
+ end
60
+ # parse repo content
61
+ if in_repo == true
62
+ val = /^\s*([^:]*?)\s*:\s*(.*?)\s*$/.match(line)
63
+ repo[repo_key(strip(val[1]))] = strip(val[2])
64
+ end
65
+ end
66
+ @cache
67
+ end
68
+
69
+ def repos
70
+ repositories.map { |repo| repo['id'] }
71
+ end
72
+
73
+ def repo(repo)
74
+ YumRepo.new(self, repo)
75
+ end
76
+
77
+ # alias for yum.repo('reponame')
78
+ def method_missing(name)
79
+ repo(name.to_s) if !name.nil?
80
+ end
81
+
82
+ def to_s
83
+ 'Yum Repository'
84
+ end
85
+
86
+ private
87
+
88
+ # Removes lefthand and righthand whitespace
89
+ def strip(value)
90
+ value.lstrip.rstrip if !value.nil?
91
+ end
92
+
93
+ # Optimize the key value
94
+ def repo_key(key)
95
+ return key if key.nil?
96
+ key.gsub('Repo-', '').downcase
97
+ end
98
+ end
99
+
100
+ class YumRepo
101
+ def initialize(yum, reponame)
102
+ @yum = yum
103
+ @reponame = reponame
104
+ end
105
+
106
+ # extracts the shortname from a repo id
107
+ # e.g. extras/7/x86_64 -> extras
108
+ def shortname(id)
109
+ val = %r{^\s*([^/]*?)/(.*?)\s*$}.match(id)
110
+ val.nil? ? nil : val[1]
111
+ end
112
+
113
+ def info
114
+ return @cache if defined?(@cache)
115
+ selection = @yum.repositories.select { |e| e['id'] == @reponame || shortname(e['id']) == @reponame }
116
+ @cache = selection[0] if !selection.nil? && selection.length == 1
117
+ @cache
118
+ end
119
+
120
+ def exist?
121
+ !info.nil?
122
+ end
123
+
124
+ def enabled?
125
+ repo = info
126
+ return false if repo.nil?
127
+ info['status'] == 'enabled'
128
+ end
129
+ end
130
+
131
+ # for compatability with serverspec
132
+ # this is deprecated syntax and will be removed in future versions
133
+ class YumRepoLegacy < Yum
134
+ name 'yumrepo'
135
+
136
+ def initialize(name)
137
+ super()
138
+ @repository = repo(name)
139
+ end
140
+
141
+ def exists?
142
+ deprecated
143
+ @repository.exist?
144
+ end
145
+
146
+ def enabled?
147
+ deprecated
148
+ @repository.enabled?
149
+ end
150
+
151
+ def deprecated
152
+ warn '[DEPRECATION] `yumrepo(reponame)` is deprecated. Please use `yum.repo(reponame)` instead.'
153
+ end
154
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ module Converter
6
+ # convert the value to an integer if we have numbers only
7
+ # otherwise we return the string
8
+ def convert_to_i(val)
9
+ val = val.to_i if val.match(/^\d+$/)
10
+ val
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ # Copyright 2015, Vulcano Security GmbH
3
+ # author: Christoph Hartmann
4
+ # author: Dominik Richter
5
+ # Tiny test file to return OS info of the tested node
6
+
7
+ # print OS detection infos
8
+ conf = {
9
+ name: os[:name],
10
+ family: os[:family],
11
+ release: os[:release],
12
+ arch: os[:arch],
13
+ }
14
+ puts JSON.dump(conf)
15
+ exit 0
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ # copyright: 2015, Vulcano Security GmbH
3
+ # license: All rights reserved
4
+ # author: Dominik Richter
5
+ # author: Christoph Hartmann
6
+
7
+ module FindFiles
8
+ TYPES = {
9
+ block: 'b',
10
+ character: 'c',
11
+ directory: 'd',
12
+ pipe: 'p',
13
+ file: 'f',
14
+ link: 'l',
15
+ socket: 's',
16
+ door: 'D',
17
+ }
18
+
19
+ def find_files(path, opts = {})
20
+ depth = opts[:depth]
21
+ type = TYPES[opts[:type].to_sym]
22
+
23
+ cmd = "find #{path}"
24
+ cmd += " -maxdepth #{depth.to_i}" if depth.to_i > 0
25
+ cmd += " -type #{type}" unless type.nil?
26
+
27
+ result = inspec.run_command(cmd)
28
+ exit_status = result.exit_status
29
+
30
+ return [nil, exit_status] unless exit_status == 0
31
+ files = result.stdout.split("\n")
32
+ .map(&:strip)
33
+ .find_all { |x| !x.empty? }
34
+ [files, exit_status]
35
+ end
36
+ end
data/lib/utils/hash.rb ADDED
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ # Inspired by: http://stackoverflow.com/a/9381776
3
+ # author: Dominik Richter
4
+ # author: Christoph Hartmann
5
+
6
+ class ::Hash
7
+ def deep_merge(second)
8
+ merger = proc { |_key, v1, v2|
9
+ v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2, &merger) : v2
10
+ }
11
+ merge(second, &merger)
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+ module Modulator
5
+ def modules
6
+ @modules ||= {}
7
+ end
8
+
9
+ def add_module(name, handler)
10
+ modules[name] = handler
11
+ end
12
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ module ContentParser
6
+ # Parse /etc/passwd files.
7
+ #
8
+ # @param [String] content the raw content of /etc/passwd
9
+ # @return [Array] Collection of passwd entries
10
+ def parse_passwd(content)
11
+ content.to_s.split("\n").map do |line|
12
+ parse_passwd_line(line)
13
+ end
14
+ end
15
+
16
+ # Parse a line of /etc/passwd
17
+ #
18
+ # @param [String] line a line of /etc/passwd
19
+ # @return [Hash] Map of entries in this line
20
+ def parse_passwd_line(line)
21
+ x = line.split(':')
22
+ {
23
+ 'name' => x.at(0),
24
+ 'password' => x.at(1),
25
+ 'uid' => x.at(2),
26
+ 'gid' => x.at(3),
27
+ 'desc' => x.at(4),
28
+ 'home' => x.at(5),
29
+ 'shell' => x.at(6),
30
+ }
31
+ end
32
+
33
+ # Parse a line with a command. For example: `a = b # comment`.
34
+ # Retrieves the actual content.
35
+ #
36
+ # @param [String] raw the content lines you want to be parsed
37
+ # @param [Hash] opts optional configuration
38
+ # @return [Array] contains the actual line and the position of the line end
39
+ def parse_comment_line(raw, opts)
40
+ idx_nl = raw.index("\n")
41
+ idx_comment = raw.index(opts[:comment_char])
42
+ idx_nl = raw.length if idx_nl.nil?
43
+ idx_comment = idx_nl + 1 if idx_comment.nil?
44
+ line = ''
45
+
46
+ # is a comment inside this line
47
+ if idx_comment < idx_nl && idx_comment != 0
48
+ line = raw[0..(idx_comment - 1)]
49
+ # in case we don't allow comments at the end
50
+ # of an assignment/statement, ignore it and fall
51
+ # back to treating this as a regular line
52
+ if opts[:standalone_comments] && !is_empty_line(line)
53
+ line = raw[0..(idx_nl - 1)]
54
+ end
55
+ # if there is no comment in this line
56
+ elsif idx_comment > idx_nl && idx_nl != 0
57
+ line = raw[0..(idx_nl - 1)]
58
+ end
59
+ [line, idx_nl]
60
+ end
61
+ end
@@ -0,0 +1,115 @@
1
+ # encoding: utf-8
2
+ # copyright: 2015, Dominik Richter
3
+ # license: All rights reserved
4
+ # author: Dominik Richter
5
+ # author: Christoph Hartmann
6
+
7
+ require 'utils/parser'
8
+
9
+ class SimpleConfig
10
+ include ContentParser
11
+
12
+ attr_reader :params, :groups
13
+ def initialize(raw_data, opts = {})
14
+ parse(raw_data, opts)
15
+ end
16
+
17
+ # Parse some data
18
+ # quotes: quoting characters, which are parsed, so everything inside
19
+ # it will be part of a string
20
+ # multiline: allow quoted text to span multiple lines
21
+ # comment_char: char which identifies comments
22
+ # standalone_comments: comments must appear alone in a line; if set to true,
23
+ # no comments can be added to the end of an assignment/statement line
24
+ def parse(raw_data, opts = nil)
25
+ @params = {}
26
+ @groups = []
27
+ @vals = @params
28
+ options = default_options.merge(opts || {})
29
+ return if raw_data.nil?
30
+
31
+ # prepare raw data if required
32
+ if !options[:line_separator].nil?
33
+ raw_data = raw_data.tr(options[:line_separator], "\n")
34
+ end
35
+ rest = raw_data
36
+ rest = parse_rest(rest, options) while rest.length > 0
37
+ end
38
+
39
+ private
40
+
41
+ def parse_values(match, values)
42
+ start_idx = 2
43
+ i = 0
44
+ count = values - 1
45
+ return match[start_idx] if (values == 1)
46
+
47
+ # iterate over expected parameters
48
+ values = []
49
+ loop do
50
+ values.push(match[start_idx + i])
51
+ i += 1
52
+ break if i > count
53
+ end
54
+ values
55
+ end
56
+
57
+ def parse_params_line(line, opts)
58
+ # now line contains what we are interested in parsing
59
+ # check if it is an assignment
60
+ m = opts[:assignment_re].match(line)
61
+ return nil if m.nil?
62
+
63
+ if opts[:multiple_values]
64
+ @vals[m[1]] ||= []
65
+ @vals[m[1]].push(parse_values(m, opts[:key_vals]))
66
+ else
67
+ @vals[m[1]] = parse_values(m, opts[:key_vals])
68
+ end
69
+ end
70
+
71
+ def parse_group_line(line, opts)
72
+ return nil if opts[:group_re].nil?
73
+ m = opts[:group_re].match(line)
74
+ return nil if m.nil?
75
+ @groups.push(m[1])
76
+ @vals = @params[m[1]] = {}
77
+ end
78
+
79
+ def parse_implicit_assignment_line(line, opts)
80
+ return nil if is_empty_line(line)
81
+ if opts[:multiple_values]
82
+ @vals[line.strip] ||= []
83
+ else
84
+ @vals[line.strip] = ''
85
+ end
86
+ end
87
+
88
+ def parse_rest(rest, opts)
89
+ line, idx_nl = parse_comment_line(rest, opts)
90
+ parse_params_line(line, opts) or
91
+ parse_group_line(line, opts) or
92
+ parse_implicit_assignment_line(line, opts)
93
+
94
+ # return whatever is left
95
+ rest[(idx_nl + 1)..-1] || ''
96
+ end
97
+
98
+ def is_empty_line(l)
99
+ l =~ /^\s*$/
100
+ end
101
+
102
+ def default_options
103
+ {
104
+ quotes: '',
105
+ multiline: false,
106
+ comment_char: '#',
107
+ line_separator: nil, # uses this char to seperate lines before parsing
108
+ assignment_re: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
109
+ group_re: /\[([^\]]+)\]\s*$/,
110
+ key_vals: 1, # default for key=value, may require for 'key val1 val2 val3'
111
+ standalone_comments: false,
112
+ multiple_values: false,
113
+ }
114
+ end
115
+ end