ajimi 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 +11 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Ajimifile.sample +50 -0
- data/Gemfile +4 -0
- data/Guardfile +43 -0
- data/LICENSE.txt +21 -0
- data/README.md +256 -0
- data/Rakefile +6 -0
- data/ajimi.gemspec +32 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/ajimi +6 -0
- data/exe/ajimi-init +9 -0
- data/lib/ajimi.rb +6 -0
- data/lib/ajimi/checker.rb +210 -0
- data/lib/ajimi/client.rb +81 -0
- data/lib/ajimi/config.rb +44 -0
- data/lib/ajimi/reporter.rb +23 -0
- data/lib/ajimi/reporter/template.erb +26 -0
- data/lib/ajimi/server.rb +68 -0
- data/lib/ajimi/server/entry.rb +49 -0
- data/lib/ajimi/server/ssh.rb +37 -0
- data/lib/ajimi/version.rb +3 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f13196aaa42aa1ee9ffa66c1cb2d981301913299
|
4
|
+
data.tar.gz: edc36ca1ec66ad4ee23c3d0589c17e8b48aaef8e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: afb2e37b084b8ac498f5d0b2326d06b238f92d0912b09e6d8b811e1281e304d581fb6a4e578dbbf000d64e0139155d361aa902b0492266bcc97e2e96d9095b2a
|
7
|
+
data.tar.gz: e3d1c6e525954f974d5f382445aa1b26eece64c80ef63a6bf0dcecf3299d7262f88a683729b9099a2cddc41104217656ca88c34e409fbf3e9cbc47bf72a8e7ab
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Ajimifile.sample
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Ajimi configuration file
|
2
|
+
|
3
|
+
# source setting
|
4
|
+
source "source.example.com", {
|
5
|
+
ssh_options: {
|
6
|
+
host: "192.168.0.1",
|
7
|
+
user: "ec2-user",
|
8
|
+
key: "~/.ssh/id_rsa"
|
9
|
+
},
|
10
|
+
enable_nice: true
|
11
|
+
}
|
12
|
+
|
13
|
+
# target setting
|
14
|
+
target "target.example.com", {
|
15
|
+
ssh_options: {
|
16
|
+
user: "ec2-user",
|
17
|
+
key: "~/.ssh/id_rsa"
|
18
|
+
},
|
19
|
+
enable_nice: false
|
20
|
+
}
|
21
|
+
|
22
|
+
# check setting
|
23
|
+
check_root_path "/"
|
24
|
+
|
25
|
+
pruned_paths [
|
26
|
+
"/dev",
|
27
|
+
"/proc",
|
28
|
+
]
|
29
|
+
|
30
|
+
ignored_paths [
|
31
|
+
*@config[:pruned_paths],
|
32
|
+
%r|^/lost\+found/?.*|,
|
33
|
+
%r|^/media/?.*|,
|
34
|
+
%r|^/mnt/?.*|,
|
35
|
+
%r|^/run/?.*|,
|
36
|
+
%r|^/sys/?.*|,
|
37
|
+
%r|^/tmp/?.*|
|
38
|
+
]
|
39
|
+
|
40
|
+
ignored_contents ({
|
41
|
+
"/root/.ssh/authorized_keys" => /Please login as the user \\"ec2-user\\" rather than the user \\"root\\"/
|
42
|
+
})
|
43
|
+
|
44
|
+
pending_paths [
|
45
|
+
"/etc/sudoers"
|
46
|
+
]
|
47
|
+
|
48
|
+
pending_contents ({
|
49
|
+
"/etc/hosts" => /127\.0\.0\.1/
|
50
|
+
})
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
|
43
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Masayuki Morita
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
# Ajimi
|
2
|
+
|
3
|
+
Ajimi is a tool to compare servers by their files to shows difference in their configurations.
|
4
|
+
It is useful to achieve some kind of a regression test against a server by finding unexpected changes to its configuration file after running Chef, Ansible, or your own provisioning tool.
|
5
|
+
|
6
|
+
'Ajimi' means 'tasting' in Japanese. It was developed for originally replacing the existing server with the Chef's cookbook, but can be used for a general purpose of comparing two servers.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'ajimi'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install ajimi
|
23
|
+
|
24
|
+
## Configuration
|
25
|
+
|
26
|
+
Generate a sample configuration file to current directory:
|
27
|
+
|
28
|
+
$ ajimi-init
|
29
|
+
|
30
|
+
And then edit Ajimifile:
|
31
|
+
|
32
|
+
$ (Your favorite editor) ./Ajimifile
|
33
|
+
|
34
|
+
A sample configuration looks like the following:
|
35
|
+
|
36
|
+
```
|
37
|
+
# Ajimi configuration file
|
38
|
+
|
39
|
+
# source setting
|
40
|
+
source "source.example.com", {
|
41
|
+
ssh_options: {
|
42
|
+
host: "192.168.0.1",
|
43
|
+
user: "ec2-user",
|
44
|
+
key: "~/.ssh/id_rsa"
|
45
|
+
},
|
46
|
+
enable_nice: true
|
47
|
+
}
|
48
|
+
|
49
|
+
# target setting
|
50
|
+
target "target.example.com", {
|
51
|
+
ssh_options: {
|
52
|
+
user: "ec2-user",
|
53
|
+
key: "~/.ssh/id_rsa"
|
54
|
+
},
|
55
|
+
enable_nice: false
|
56
|
+
}
|
57
|
+
|
58
|
+
# check setting
|
59
|
+
check_root_path "/"
|
60
|
+
|
61
|
+
pruned_paths [
|
62
|
+
"/dev",
|
63
|
+
"/proc",
|
64
|
+
]
|
65
|
+
|
66
|
+
ignored_paths [
|
67
|
+
*@config[:pruned_paths],
|
68
|
+
%r|^/lost\+found/?.*|,
|
69
|
+
%r|^/media/?.*|,
|
70
|
+
%r|^/mnt/?.*|,
|
71
|
+
%r|^/run/?.*|,
|
72
|
+
%r|^/sys/?.*|,
|
73
|
+
%r|^/tmp/?.*|
|
74
|
+
]
|
75
|
+
|
76
|
+
ignored_contents ({
|
77
|
+
"/root/.ssh/authorized_keys" => /Please login as the user \\"ec2-user\\" rather than the user \\"root\\"/
|
78
|
+
})
|
79
|
+
|
80
|
+
pending_paths [
|
81
|
+
"/etc/sudoers"
|
82
|
+
]
|
83
|
+
|
84
|
+
pending_contents ({
|
85
|
+
"/etc/hosts" => /127\.0\.0\.1/
|
86
|
+
})
|
87
|
+
|
88
|
+
```
|
89
|
+
|
90
|
+
The following arguments are supported in the Ajimifile:
|
91
|
+
|
92
|
+
* `source` - String (Required), Hash (Required): Source server's name and options. Options are as follows.
|
93
|
+
* `ssh_options` - Hash (Required): SSH connection options
|
94
|
+
* `host` - String (Optional): SSH hostname, FQDN or IP address. Default is name of `source`.
|
95
|
+
* `user` - String (Required): SSH username.
|
96
|
+
* `key` - String (Required): Path to user's SSH secret key.
|
97
|
+
* `enable_nice` - Boolean (Optional): If true, the find process is wrapped by nice and ionice to lower load. Default is false.
|
98
|
+
* `target` - String (Required): Target server's name and options. Options are the same as source.
|
99
|
+
* `check_root_path` - String (Required): Root path to check. If "/", Ajimi checks in the whole filesystem.
|
100
|
+
* `pruned_paths` - Array[String|Regexp] (Optional): List of the path which should be excluded in the find process. Note that `pruned_paths` is better performance than `ignored_paths`/`pending_paths`.
|
101
|
+
* `ignored_paths` - Array[String|Regexp] (Optional): List of the path which should be ignored as known difference.
|
102
|
+
* `ignored_contents` - Hash{String => String|Regexp} (Optional): Hash of the path => pattern which should be ignored as known difference for each of the content.
|
103
|
+
* `pending_paths`- Array[String|Regexp] (Optional): List of the path which should be resolved later but ignored temporarily as known difference.
|
104
|
+
* `pending_contents` - Hash{String => String|Regexp} (Optional): Hash of the path => pattern which should be resolved later but ignored temporarily as known difference for each of the content.
|
105
|
+
|
106
|
+
## Usage
|
107
|
+
|
108
|
+
Ajimi is a single command-line application: `ajimi`.
|
109
|
+
It takes a subcommand such as `check` or `exec`.
|
110
|
+
To view a list of the available commands , just run `ajimi` with no arguments:
|
111
|
+
|
112
|
+
```
|
113
|
+
$ ajimi
|
114
|
+
Commands:
|
115
|
+
ajimi check # Show differences between the source and the target server
|
116
|
+
ajimi dir <path> # Show differences between the source and the target server in the specified directory
|
117
|
+
ajimi exec source|target <command> # Execute an arbitrary command on the source or the target server
|
118
|
+
ajimi file <path> # Show differences between the source and the target server in the specified file
|
119
|
+
ajimi help [COMMAND] # Describe available commands or one specific command
|
120
|
+
|
121
|
+
Options:
|
122
|
+
[--ajimifile=AJIMIFILE] # Ajimifile path
|
123
|
+
# Default: ./Ajimifile
|
124
|
+
[--verbose], [--no-verbose]
|
125
|
+
# Default: true
|
126
|
+
```
|
127
|
+
|
128
|
+
After setting your Ajimifle, Run the following command in order to verify the SSH connection:
|
129
|
+
|
130
|
+
$ ajimi exec source hostname
|
131
|
+
$ ajimi exec target hostname
|
132
|
+
|
133
|
+
And then, first ajimi check with `--find-max-depth` option:
|
134
|
+
|
135
|
+
$ ajimi check --find-max-depth=3 > ajimi.log
|
136
|
+
|
137
|
+
Check the diffs report in ajimi.log, and add roughly unnecessary paths to `pruned_paths` in Ajimifile.
|
138
|
+
|
139
|
+
Next, gradually increasing `find-max-depth=4, 5, ...`,
|
140
|
+
|
141
|
+
$ ajimi check --find-max-depth=4 > ajimi.log
|
142
|
+
|
143
|
+
Add known differences to `ignored_paths` or `pending_paths`.
|
144
|
+
|
145
|
+
After checking the difference of paths, then check the contents of files where the difference has been reported:
|
146
|
+
|
147
|
+
$ ajimi check --enable-check-contents > ajimi.log
|
148
|
+
|
149
|
+
Add known differences to `ignored_contents` or `pending_contents`,
|
150
|
+
and repeat until the number of lines of diffs report becomes human-readable.
|
151
|
+
|
152
|
+
Finally, resolve issues and remove `pending_paths` or `pending_contents`.
|
153
|
+
|
154
|
+
## Command reference
|
155
|
+
|
156
|
+
```
|
157
|
+
$ ajimi
|
158
|
+
Commands:
|
159
|
+
ajimi check # Show differences between the source and the target server
|
160
|
+
ajimi dir <path> # Show differences between the source and the target server in the specified directory
|
161
|
+
ajimi exec source|target <command> # Execute an arbitrary command on the source or the target server
|
162
|
+
ajimi file <path> # Show differences between the source and the target server in the specified file
|
163
|
+
ajimi help [COMMAND] # Describe available commands or one specific command
|
164
|
+
|
165
|
+
Options:
|
166
|
+
[--ajimifile=AJIMIFILE] # Ajimifile path
|
167
|
+
# Default: ./Ajimifile
|
168
|
+
[--verbose], [--no-verbose]
|
169
|
+
# Default: true
|
170
|
+
```
|
171
|
+
|
172
|
+
```
|
173
|
+
$ ajimi help check
|
174
|
+
Usage:
|
175
|
+
ajimi check
|
176
|
+
|
177
|
+
Options:
|
178
|
+
[--check-root-path=CHECK_ROOT_PATH]
|
179
|
+
[--find-max-depth=N]
|
180
|
+
[--enable-check-contents], [--no-enable-check-contents]
|
181
|
+
[--limit-check-contents=N]
|
182
|
+
# Default: 0
|
183
|
+
[--ajimifile=AJIMIFILE] # Ajimifile path
|
184
|
+
# Default: ./Ajimifile
|
185
|
+
[--verbose], [--no-verbose]
|
186
|
+
# Default: true
|
187
|
+
|
188
|
+
Show differences between the source and the target server
|
189
|
+
```
|
190
|
+
|
191
|
+
```
|
192
|
+
$ ajimi help dir
|
193
|
+
Usage:
|
194
|
+
ajimi dir <path>
|
195
|
+
|
196
|
+
Options:
|
197
|
+
[--find-max-depth=N]
|
198
|
+
# Default: 1
|
199
|
+
[--ignored-pattern=IGNORED_PATTERN]
|
200
|
+
[--ajimifile=AJIMIFILE] # Ajimifile path
|
201
|
+
# Default: ./Ajimifile
|
202
|
+
[--verbose], [--no-verbose]
|
203
|
+
# Default: true
|
204
|
+
|
205
|
+
Show differences between the source and the target server in the specified directory
|
206
|
+
```
|
207
|
+
|
208
|
+
```
|
209
|
+
$ ajimi help file
|
210
|
+
Usage:
|
211
|
+
ajimi file <path>
|
212
|
+
|
213
|
+
Options:
|
214
|
+
[--ignored-pattern=IGNORED_PATTERN]
|
215
|
+
[--ajimifile=AJIMIFILE] # Ajimifile path
|
216
|
+
# Default: ./Ajimifile
|
217
|
+
[--verbose], [--no-verbose]
|
218
|
+
# Default: true
|
219
|
+
|
220
|
+
Show differences between the source and the target server in the specified file
|
221
|
+
```
|
222
|
+
|
223
|
+
```
|
224
|
+
$ ajimi help exec
|
225
|
+
Usage:
|
226
|
+
ajimi exec source|target <command>
|
227
|
+
|
228
|
+
Options:
|
229
|
+
[--ajimifile=AJIMIFILE] # Ajimifile path
|
230
|
+
# Default: ./Ajimifile
|
231
|
+
[--verbose], [--no-verbose]
|
232
|
+
# Default: true
|
233
|
+
|
234
|
+
Execute an arbitrary command on the source or the target server
|
235
|
+
```
|
236
|
+
|
237
|
+
## Development and Test
|
238
|
+
|
239
|
+
$ bundle install
|
240
|
+
$ bundle exec ajimi-init
|
241
|
+
(Implement some feature)
|
242
|
+
$ bundle exec rake spec
|
243
|
+
|
244
|
+
## Contributing
|
245
|
+
|
246
|
+
1. Fork it
|
247
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
248
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
249
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
250
|
+
5. Create new Pull Request
|
251
|
+
|
252
|
+
|
253
|
+
## License
|
254
|
+
|
255
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
256
|
+
|
data/Rakefile
ADDED
data/ajimi.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ajimi/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ajimi"
|
8
|
+
spec.version = Ajimi::VERSION
|
9
|
+
spec.authors = ["Masayuki Morita"]
|
10
|
+
spec.email = ["masayuki.morita@crowdworks.co.jp"]
|
11
|
+
|
12
|
+
spec.summary = %q{server diff tool}
|
13
|
+
spec.description = %q{Ajimi is a tool to compare servers by their files to shows difference in their configurations. It is useful to achieve some kind of a regression test against a server by finding unexpected changes to its configuration file after running Chef, Ansible, or your own provisioning tool.}
|
14
|
+
spec.homepage = "https://github.com/crowdworks/ajimi"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency "net-ssh", "~> 2.0"
|
23
|
+
spec.add_runtime_dependency "diff-lcs"
|
24
|
+
spec.add_runtime_dependency "thor"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
spec.add_development_dependency "guard-rspec", "~> 4.0"
|
30
|
+
spec.add_development_dependency "terminal-notifier-guard"
|
31
|
+
|
32
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "ajimi"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/ajimi
ADDED
data/exe/ajimi-init
ADDED
data/lib/ajimi.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'diff/lcs'
|
2
|
+
|
3
|
+
module Ajimi
|
4
|
+
class Checker
|
5
|
+
attr_accessor :diffs, :result, :source, :target, :diff_contents_cache, :enable_check_contents
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
@source = @config[:source]
|
10
|
+
@target = @config[:target]
|
11
|
+
@check_root_path = @config[:check_root_path]
|
12
|
+
@pruned_paths = @config[:pruned_paths] || []
|
13
|
+
@ignored_paths = @config[:ignored_paths] || []
|
14
|
+
@ignored_contents = @config[:ignored_contents] || {}
|
15
|
+
@pending_paths = @config[:pending_paths] || []
|
16
|
+
@pending_contents = @config[:pending_contents] || {}
|
17
|
+
@enable_check_contents = @config[:enable_check_contents] || false
|
18
|
+
@limit_check_contents = @config[:limit_check_contents] || 0
|
19
|
+
@find_max_depth = @config[:find_max_depth]
|
20
|
+
@verbose = @config[:verbose] || false
|
21
|
+
end
|
22
|
+
|
23
|
+
def check
|
24
|
+
puts_verbose "Start ajimi check with options: #{@config}\n"
|
25
|
+
|
26
|
+
puts_verbose "Finding...: #{@source.host}\n"
|
27
|
+
@source_find = @source.find(@check_root_path, @find_max_depth, @pruned_paths)
|
28
|
+
|
29
|
+
puts_verbose "Finding...: #{@target.host}\n"
|
30
|
+
@target_find = @target.find(@check_root_path, @find_max_depth, @pruned_paths)
|
31
|
+
|
32
|
+
puts_verbose "Checking diff entries...\n"
|
33
|
+
@diffs = diff_entries(@source_find, @target_find)
|
34
|
+
|
35
|
+
puts_verbose "Checking ignored_paths and pending_paths...\n"
|
36
|
+
@diffs = filter_ignored_and_pending_paths(@diffs, @ignored_paths, @pending_paths)
|
37
|
+
|
38
|
+
if @enable_check_contents
|
39
|
+
puts_verbose "Checking ignored_contents and pending_contents...\n"
|
40
|
+
@diffs = filter_ignored_and_pending_contents(@diffs, @ignored_contents, @pending_contents, @limit_check_contents)
|
41
|
+
end
|
42
|
+
|
43
|
+
puts_verbose "Diffs empty?: #{@diffs.empty?}\n"
|
44
|
+
puts_verbose "\n"
|
45
|
+
|
46
|
+
@result = @diffs.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
def puts_verbose(message)
|
50
|
+
puts message if @config[:verbose]
|
51
|
+
end
|
52
|
+
|
53
|
+
def diff_entries(source_find, target_find)
|
54
|
+
diffs = ::Diff::LCS.diff(source_find, target_find)
|
55
|
+
diffs.map do |diff|
|
56
|
+
diff.map do |change|
|
57
|
+
::Diff::LCS::Change.new(
|
58
|
+
change.action,
|
59
|
+
change.position,
|
60
|
+
Ajimi::Server::Entry.parse(change.element)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def filter_ignored_and_pending_paths(diffs, ignored_paths, pending_paths)
|
67
|
+
if !ignored_paths.empty?
|
68
|
+
@ignored_by_path = filter_paths(diffs, ignored_paths)
|
69
|
+
diffs = remove_entry_from_diffs(diffs, @ignored_by_path)
|
70
|
+
end
|
71
|
+
|
72
|
+
if !pending_paths.empty?
|
73
|
+
@pending_by_path = filter_paths(diffs, pending_paths)
|
74
|
+
diffs = remove_entry_from_diffs(diffs, @pending_by_path)
|
75
|
+
end
|
76
|
+
diffs
|
77
|
+
end
|
78
|
+
|
79
|
+
def filter_paths(diffs, filter_paths = [])
|
80
|
+
filtered = []
|
81
|
+
union_regexp_pattern = Regexp.union(filter_paths)
|
82
|
+
diffs.each do |diff|
|
83
|
+
diff.each do |change|
|
84
|
+
filtered << change.element.path if change.element.path.match union_regexp_pattern
|
85
|
+
end
|
86
|
+
end
|
87
|
+
filtered.uniq.sort
|
88
|
+
end
|
89
|
+
|
90
|
+
def filter_ignored_and_pending_contents(diffs, ignored_contents, pending_contents, limit_check_contents)
|
91
|
+
@ignored_by_content = []
|
92
|
+
@pending_by_content = []
|
93
|
+
@diff_contents_cache = ""
|
94
|
+
|
95
|
+
diff_files = product_set_file_paths(diffs)
|
96
|
+
diff_files = diff_files.slice(0, limit_check_contents) if limit_check_contents > 0
|
97
|
+
diff_files.each do |file|
|
98
|
+
diff_file_result = diff_file(file)
|
99
|
+
ignored_diff_file_result = filter_diff_file(diff_file_result, ignored_contents[file])
|
100
|
+
if ignored_diff_file_result.flatten.empty?
|
101
|
+
@ignored_by_content << file
|
102
|
+
else
|
103
|
+
pending_diff_file_result = filter_diff_file(ignored_diff_file_result, pending_contents[file])
|
104
|
+
if pending_diff_file_result.flatten.empty?
|
105
|
+
@pending_by_content << file
|
106
|
+
else
|
107
|
+
@diff_contents_cache << "--- #{@source.host}: #{file}\n"
|
108
|
+
@diff_contents_cache << "+++ #{@target.host}: #{file}\n"
|
109
|
+
@diff_contents_cache << "\n"
|
110
|
+
pending_diff_file_result.each do |diff|
|
111
|
+
diff.map do |change|
|
112
|
+
@diff_contents_cache << change.action + " " + change.position.to_s + " " + change.element.to_s + "\n"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
diffs = remove_entry_from_diffs(diffs, @ignored_by_content + @pending_by_content)
|
119
|
+
end
|
120
|
+
|
121
|
+
def uniq_diff_file_paths(diffs)
|
122
|
+
return [] if diffs.flatten.empty?
|
123
|
+
diff_file_paths = []
|
124
|
+
diffs.each do |diff|
|
125
|
+
diff.each do |change|
|
126
|
+
diff_file_paths << change.element.path if change.element.file?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
diff_file_paths.uniq.sort
|
130
|
+
end
|
131
|
+
|
132
|
+
def split_diff_file_paths(diffs)
|
133
|
+
return [],[] if diffs.flatten.empty?
|
134
|
+
minus = []
|
135
|
+
plus = []
|
136
|
+
diffs.each do |diff|
|
137
|
+
diff.each do |change|
|
138
|
+
minus << change.element.path if change.action == "-" && change.element.file?
|
139
|
+
plus << change.element.path if change.action == "+" && change.element.file?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
return minus, plus
|
143
|
+
end
|
144
|
+
|
145
|
+
def union_set_diff_file_paths(diffs)
|
146
|
+
minus, plus = split_diff_file_paths(diffs)
|
147
|
+
(minus | plus).sort
|
148
|
+
end
|
149
|
+
|
150
|
+
def product_set_file_paths(diffs)
|
151
|
+
minus, plus = split_diff_file_paths(diffs)
|
152
|
+
(minus & plus).sort
|
153
|
+
end
|
154
|
+
|
155
|
+
def diff_file(path)
|
156
|
+
source_file = @source.cat_or_md5sum(path)
|
157
|
+
target_file = @target.cat_or_md5sum(path)
|
158
|
+
diffs = ::Diff::LCS.diff(source_file, target_file)
|
159
|
+
end
|
160
|
+
|
161
|
+
def filter_diff_file(diffs, filter_pattern = nil)
|
162
|
+
if filter_pattern.nil?
|
163
|
+
diffs
|
164
|
+
else
|
165
|
+
diffs.map do |diff|
|
166
|
+
diff.reject do |change|
|
167
|
+
change.element.match filter_pattern
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def remove_entry_from_diffs(diffs, remove_list)
|
174
|
+
diffs.map do |diff|
|
175
|
+
diff.reject do |change|
|
176
|
+
remove_list.include? change.element.path
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def source_file_count
|
182
|
+
@source_find ? @source_find.count : 0
|
183
|
+
end
|
184
|
+
|
185
|
+
def target_file_count
|
186
|
+
@target_find ? @target_find.count : 0
|
187
|
+
end
|
188
|
+
|
189
|
+
def ignored_by_path_file_count
|
190
|
+
@ignored_by_path ? @ignored_by_path.count : 0
|
191
|
+
end
|
192
|
+
|
193
|
+
def pending_by_path_file_count
|
194
|
+
@pending_by_path ? @pending_by_path.count : 0
|
195
|
+
end
|
196
|
+
|
197
|
+
def ignored_by_content_file_count
|
198
|
+
@ignored_by_content ? @ignored_by_content.count : 0
|
199
|
+
end
|
200
|
+
|
201
|
+
def pending_by_content_file_count
|
202
|
+
@pending_by_content ? @pending_by_content.count : 0
|
203
|
+
end
|
204
|
+
|
205
|
+
def diff_file_count
|
206
|
+
union_set_diff_file_paths(@diffs).count
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
data/lib/ajimi/client.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Ajimi
|
4
|
+
class Client < Thor
|
5
|
+
attr_accessor :checker, :reporter
|
6
|
+
|
7
|
+
class_option :ajimifile, :default => './Ajimifile', :desc => "Ajimifile path"
|
8
|
+
class_option :verbose, :type => :boolean, :default => true
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super
|
12
|
+
@config = Ajimi::Config.load(options[:ajimifile])
|
13
|
+
@config[:verbose] = options[:verbose] unless options[:verbose].nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "check", "Show differences between the source and the target server"
|
17
|
+
option :check_root_path, :type => :string
|
18
|
+
option :find_max_depth, :type => :numeric
|
19
|
+
option :enable_check_contents, :type => :boolean, :default => false
|
20
|
+
option :limit_check_contents, :type => :numeric, :default => 0
|
21
|
+
def check
|
22
|
+
@config.merge!( {
|
23
|
+
find_max_depth: options[:find_max_depth],
|
24
|
+
enable_check_contents: options[:enable_check_contents],
|
25
|
+
limit_check_contents: options[:limit_check_contents]
|
26
|
+
} )
|
27
|
+
@config[:check_root_path] = options[:check_root_path] if options[:check_root_path]
|
28
|
+
_check
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "dir <path>", "Show differences between the source and the target server in the specified directory"
|
32
|
+
option :find_max_depth, :type => :numeric, :default => 1
|
33
|
+
option :ignored_pattern, :type => :string
|
34
|
+
def dir(path)
|
35
|
+
@config.merge!( {
|
36
|
+
check_root_path: path,
|
37
|
+
find_max_depth: options[:find_max_depth],
|
38
|
+
enable_check_contents: false
|
39
|
+
} )
|
40
|
+
@config[:ignored_paths] << Regexp.new(options[:ignored_pattern]) if options[:ignored_pattern]
|
41
|
+
|
42
|
+
_check
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "file <path>", "Show differences between the source and the target server in the specified file"
|
46
|
+
option :ignored_pattern, :type => :string
|
47
|
+
def file(path)
|
48
|
+
@config.merge!( {
|
49
|
+
check_root_path: path,
|
50
|
+
enable_check_contents: true
|
51
|
+
} )
|
52
|
+
@config[:ignored_contents].merge!( { path => Regexp.new(options[:ignored_pattern]) } ) if options[:ignored_pattern]
|
53
|
+
|
54
|
+
_check
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "exec source|target <command>", "Execute an arbitrary command on the source or the target server"
|
58
|
+
def exec(server, command)
|
59
|
+
raise ArgumentError, "server option must be source or target" unless %w(source target).include? server
|
60
|
+
|
61
|
+
@server = @config[server.to_sym]
|
62
|
+
puts "Execute command at #{server}_host: #{@server.host}\n"
|
63
|
+
stdout = @server.command_exec(command)
|
64
|
+
puts "#{stdout}"
|
65
|
+
puts "\n"
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def _check
|
71
|
+
@checker ||= Checker.new(@config)
|
72
|
+
result = @checker.check
|
73
|
+
|
74
|
+
@reporter ||= Reporter.new(@checker)
|
75
|
+
@reporter.report
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
data/lib/ajimi/config.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Ajimi
|
2
|
+
class Config
|
3
|
+
|
4
|
+
attr_accessor :config
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@config = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load(path)
|
11
|
+
Ajimi::Config.new.tap do |obj|
|
12
|
+
obj.load_file(path)
|
13
|
+
end.config
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_file(path)
|
17
|
+
instance_eval(File.read(path), path) if path
|
18
|
+
end
|
19
|
+
|
20
|
+
CONFIG_KEYWORDS = %i(
|
21
|
+
source
|
22
|
+
target
|
23
|
+
check_root_path
|
24
|
+
pruned_paths
|
25
|
+
ignored_paths
|
26
|
+
ignored_contents
|
27
|
+
pending_paths
|
28
|
+
pending_contents
|
29
|
+
)
|
30
|
+
|
31
|
+
CONFIG_KEYWORDS.each do |keyword|
|
32
|
+
define_method(keyword) do |args|
|
33
|
+
@config[keyword] = args
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
%i| source target |.each do |server_role|
|
38
|
+
define_method server_role do |*args|
|
39
|
+
@config[server_role] = Ajimi::Server.new(*args)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Ajimi
|
4
|
+
class Reporter
|
5
|
+
|
6
|
+
def initialize(checker, report_template_path = nil)
|
7
|
+
@checker = checker
|
8
|
+
@report_template_path = report_template_path || File.expand_path('../reporter/template.erb', __FILE__)
|
9
|
+
end
|
10
|
+
|
11
|
+
def report
|
12
|
+
if @checker.result
|
13
|
+
puts "no diffs"
|
14
|
+
true
|
15
|
+
else
|
16
|
+
erb = File.read(@report_template_path)
|
17
|
+
puts ERB.new(erb, nil, '-').result(binding)
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
###### diff entries report ######
|
2
|
+
--- <%= @checker.source.host %>
|
3
|
+
+++ <%= @checker.target.host %>
|
4
|
+
|
5
|
+
<%- @checker.diffs.each do |diff| -%>
|
6
|
+
<%- diff.each do |change| -%>
|
7
|
+
<%= "#{change.action} #{change.position} #{change.element}" %>
|
8
|
+
<%- end -%>
|
9
|
+
<%- end -%>
|
10
|
+
|
11
|
+
###### diff contents report ######
|
12
|
+
<%- if @checker.enable_check_contents -%>
|
13
|
+
<%= @checker.diff_contents_cache -%>
|
14
|
+
<%- else -%>
|
15
|
+
check_contents was skipped (enable_check_contents = false)
|
16
|
+
<%- end -%>
|
17
|
+
|
18
|
+
###### diff summary report ######
|
19
|
+
source: <%= @checker.source_file_count %> files
|
20
|
+
target: <%= @checker.target_file_count %> files
|
21
|
+
ignored_by_path: <%= @checker.ignored_by_path_file_count %> files
|
22
|
+
pending_by_path: <%= @checker.pending_by_path_file_count %> files
|
23
|
+
ignored_by_content: <%= @checker.ignored_by_content_file_count %> files
|
24
|
+
pending_by_content: <%= @checker.pending_by_content_file_count %> files
|
25
|
+
diff: <%= @checker.diff_file_count %> files
|
26
|
+
|
data/lib/ajimi/server.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'ajimi/server/ssh'
|
2
|
+
require 'ajimi/server/entry'
|
3
|
+
|
4
|
+
module Ajimi
|
5
|
+
class Server
|
6
|
+
|
7
|
+
def initialize(name, **options)
|
8
|
+
@name = name
|
9
|
+
@options = options
|
10
|
+
@options[:ssh_options] = options[:ssh_options] || {}
|
11
|
+
@options[:ssh_options][:host] = options[:ssh_options][:host] || @name
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
self.instance_variable_get(:@name) == other.instance_variable_get(:@name)
|
16
|
+
self.instance_variable_get(:@options) == other.instance_variable_get(:@options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def host
|
20
|
+
@options[:ssh_options][:host]
|
21
|
+
end
|
22
|
+
|
23
|
+
def backend
|
24
|
+
@backend ||= Ajimi::Server::Ssh.new(@options[:ssh_options])
|
25
|
+
end
|
26
|
+
|
27
|
+
def command_exec(cmd)
|
28
|
+
backend.command_exec(cmd)
|
29
|
+
end
|
30
|
+
|
31
|
+
def find(dir, find_max_depth = nil, pruned_paths = [], enable_nice = nil)
|
32
|
+
enable_nice = @options[:enable_nice] if enable_nice.nil?
|
33
|
+
cmd = build_find_cmd(dir, find_max_depth, pruned_paths, enable_nice)
|
34
|
+
stdout = command_exec(cmd)
|
35
|
+
stdout.split(/\n/).map {|line| line.chomp }.sort
|
36
|
+
end
|
37
|
+
|
38
|
+
def entries(dir)
|
39
|
+
@entries ||= find(dir).map{ |line| Ajimi::Server::Entry.parse(line) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def cat(file)
|
43
|
+
stdout = command_exec("sudo cat #{file}")
|
44
|
+
stdout.split(/\n/).map {|line| line.chomp }
|
45
|
+
end
|
46
|
+
|
47
|
+
def cat_or_md5sum(file)
|
48
|
+
stdout = command_exec("if (sudo file -b #{file} | grep text > /dev/null 2>&1) ; then (sudo cat #{file}) else (sudo md5sum #{file}) fi")
|
49
|
+
stdout.split(/\n/).map {|line| line.chomp }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def build_find_cmd(dir, find_max_depth = nil, pruned_paths = [], enable_nice = false)
|
55
|
+
cmd = "sudo"
|
56
|
+
cmd += " nice -n 19 ionice -c 2 -n 7" if enable_nice
|
57
|
+
cmd += " find #{dir} -ls"
|
58
|
+
cmd += " -maxdepth #{find_max_depth}" if find_max_depth
|
59
|
+
cmd += build_pruned_paths_option(pruned_paths)
|
60
|
+
cmd += " | awk '{printf \"%s, %s, %s, %s, %s\\n\", \$11, \$3, \$5, \$6, \$7}'"
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_pruned_paths_option(pruned_paths = [])
|
64
|
+
pruned_paths.map{ |path| " -path #{path} -prune" }.join(" -o")
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Ajimi
|
2
|
+
class Server
|
3
|
+
class Entry
|
4
|
+
attr_accessor :path, :mode, :user, :group, :bytes
|
5
|
+
|
6
|
+
def initialize(params)
|
7
|
+
@path = params[:path]
|
8
|
+
@mode = params[:mode]
|
9
|
+
@user = params[:user]
|
10
|
+
@group = params[:group]
|
11
|
+
@bytes = params[:bytes]
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
self.path == other.path &&
|
16
|
+
self.mode == other.mode &&
|
17
|
+
self.user == other.user &&
|
18
|
+
self.group == other.group &&
|
19
|
+
self.bytes == other.bytes
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"#{@path}, #{@mode}, #{@user}, #{@group}, #{@bytes}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def dir?
|
27
|
+
@mode[0] == "d"
|
28
|
+
end
|
29
|
+
|
30
|
+
def file?
|
31
|
+
@mode[0] == "-"
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
def parse(line)
|
36
|
+
path, mode, user, group, bytes = line.chomp.split(', ')
|
37
|
+
Ajimi::Server::Entry.new(
|
38
|
+
path: path,
|
39
|
+
mode: mode,
|
40
|
+
user: user,
|
41
|
+
group: group,
|
42
|
+
bytes: bytes
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
module Ajimi
|
4
|
+
class Server
|
5
|
+
class Ssh
|
6
|
+
def initialize(options = {})
|
7
|
+
@host = options[:host]
|
8
|
+
@user = options[:user]
|
9
|
+
@key = options[:key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def net_ssh
|
13
|
+
Net::SSH
|
14
|
+
end
|
15
|
+
|
16
|
+
def command_exec(cmd)
|
17
|
+
ssh_options_default = {}
|
18
|
+
ssh_options_override = {
|
19
|
+
keys: @key
|
20
|
+
}
|
21
|
+
ssh_options = ssh_options_default.merge(ssh_options_override)
|
22
|
+
|
23
|
+
stdout = ""
|
24
|
+
stderr = ""
|
25
|
+
net_ssh.start(@host, @user, ssh_options) do |session|
|
26
|
+
session.exec!(cmd) do |channel, stream, data|
|
27
|
+
stdout << data if stream == :stdout
|
28
|
+
stderr << data if stream == :stderr
|
29
|
+
end
|
30
|
+
end
|
31
|
+
$stderr.puts stderr unless stderr == ""
|
32
|
+
stdout
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ajimi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Masayuki Morita
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-ssh
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: diff-lcs
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: thor
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
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: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '4.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '4.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: terminal-notifier-guard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Ajimi is a tool to compare servers by their files to shows difference
|
126
|
+
in their configurations. It is useful to achieve some kind of a regression test
|
127
|
+
against a server by finding unexpected changes to its configuration file after running
|
128
|
+
Chef, Ansible, or your own provisioning tool.
|
129
|
+
email:
|
130
|
+
- masayuki.morita@crowdworks.co.jp
|
131
|
+
executables:
|
132
|
+
- ajimi
|
133
|
+
- ajimi-init
|
134
|
+
extensions: []
|
135
|
+
extra_rdoc_files: []
|
136
|
+
files:
|
137
|
+
- ".gitignore"
|
138
|
+
- ".rspec"
|
139
|
+
- ".travis.yml"
|
140
|
+
- Ajimifile.sample
|
141
|
+
- Gemfile
|
142
|
+
- Guardfile
|
143
|
+
- LICENSE.txt
|
144
|
+
- README.md
|
145
|
+
- Rakefile
|
146
|
+
- ajimi.gemspec
|
147
|
+
- bin/console
|
148
|
+
- bin/setup
|
149
|
+
- exe/ajimi
|
150
|
+
- exe/ajimi-init
|
151
|
+
- lib/ajimi.rb
|
152
|
+
- lib/ajimi/checker.rb
|
153
|
+
- lib/ajimi/client.rb
|
154
|
+
- lib/ajimi/config.rb
|
155
|
+
- lib/ajimi/reporter.rb
|
156
|
+
- lib/ajimi/reporter/template.erb
|
157
|
+
- lib/ajimi/server.rb
|
158
|
+
- lib/ajimi/server/entry.rb
|
159
|
+
- lib/ajimi/server/ssh.rb
|
160
|
+
- lib/ajimi/version.rb
|
161
|
+
homepage: https://github.com/crowdworks/ajimi
|
162
|
+
licenses:
|
163
|
+
- MIT
|
164
|
+
metadata: {}
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
requirements: []
|
180
|
+
rubyforge_project:
|
181
|
+
rubygems_version: 2.2.2
|
182
|
+
signing_key:
|
183
|
+
specification_version: 4
|
184
|
+
summary: server diff tool
|
185
|
+
test_files: []
|