configmonkey_cli 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +87 -0
  6. data/Rakefile +1 -0
  7. data/VERSION +1 -0
  8. data/bin/configmonkey +9 -0
  9. data/bin/configmonkey.sh +14 -0
  10. data/configmonkey_cli.gemspec +27 -0
  11. data/lib/configmonkey_cli.rb +43 -0
  12. data/lib/configmonkey_cli/application.rb +159 -0
  13. data/lib/configmonkey_cli/application/colorize.rb +22 -0
  14. data/lib/configmonkey_cli/application/configuration.rb +38 -0
  15. data/lib/configmonkey_cli/application/configuration.tpl +0 -0
  16. data/lib/configmonkey_cli/application/core.rb +78 -0
  17. data/lib/configmonkey_cli/application/dispatch.rb +81 -0
  18. data/lib/configmonkey_cli/application/manifest.rb +316 -0
  19. data/lib/configmonkey_cli/application/manifest_actions/base.rb +90 -0
  20. data/lib/configmonkey_cli/application/manifest_actions/chmod.rb +40 -0
  21. data/lib/configmonkey_cli/application/manifest_actions/copy.rb +38 -0
  22. data/lib/configmonkey_cli/application/manifest_actions/custom.rb +52 -0
  23. data/lib/configmonkey_cli/application/manifest_actions/inplace.rb +28 -0
  24. data/lib/configmonkey_cli/application/manifest_actions/invoke.rb +45 -0
  25. data/lib/configmonkey_cli/application/manifest_actions/link.rb +46 -0
  26. data/lib/configmonkey_cli/application/manifest_actions/mkdir.rb +41 -0
  27. data/lib/configmonkey_cli/application/manifest_actions/remove.rb +46 -0
  28. data/lib/configmonkey_cli/application/manifest_actions/rsync.rb +112 -0
  29. data/lib/configmonkey_cli/application/manifest_actions/rtfm.rb +32 -0
  30. data/lib/configmonkey_cli/application/manifest_actions/sync_links.rb +60 -0
  31. data/lib/configmonkey_cli/application/manifest_actions/template.rb +38 -0
  32. data/lib/configmonkey_cli/application/output_helper.rb +30 -0
  33. data/lib/configmonkey_cli/helper.rb +62 -0
  34. data/lib/configmonkey_cli/version.rb +4 -0
  35. 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