configmonkey_cli 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +87 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/bin/configmonkey +9 -0
- data/bin/configmonkey.sh +14 -0
- data/configmonkey_cli.gemspec +27 -0
- data/lib/configmonkey_cli.rb +43 -0
- data/lib/configmonkey_cli/application.rb +159 -0
- data/lib/configmonkey_cli/application/colorize.rb +22 -0
- data/lib/configmonkey_cli/application/configuration.rb +38 -0
- data/lib/configmonkey_cli/application/configuration.tpl +0 -0
- data/lib/configmonkey_cli/application/core.rb +78 -0
- data/lib/configmonkey_cli/application/dispatch.rb +81 -0
- data/lib/configmonkey_cli/application/manifest.rb +316 -0
- data/lib/configmonkey_cli/application/manifest_actions/base.rb +90 -0
- data/lib/configmonkey_cli/application/manifest_actions/chmod.rb +40 -0
- data/lib/configmonkey_cli/application/manifest_actions/copy.rb +38 -0
- data/lib/configmonkey_cli/application/manifest_actions/custom.rb +52 -0
- data/lib/configmonkey_cli/application/manifest_actions/inplace.rb +28 -0
- data/lib/configmonkey_cli/application/manifest_actions/invoke.rb +45 -0
- data/lib/configmonkey_cli/application/manifest_actions/link.rb +46 -0
- data/lib/configmonkey_cli/application/manifest_actions/mkdir.rb +41 -0
- data/lib/configmonkey_cli/application/manifest_actions/remove.rb +46 -0
- data/lib/configmonkey_cli/application/manifest_actions/rsync.rb +112 -0
- data/lib/configmonkey_cli/application/manifest_actions/rtfm.rb +32 -0
- data/lib/configmonkey_cli/application/manifest_actions/sync_links.rb +60 -0
- data/lib/configmonkey_cli/application/manifest_actions/template.rb +38 -0
- data/lib/configmonkey_cli/application/output_helper.rb +30 -0
- data/lib/configmonkey_cli/helper.rb +62 -0
- data/lib/configmonkey_cli/version.rb +4 -0
- metadata +162 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Base
|
5
|
+
attr_reader :app, :manifest, :args, :opts, :thor
|
6
|
+
|
7
|
+
def initialize app, manifest, *args, &block
|
8
|
+
@app = app
|
9
|
+
@thor = manifest.thor
|
10
|
+
@args = []
|
11
|
+
@opts = {}
|
12
|
+
@manifest = manifest
|
13
|
+
|
14
|
+
init(*args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"#<…::#{self.class.name.split("::").last(2).join("::")} @args=#{@args} @opts=#{@opts}>"
|
19
|
+
end
|
20
|
+
alias_method :inspect, :to_s
|
21
|
+
|
22
|
+
def args_and_opts hargs_and_opts = {}
|
23
|
+
args, opts = [], {}
|
24
|
+
hargs_and_opts.each do |k, v|
|
25
|
+
if k.is_a?(String)
|
26
|
+
args << k << v
|
27
|
+
elsif k.is_a?(Symbol)
|
28
|
+
opts[k] = v
|
29
|
+
else
|
30
|
+
raise "what the heck did you pass as a key?"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
[args, opts]
|
34
|
+
end
|
35
|
+
|
36
|
+
def rel path
|
37
|
+
thor.relative_to_original_destination_root(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def expand_src src = ""
|
41
|
+
File.join(manifest.directory, src)
|
42
|
+
end
|
43
|
+
|
44
|
+
def expand_dst dst = ""
|
45
|
+
File.join(thor.destination_root, dst)
|
46
|
+
end
|
47
|
+
|
48
|
+
def exists? *args
|
49
|
+
if args[0].is_a?(Hash)
|
50
|
+
File.exists?(send(:"expand_#{args[0].keys[0]}", args[0].values[0]))
|
51
|
+
else
|
52
|
+
File.exists?(args[0])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def status name, *args
|
57
|
+
case args.length
|
58
|
+
when 0
|
59
|
+
raise ArgumentError("at least name and string is required")
|
60
|
+
when 1 # status :fake, rel(@destination)
|
61
|
+
thor.say_status name, args[0], :green
|
62
|
+
when 2 # status :fake, :green, rel(@destination)
|
63
|
+
thor.say_status name, args[1], args[0]
|
64
|
+
when 3 # status :fake, :green, rel(@destination), :red
|
65
|
+
thor.say_status name, thor.set_color(args[1], *args[2..-1]), args[0]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
[:padded, :c, :say, :ask, :yes?, :no?].each do |meth|
|
70
|
+
define_method(meth) do |*args, &block|
|
71
|
+
manifest.send(meth, *args, &block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def init *a, &b
|
76
|
+
end
|
77
|
+
|
78
|
+
def prepare
|
79
|
+
end
|
80
|
+
|
81
|
+
def simulate &b
|
82
|
+
app.warn self.inspect
|
83
|
+
end
|
84
|
+
|
85
|
+
def destructive &b
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Chmod < Base
|
5
|
+
def init path, mode, opts = {}
|
6
|
+
@opts = opts.reverse_merge({
|
7
|
+
#_p: true
|
8
|
+
})
|
9
|
+
|
10
|
+
@args = [path, mode]
|
11
|
+
end
|
12
|
+
|
13
|
+
def prepare
|
14
|
+
@path = expand_dst(@args[0])
|
15
|
+
@mode = @args[1]
|
16
|
+
end
|
17
|
+
|
18
|
+
def simulate
|
19
|
+
if thor.options[:pretend]
|
20
|
+
destructive
|
21
|
+
else
|
22
|
+
status :fake, :black, "#{@args[0]} (#{@args[1].to_s(8)})"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def destructive
|
27
|
+
if File.exist?(@path)
|
28
|
+
if @mode == File.stat(@path).mode - 0100000
|
29
|
+
status :identical, :blue, "#{@args[0]} (#{@args[1].to_s(8)})"
|
30
|
+
else
|
31
|
+
thor.chmod(@path, @mode)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
status :noexist, :red, "#{@args[0]} (#{@args[1].to_s(8)})"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Copy < Base
|
5
|
+
def init hargs_and_opts = {}
|
6
|
+
@args, @opts = args_and_opts(hargs_and_opts)
|
7
|
+
end
|
8
|
+
|
9
|
+
def prepare
|
10
|
+
@opts[:force] = app.opts[:default_yes]
|
11
|
+
@source = @args[0]
|
12
|
+
@destination = expand_dst(@args[1])
|
13
|
+
end
|
14
|
+
|
15
|
+
def simulate
|
16
|
+
if thor.options[:pretend]
|
17
|
+
destructive
|
18
|
+
else
|
19
|
+
status :fake, :black, rel(@destination)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def destructive
|
24
|
+
absolute_source = File.join(thor.source_paths[0], @source)
|
25
|
+
if FileTest.directory?(absolute_source)
|
26
|
+
thor.directory(@source, @destination, @opts)
|
27
|
+
else
|
28
|
+
thor.copy_file(@source, @destination, @opts)
|
29
|
+
if @opts[:chmod] && File.exist?(absolute_source) && File.exist?(@destination)
|
30
|
+
mode = @opts[:chmod] == true ? File.stat(absolute_source).mode - 0100000 : @opts[:chmod]
|
31
|
+
thor.chmod(@destination, mode) unless mode == File.stat(@destination).mode - 0100000
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Custom < Base
|
5
|
+
def init label = nil, &block
|
6
|
+
@label = label
|
7
|
+
block.call(Proxy.new(self)) if block
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
@label ? super.gsub("Custom @args", "Custom @label=#{@label} @args") : super
|
12
|
+
end
|
13
|
+
|
14
|
+
def prepare
|
15
|
+
status :proc, :yellow, (@label || "unlabeled custom block"), :blue
|
16
|
+
end
|
17
|
+
|
18
|
+
def simulate
|
19
|
+
@args.each do |scope, block|
|
20
|
+
block.call(self, manifest) if scope == :always || scope == :simulate
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def destructive
|
25
|
+
@args.each do |scope, block|
|
26
|
+
block.call(self, manifest) if scope == :always || scope == :destructive
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Proxy
|
31
|
+
attr_reader :action
|
32
|
+
|
33
|
+
def initialize action
|
34
|
+
@action = action
|
35
|
+
end
|
36
|
+
|
37
|
+
def always &block
|
38
|
+
@action.args << [:always, block]
|
39
|
+
end
|
40
|
+
|
41
|
+
def simulate &block
|
42
|
+
@action.args << [:simulate, block]
|
43
|
+
end
|
44
|
+
|
45
|
+
def destructive &block
|
46
|
+
@action.args << [:destructive, block]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Inplace < Base
|
5
|
+
def init label = nil, &block
|
6
|
+
@label = label
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
@label ? super.gsub("Custom @args", "Custom @label=#{@label} @args") : super
|
12
|
+
end
|
13
|
+
|
14
|
+
def prepare
|
15
|
+
status :proc, :yellow, (@label || "unlabeled inplace block"), :blue
|
16
|
+
end
|
17
|
+
|
18
|
+
def simulate
|
19
|
+
destructive
|
20
|
+
end
|
21
|
+
|
22
|
+
def destructive
|
23
|
+
instance_exec(manifest, self, &@block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Invoke < Base
|
5
|
+
def init command = nil, opts = {}, &command_builder
|
6
|
+
if command_builder
|
7
|
+
command = "#{command}" # reference copy
|
8
|
+
command = command_builder.call(command, opts)
|
9
|
+
end
|
10
|
+
@args = [command]
|
11
|
+
@opts = opts.reverse_merge(chomp: true, echo: true, fail: true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def simulate
|
15
|
+
status :invoke, :yellow, @args[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
def destructive
|
19
|
+
status :invoke, :yellow, @args[0]
|
20
|
+
code, res = exec(@args[0], @opts[:chomp])
|
21
|
+
|
22
|
+
if opts[:echo]
|
23
|
+
if code.exitstatus.zero?
|
24
|
+
say padded("#{c "[OK]", :green} #{res}", :black)
|
25
|
+
else
|
26
|
+
say padded("[#{code.exitstatus}] #{res}", :red)
|
27
|
+
raise "Invoked process exited with status #{code.exitstatus}: #{res}" if opts[:fail]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def exec cmd, chomp = true
|
33
|
+
app.debug "§invoking:#{cmd}"
|
34
|
+
_stdin, _stdouterr, _thread = Open3.popen2e(cmd)
|
35
|
+
_thread.join
|
36
|
+
res = _stdouterr.read
|
37
|
+
[_thread.value, chomp ? res.chomp : res]
|
38
|
+
ensure
|
39
|
+
_stdin.close rescue false
|
40
|
+
_stdouterr.close rescue false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Link < Base
|
5
|
+
def init hargs_and_opts = {}
|
6
|
+
@args, @opts = args_and_opts(hargs_and_opts)
|
7
|
+
@opts = @opts.reverse_merge({
|
8
|
+
prefix: nil,
|
9
|
+
map: "d:d",
|
10
|
+
hard: false,
|
11
|
+
})
|
12
|
+
end
|
13
|
+
|
14
|
+
def prepare
|
15
|
+
@opts[:force] = app.opts[:default_yes]
|
16
|
+
@opts[:symbolic] = !@opts[:hard]
|
17
|
+
@source = @args[0]
|
18
|
+
@destination = @args[1]
|
19
|
+
map = @opts[:map].split(":")
|
20
|
+
|
21
|
+
# prefix source
|
22
|
+
@source = File.join(map[0]["d"] ? thor.destination_root : manifest.directory, @source)
|
23
|
+
@destination = File.join(map[1]["d"] ? thor.destination_root : manifest.directory, @destination)
|
24
|
+
|
25
|
+
# prefix target link
|
26
|
+
if(@opts[:prefix] && map[1].downcase["p"])
|
27
|
+
prefix = "cm--#{manifest.checksum(@opts[:prefix], soft: !map[1]["H"])}--"
|
28
|
+
@destination = File.join(File.dirname(@destination), "#{prefix}#{File.basename(@destination)}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def simulate
|
33
|
+
if thor.options[:pretend]
|
34
|
+
destructive
|
35
|
+
else
|
36
|
+
status :fake, :black, rel(@destination)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def destructive
|
41
|
+
thor.create_link(@destination, @source, @opts)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Mkdir < Base
|
5
|
+
def init directory, *sub_directories
|
6
|
+
@opts = sub_directories.extract_options!.reverse_merge({
|
7
|
+
#_p: true
|
8
|
+
})
|
9
|
+
|
10
|
+
# assemble directories
|
11
|
+
sub_directories.flatten!
|
12
|
+
if sub_directories.any?
|
13
|
+
@args = sub_directories.map {|d| File.join(directory, d) }
|
14
|
+
else
|
15
|
+
@args = [directory]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def prepare
|
20
|
+
@directories = @args.map{|dir| expand_dst(dir) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def simulate
|
24
|
+
if thor.options[:pretend]
|
25
|
+
destructive
|
26
|
+
else
|
27
|
+
@directories.each do |dir|
|
28
|
+
status :fake, :black, @args[0]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def destructive
|
34
|
+
@directories.each do |dir|
|
35
|
+
thor.empty_directory(dir, @opts)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Remove < Base
|
5
|
+
def init directory, *sub_directories
|
6
|
+
@opts = sub_directories.extract_options!.reverse_merge({
|
7
|
+
#_p: true
|
8
|
+
})
|
9
|
+
|
10
|
+
# assemble directories
|
11
|
+
sub_directories.flatten!
|
12
|
+
if sub_directories.any?
|
13
|
+
@args = sub_directories.map {|d| File.join(directory, d) }
|
14
|
+
else
|
15
|
+
@args = [directory]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def prepare
|
20
|
+
@opts[:force] = app.opts[:default_yes]
|
21
|
+
@directories = @args.map{|dir| expand_dst(dir) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def simulate
|
25
|
+
if thor.options[:pretend]
|
26
|
+
destructive
|
27
|
+
else
|
28
|
+
@directories.each do |dir|
|
29
|
+
status :fake, :black, @args[0]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def destructive
|
35
|
+
@directories.each do |dir|
|
36
|
+
if FileTest.directory?(dir)
|
37
|
+
thor.remove_dir(dir, @opts)
|
38
|
+
else
|
39
|
+
thor.remove_file(dir, @opts)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module ConfigmonkeyCli
|
2
|
+
class Application
|
3
|
+
module ManifestAction
|
4
|
+
class Rsync < Base
|
5
|
+
def init hargs_and_opts = {}
|
6
|
+
@args, @opts = args_and_opts(hargs_and_opts)
|
7
|
+
@opts = @opts.reverse_merge({
|
8
|
+
binary: "rsync",
|
9
|
+
delete: true,
|
10
|
+
delay: true,
|
11
|
+
preview: false,
|
12
|
+
flags: [],
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepare
|
17
|
+
@source = @args[0]
|
18
|
+
@destination = @args[1]
|
19
|
+
status :synchronize, :green, "#{@source} => #{@destination}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def simulate
|
23
|
+
render_sync(do_sync(true))
|
24
|
+
end
|
25
|
+
|
26
|
+
def destructive
|
27
|
+
if @opts[:preview] && !(app.opts[:default_yes] || app.opts[:default_accept])
|
28
|
+
preview = do_sync(true)
|
29
|
+
render_sync(preview)
|
30
|
+
if preview.any?
|
31
|
+
if manifest.yes?("Apply changes?", default: @opts[:preview] == true || [:y, :yes].include?(@opts[:preview].to_sym))
|
32
|
+
render_sync do_sync
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
render_sync do_sync
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# ----------------------------------------------
|
41
|
+
|
42
|
+
def str_flags force_dry = false
|
43
|
+
([].tap{|f|
|
44
|
+
f << "--archive"
|
45
|
+
f << "--whole-file" # better I guess?
|
46
|
+
f << "--dry-run" if force_dry || app.opts[:simulation]
|
47
|
+
f << "--itemize-changes" # for parsing and display
|
48
|
+
f << "--delay-updates" if @opts[:delay]
|
49
|
+
if @opts[:delete]
|
50
|
+
f << (@opts[:delay] ? "--delete-#{@opts[:delay].is_a?(String) ? @opts[:delay] : "delay"}" : "--delete")
|
51
|
+
end
|
52
|
+
}.compact + @opts[:flags]).join(" ")
|
53
|
+
end
|
54
|
+
|
55
|
+
def rsync_command src, dst, force_dry = false
|
56
|
+
[
|
57
|
+
@opts[:binary],
|
58
|
+
str_flags(force_dry),
|
59
|
+
Shellwords.escape(expand_src(src)),
|
60
|
+
Shellwords.escape(expand_dst(dst))
|
61
|
+
].join(" ")
|
62
|
+
end
|
63
|
+
|
64
|
+
def status_color_from_mode mode
|
65
|
+
if mode[1] == "f"
|
66
|
+
:white
|
67
|
+
elsif mode == "*deleting"
|
68
|
+
:red
|
69
|
+
elsif mode[1] == "d"
|
70
|
+
:blue
|
71
|
+
else
|
72
|
+
:white
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def status_color_for_mode mode
|
77
|
+
if mode[0] == ">" || mode[0..1] == "cd"
|
78
|
+
:green
|
79
|
+
elsif mode == "*deleting"
|
80
|
+
:red
|
81
|
+
else
|
82
|
+
:yellow
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_sync ary
|
87
|
+
ary.each do |mode, file|
|
88
|
+
status mode, status_color_for_mode(mode), file, status_color_from_mode(mode)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def do_sync force_dry = false
|
93
|
+
cmd = rsync_command(@source, @destination, force_dry)
|
94
|
+
code, res = exec(cmd)
|
95
|
+
raise "rsync exited with status #{code}: #{res}" unless code.exitstatus == 0
|
96
|
+
res.split("\n").map{|l| l.split(" ", 2) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def exec cmd, chomp = true
|
100
|
+
app.debug "§invoking:#{cmd}"
|
101
|
+
_stdin, _stdouterr, _thread = Open3.popen2e(cmd)
|
102
|
+
_thread.join
|
103
|
+
res = _stdouterr.read
|
104
|
+
[_thread.value, chomp ? res.chomp : res]
|
105
|
+
ensure
|
106
|
+
_stdin.close rescue false
|
107
|
+
_stdouterr.close rescue false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|