configmonkey_cli 1.0.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 +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
|