pups 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 055f645c7b240abd8d9d8206029043d1097611c8
4
+ data.tar.gz: 9a0c6803b35b3813058ed89c33f937a959dc9fcb
5
+ SHA512:
6
+ metadata.gz: 2b64563d45db0ec09bb16465e09d81ca4ef39ac92dee8c1b6dd894a24ea4eda5ecc08e6890391e57e3653365f29a39fdcf17399ee383b7bcb9e7e5978ec44b52
7
+ data.tar.gz: 0069a7d13b2070240fc23b17befc9dc2a34d1a51c6e7573b575acf892d22251fbf1bd4c44a94820cc86c3d1dbdb5999e0b048fec6dc528a2ce44f0a81d62f9f7
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pups.gemspec
4
+ gemspec
@@ -0,0 +1,6 @@
1
+ guard :minitest do
2
+ # with Minitest::Unit
3
+ watch(%r{^test/(.*)\/?(.*)_test\.rb$})
4
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[2]}_test.rb" }
5
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
6
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sam Saffron
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,126 @@
1
+ # pups
2
+
3
+ Simple yaml based bootstrapper
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'pups'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install pups
18
+
19
+ ## Usage
20
+
21
+ pups is a small library that allows you to automate the process of creating Unix images.
22
+
23
+ Example:
24
+
25
+ ```
26
+ # somefile.yaml
27
+ params:
28
+ hello: hello world
29
+
30
+ run:
31
+ exec: /bin/bash -c 'echo $hello >>> hello'
32
+ ```
33
+
34
+ Running: `pups somefile.yaml` will execute the shell script resulting in a file called "hello" with the "hello world" contents
35
+
36
+ ### Features:
37
+
38
+ ####Execution
39
+
40
+ Run multiple commands in one path:
41
+
42
+ ```
43
+ run:
44
+ exec:
45
+ cd: some/path
46
+ cmd:
47
+ - echo 1
48
+ - echo 2
49
+ ```
50
+
51
+ Run commands in the background (for services etc)
52
+
53
+ ```
54
+ run:
55
+ exec:
56
+ cmd: /usr/bin/sshd
57
+ background: true
58
+ ```
59
+
60
+ Suppress exceptions on certain commands
61
+
62
+ ```
63
+ run:
64
+ exec:
65
+ cmd: /test
66
+ raise_on_faile: false
67
+ ```
68
+
69
+ ####Replacements:
70
+
71
+ ```
72
+ run:
73
+ replace:
74
+ filename: "/etc/redis/redis.conf"
75
+ from: /^pidfile.*$/
76
+ to: ""
77
+ ```
78
+
79
+ Will substitued the regex with blank, removing the pidfile line
80
+
81
+ ```
82
+ run:
83
+ - replace:
84
+ filename: "/etc/nginx/conf.d/discourse.conf"
85
+ from: /upstream[^\}]+\}/m
86
+ to: "upstream discourse {
87
+ server 127.0.0.1:3000;
88
+ }"
89
+ ```
90
+
91
+ Multiline replace using regex
92
+
93
+ ####Merge yaml files:
94
+
95
+ ```
96
+ home: /var/www/my_app
97
+ params:
98
+ database_yml:
99
+ production:
100
+ username: discourse
101
+ password: foo
102
+
103
+ run:
104
+ - merge: $home/config/database.yml $database_yml
105
+
106
+ ```
107
+
108
+ Will merge the yaml file with the inline contents
109
+
110
+ ####A common environment
111
+
112
+ ```
113
+ env:
114
+ MY_ENV: 1
115
+ ```
116
+
117
+ All executions will get this environment set up
118
+
119
+
120
+ ## Contributing
121
+
122
+ 1. Fork it
123
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
124
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
125
+ 4. Push to the branch (`git push origin my-new-feature`)
126
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = "test/*_test.rb"
6
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
4
+
5
+ require "pups"
6
+ require "pups/cli"
7
+
8
+ Pups::Cli.run(ARGV)
9
+
@@ -0,0 +1,23 @@
1
+ require "logger"
2
+ require "yaml"
3
+
4
+ require "pups/version"
5
+ require "pups/config"
6
+ require "pups/command"
7
+ require "pups/exec_command"
8
+ require "pups/merge_command"
9
+ require "pups/replace_command"
10
+ require "pups/file_command"
11
+
12
+ require "pups/runit"
13
+
14
+ module Pups
15
+ def self.log
16
+ # at the moment docker likes this
17
+ @logger ||= Logger.new(STDERR)
18
+ end
19
+
20
+ def self.log=(logger)
21
+ @logger = logger
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ class Pups::Cli
2
+
3
+ def self.usage
4
+ puts "Usage: pups FILE or pups --stdin"
5
+ exit 1
6
+ end
7
+ def self.run(args)
8
+ if args.length != 1
9
+ usage
10
+ end
11
+
12
+ Pups.log.info("Loading #{args[0]}")
13
+ if args[0] == "--stdin"
14
+ conf = STDIN.readlines.join
15
+ split = conf.split("_FILE_SEPERATOR_")
16
+
17
+ conf = nil
18
+ split.each do |data|
19
+ current = YAML.load(data.strip)
20
+ if conf
21
+ conf = Pups::MergeCommand.deep_merge(conf, current, :merge_arrays)
22
+ else
23
+ conf = current
24
+ end
25
+ end
26
+
27
+ config = Pups::Config.new(conf)
28
+ else
29
+ config = Pups::Config.load_file(args[0])
30
+ end
31
+ config.run
32
+
33
+ ensure
34
+ Pups::ExecCommand.terminate_async
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ class Pups::Command
2
+
3
+ def self.run(command,params)
4
+ case command
5
+ when String then self.from_str(command,params).run
6
+ when Hash then self.from_hash(command,params).run
7
+ end
8
+ end
9
+
10
+ def self.interpolate_params(cmd,params)
11
+ Pups::Config.interpolate_params(cmd,params)
12
+ end
13
+
14
+ def interpolate_params(cmd)
15
+ Pups::Command.interpolate_params(cmd,@params)
16
+ end
17
+ end
@@ -0,0 +1,115 @@
1
+ class Pups::Config
2
+
3
+ attr_reader :config, :params
4
+
5
+ def self.load_file(config_file)
6
+ new YAML.load_file(config_file)
7
+ end
8
+
9
+ def self.load_config(config)
10
+ new YAML.load(config)
11
+ end
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ validate!(@config)
16
+ @params = @config["params"]
17
+ @params ||= {}
18
+ ENV.each do |k,v|
19
+ @params["$ENV_#{k}"] = v
20
+ end
21
+ inject_hooks
22
+ end
23
+
24
+ def validate!(conf)
25
+ # raise proper errors if nodes are missing etc
26
+ end
27
+
28
+ def inject_hooks
29
+ return unless hooks = @config["hooks"]
30
+
31
+ run = @config["run"]
32
+
33
+ positions = {}
34
+ run.each do |row|
35
+ if Hash === row
36
+ command = row.first
37
+ if Hash === command[1]
38
+ hook = command[1]["hook"]
39
+ positions[hook] = row if hook
40
+ end
41
+ end
42
+ end
43
+
44
+ hooks.each do |full, list|
45
+
46
+ offset = nil
47
+ name = nil
48
+
49
+ if full =~ /^after_/
50
+ name = full[6..-1]
51
+ offset = 1
52
+ end
53
+
54
+ if full =~ /^before_/
55
+ name = full[7..-1]
56
+ offset = 0
57
+ end
58
+
59
+ index = run.index(positions[name])
60
+
61
+ if index && index >= 0
62
+ run.insert(index + offset, *list)
63
+ else
64
+ Pups.log.info "Skipped missing #{full} hook"
65
+ end
66
+
67
+ end
68
+ end
69
+
70
+ def run
71
+ run_commands
72
+ rescue => e
73
+ puts
74
+ puts
75
+ puts "FAILED"
76
+ puts "-" * 20
77
+ puts "#{e.class}: #{e}"
78
+ puts "Location of failure: #{e.backtrace[0]}"
79
+ if @last_command
80
+ puts "#{@last_command[:command]} failed with the params #{@last_command[:params].inspect}"
81
+ end
82
+ exit 1
83
+ end
84
+
85
+ def run_commands
86
+ @config["run"].each do |item|
87
+ item.each do |k,v|
88
+ type = case k
89
+ when "exec" then Pups::ExecCommand
90
+ when "merge" then Pups::MergeCommand
91
+ when "replace" then Pups::ReplaceCommand
92
+ when "file" then Pups::FileCommand
93
+ else raise SyntaxError.new("Invalid run command #{k}")
94
+ end
95
+
96
+ @last_command = { command: k, params: v }
97
+ type.run(v, @params)
98
+ end
99
+ end
100
+ end
101
+
102
+ def interpolate_params(cmd)
103
+ self.class.interpolate_params(cmd,@params)
104
+ end
105
+
106
+ def self.interpolate_params(cmd, params)
107
+ return unless cmd
108
+ processed = cmd.dup
109
+ params.each do |k,v|
110
+ processed.gsub!("$#{k}", v.to_s)
111
+ end
112
+ processed
113
+ end
114
+
115
+ end
@@ -0,0 +1,116 @@
1
+ require 'timeout'
2
+
3
+ class Pups::ExecCommand < Pups::Command
4
+ attr_reader :commands, :cd
5
+ attr_accessor :background, :raise_on_fail, :stdin, :stop_signal
6
+
7
+ def self.terminate_async(opts={})
8
+
9
+ return unless defined? @@asyncs
10
+
11
+ Pups.log.info("Terminating async processes")
12
+
13
+ @@asyncs.each do |async|
14
+ Pups.log.info("Sending #{async[:stop_signal]} to #{async[:command]} pid: #{async[:pid]}")
15
+ Process.kill(async[:stop_signal],async[:pid]) rescue nil
16
+ end
17
+
18
+ @@asyncs.map do |async|
19
+ Thread.new do
20
+ begin
21
+ Timeout.timeout(opts[:wait] || 10) do
22
+ Process.wait(async[:pid]) rescue nil
23
+ end
24
+ rescue Timeout::Error
25
+ Pups.log.info("#{async[:command]} pid:#{async[:pid]} did not terminate cleanly, forcing termination!")
26
+ begin
27
+ Process.kill("KILL",async[:pid])
28
+ Process.wait(async[:pid])
29
+ rescue Errno::ESRCH
30
+ rescue Errno::ECHILD
31
+ end
32
+
33
+ end
34
+ end
35
+ end.each(&:join)
36
+
37
+ end
38
+
39
+ def self.from_hash(hash, params)
40
+ cmd = new(params, hash["cd"])
41
+
42
+ case c = hash["cmd"]
43
+ when String then cmd.add(c)
44
+ when Array then c.each{|i| cmd.add(i)}
45
+ end
46
+
47
+ cmd.background = hash["background"]
48
+ cmd.stop_signal = hash["stop_signal"] || "TERM"
49
+ cmd.raise_on_fail = hash["raise_on_fail"] if hash.key? "raise_on_fail"
50
+ cmd.stdin = interpolate_params(hash["stdin"], params)
51
+
52
+ cmd
53
+ end
54
+
55
+ def self.from_str(str, params)
56
+ cmd = new(params)
57
+ cmd.add(str)
58
+ cmd
59
+ end
60
+
61
+ def initialize(params, cd = nil)
62
+ @commands = []
63
+ @params = params
64
+ @cd = interpolate_params(cd)
65
+ @raise_on_fail = true
66
+ end
67
+
68
+ def add(cmd)
69
+ @commands << process_params(cmd)
70
+ end
71
+
72
+ def run
73
+ commands.each do |command|
74
+ Pups.log.info("> #{command}")
75
+ pid = spawn(command)
76
+ Pups.log.info(@result.readlines.join("\n")) if @result
77
+ pid
78
+ end
79
+ rescue
80
+ raise if @raise_on_fail
81
+ end
82
+
83
+ def spawn(command)
84
+ if background
85
+ pid = Process.spawn(command)
86
+ (@@asyncs ||= []) << {pid: pid, command: command, stop_signal: (stop_signal || "TERM")}
87
+ Thread.new do
88
+ Process.wait(pid)
89
+ @@asyncs.delete_if{|async| async[:pid] == pid}
90
+ end
91
+ return pid
92
+ end
93
+
94
+ IO.popen(command, "w+") do |f|
95
+ if stdin
96
+ # need a way to get stdout without blocking
97
+ Pups.log.info(stdin)
98
+ f.write stdin
99
+ f.close
100
+ else
101
+ Pups.log.info(f.readlines.join)
102
+ end
103
+ end
104
+
105
+ raise RuntimeError.new("#{command} failed with return #{$?.inspect}") unless $? == 0
106
+
107
+ nil
108
+
109
+ end
110
+
111
+ def process_params(cmd)
112
+ processed = interpolate_params(cmd)
113
+ @cd ? "cd #{cd} && #{processed}" : processed
114
+ end
115
+
116
+ end
@@ -0,0 +1,37 @@
1
+ class Pups::FileCommand < Pups::Command
2
+ attr_accessor :path, :contents, :params, :type, :chmod
3
+
4
+ def self.from_hash(hash, params)
5
+ command = new
6
+ command.path = hash["path"]
7
+ command.contents = hash["contents"]
8
+ command.chmod = hash["chmod"]
9
+ command.params = params
10
+
11
+ command
12
+ end
13
+
14
+ def initialize
15
+ @params = {}
16
+ @type = :bash
17
+ end
18
+
19
+ def params=(p)
20
+ @params = p
21
+ end
22
+
23
+ def run
24
+ path = interpolate_params(@path)
25
+
26
+ `mkdir -p #{File.dirname(path)}`
27
+ File.open(path, "w") do |f|
28
+ f.write(interpolate_params(contents))
29
+ end
30
+ if @chmod
31
+ `chmod #{@chmod} #{path}`
32
+ end
33
+ Pups.log.info("File > #{path} chmod: #{@chmod}")
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,48 @@
1
+ class Pups::MergeCommand < Pups::Command
2
+ attr_reader :filename
3
+ attr_reader :merge_hash
4
+
5
+ def self.from_str(command, params)
6
+ new(command,params)
7
+ end
8
+
9
+ def self.parse_command(command)
10
+ split = command.split(" ")
11
+ raise ArgumentError.new("Invalid merge command #{command}") unless split[-1][0] == "$"
12
+
13
+ [split[0..-2].join(" ") , split[-1][1..-1]]
14
+ end
15
+
16
+ def initialize(command, params)
17
+ @params = params
18
+
19
+ filename, target_param = Pups::MergeCommand.parse_command(command)
20
+ @filename = interpolate_params(filename)
21
+ @merge_hash = params[target_param]
22
+ end
23
+
24
+ def run
25
+ merged = self.class.deep_merge(YAML.load_file(@filename), @merge_hash)
26
+ File.open(@filename,"w"){|f| f.write(merged.to_yaml) }
27
+ Pups.log.info("Merge: #{@filename} with: \n#{@merge_hash.inspect}")
28
+ end
29
+
30
+ def self.deep_merge(first,second, *args)
31
+ args ||= []
32
+ merge_arrays = args.include? :merge_arrays
33
+
34
+ merger = proc { |key, v1, v2|
35
+ if Hash === v1 && Hash === v2
36
+ v1.merge(v2, &merger)
37
+ elsif Array === v1 && Array === v2
38
+ merge_arrays ? v1 + v2 : v2
39
+ elsif NilClass === v2
40
+ v1
41
+ else
42
+ v2
43
+ end
44
+ }
45
+ first.merge(second, &merger)
46
+ end
47
+
48
+ end
@@ -0,0 +1,43 @@
1
+ class Pups::ReplaceCommand < Pups::Command
2
+ attr_accessor :text, :from, :to, :filename, :direction, :global
3
+
4
+ def self.from_hash(hash, params)
5
+ replacer = new(params)
6
+ replacer.from = guess_replace_type(hash["from"])
7
+ replacer.to = guess_replace_type(hash["to"])
8
+ replacer.text = File.read(hash["filename"])
9
+ replacer.filename = hash["filename"]
10
+ replacer.direction = hash["direction"].to_sym if hash["direction"]
11
+ replacer.global = hash["global"].to_s == "true"
12
+ replacer
13
+ end
14
+
15
+ def self.guess_replace_type(item)
16
+ # evaling to get all the regex flags easily
17
+ item[0] == "/" ? eval(item) : item
18
+ end
19
+
20
+ def initialize(params)
21
+ @params = params
22
+ end
23
+
24
+ def replaced_text
25
+ new_to = to
26
+ if String === to
27
+ new_to = interpolate_params(to)
28
+ end
29
+ if global
30
+ text.gsub(from,new_to)
31
+ elsif direction == :reverse
32
+ index = text.rindex(from)
33
+ text[0..index-1] << text[index..-1].sub(from,new_to)
34
+ else
35
+ text.sub(from,new_to)
36
+ end
37
+ end
38
+
39
+ def run
40
+ Pups.log.info("Replacing #{from.to_s} with #{to.to_s} in #{filename}")
41
+ File.open(filename, "w"){|f| f.write replaced_text }
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ class Pups::Runit
2
+
3
+ attr_accessor :env, :exec, :cd, :name
4
+
5
+
6
+ def initialize(name)
7
+ @name = name
8
+ end
9
+
10
+ def setup
11
+ `mkdir -p /etc/service/#{name}`
12
+ run = "/etc/service/#{name}/run"
13
+ File.open(run, "w") do |f|
14
+ f.write(run_script)
15
+ end
16
+ `chmod +x #{run}`
17
+ end
18
+
19
+ def run_script
20
+ "#!/bin/bash
21
+ exec 2>&1
22
+ #{env_script}
23
+ #{cd_script}
24
+ #{exec}
25
+ "
26
+ end
27
+
28
+ def cd_script
29
+ "cd #{@cd}" if @cd
30
+ end
31
+
32
+ def env_script
33
+ if @env
34
+ @env.map do |k,v|
35
+ "export #{k}=#{v}"
36
+ end.join("\n")
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,3 @@
1
+ module Pups
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pups/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pups"
8
+ spec.version = Pups::VERSION
9
+ spec.authors = ["Sam Saffron"]
10
+ spec.email = ["sam.saffron@gmail.com"]
11
+ spec.description = %q{Process orchestrator}
12
+ spec.summary = %q{Process orchestrator}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "guard"
25
+ spec.add_development_dependency "guard-minitest"
26
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+ require 'tempfile'
3
+
4
+ module Pups
5
+ class ConfigTest < MiniTest::Test
6
+
7
+ def test_config_from_env
8
+ ENV["HELLO"] = "world"
9
+ config = Config.new({})
10
+ assert_equal("world", config.params["$ENV_HELLO"])
11
+ end
12
+
13
+ def test_integration
14
+
15
+ f = Tempfile.new("test")
16
+ f.close
17
+
18
+ config = <<YAML
19
+ params:
20
+ run: #{f.path}
21
+ run:
22
+ - exec: echo hello world >> #{f.path}
23
+ YAML
24
+
25
+ Config.new(YAML.load(config)).run
26
+ assert_equal("hello world", File.read(f.path).strip)
27
+
28
+ ensure
29
+ f.unlink
30
+ end
31
+
32
+ def test_hooks
33
+ yaml = <<YAML
34
+ run:
35
+ - exec: 1
36
+ - exec:
37
+ hook: middle
38
+ cmd: 2
39
+ - exec: 3
40
+ hooks:
41
+ after_middle:
42
+ - exec: 2.1
43
+ before_middle:
44
+ - exec: 1.9
45
+ YAML
46
+
47
+ config = Config.load_config(yaml).config
48
+ assert_equal({ "exec" => 1.9 }, config["run"][1])
49
+ assert_equal({ "exec" => 2.1 }, config["run"][3])
50
+
51
+
52
+ end
53
+ end
54
+ end
55
+
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+ require 'tempfile'
3
+
4
+ module Pups
5
+ class ExecCommandTest < MiniTest::Test
6
+
7
+ def from_str(str, params={})
8
+ ExecCommand.from_str(str, params).commands
9
+ end
10
+
11
+ def from_hash(hash, params={})
12
+ ExecCommand.from_hash(hash, params).commands
13
+ end
14
+
15
+ def test_simple_str_command
16
+ assert_equal(["do_something"],
17
+ from_str("do_something"))
18
+ end
19
+
20
+ def test_simple_str_command_with_param
21
+ assert_equal(["hello world"],
22
+ from_str("hello $bob", {"bob" => "world"}))
23
+ end
24
+
25
+ def test_nested_command
26
+ assert_equal(["first"],
27
+ from_hash("cmd" => "first"))
28
+ end
29
+
30
+ def test_multi_commands
31
+ assert_equal(["first","second"],
32
+ from_hash("cmd" => ["first","second"]))
33
+ end
34
+
35
+ def test_multi_commands_with_home
36
+ assert_equal(["cd /home/sam && first",
37
+ "cd /home/sam && second"],
38
+ from_hash("cmd" => ["first","second"],
39
+ "cd" => "/home/sam"))
40
+ end
41
+
42
+ def test_exec_works
43
+ ExecCommand.from_str("ls",{}).run
44
+ end
45
+
46
+ def test_fails_for_bad_command
47
+ assert_raises(Errno::ENOENT) do
48
+ ExecCommand.from_str("boom",{}).run
49
+ end
50
+ end
51
+
52
+ def test_backgroud_task_do_not_fail
53
+ cmd = ExecCommand.new({})
54
+ cmd.background = true
55
+ cmd.add("sleep 10 && exit 1")
56
+ cmd.run
57
+ end
58
+
59
+ def test_raise_on_fail
60
+ cmd = ExecCommand.new({})
61
+ cmd.add("chgrp -a")
62
+ cmd.raise_on_fail = false
63
+ cmd.run
64
+ end
65
+
66
+ def test_stdin
67
+
68
+ `touch test_file`
69
+ cmd = ExecCommand.new({})
70
+ cmd.add("read test ; echo $test > test_file")
71
+ cmd.stdin = "hello"
72
+ cmd.run
73
+
74
+ assert_equal("hello\n", File.read("test_file"))
75
+
76
+ ensure
77
+ File.delete("test_file")
78
+ end
79
+
80
+ def test_fails_for_non_zero_exit
81
+ assert_raises(RuntimeError) do
82
+ ExecCommand.from_str("chgrp -a",{}).run
83
+ end
84
+ end
85
+
86
+
87
+ def test_can_terminate_async
88
+ cmd = ExecCommand.new({})
89
+ cmd.background = true
90
+ pid = cmd.spawn("sleep 10 && exit 1")
91
+ ExecCommand.terminate_async
92
+ assert_raises(Errno::ECHILD) do
93
+ Process.waitpid(pid,Process::WNOHANG)
94
+ end
95
+ end
96
+
97
+ def test_can_terminate_rogues
98
+ cmd = ExecCommand.new({})
99
+ cmd.background = true
100
+ pid = cmd.spawn("trap \"echo TERM && sleep 100\" TERM ; sleep 100")
101
+ # we need to give bash enough time to trap
102
+ sleep 0.01
103
+
104
+ ExecCommand.terminate_async(wait: 0.1)
105
+
106
+ assert_raises(Errno::ECHILD) do
107
+ Process.waitpid(pid,Process::WNOHANG)
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+ require 'tempfile'
3
+
4
+ module Pups
5
+ class FileCommandTest < MiniTest::Test
6
+
7
+ def test_simple_file_creation
8
+ tmp = Tempfile.new("test")
9
+ tmp.write("x")
10
+ tmp.close
11
+
12
+
13
+ cmd = FileCommand.new
14
+ cmd.path = tmp.path
15
+ cmd.contents = "hello $world"
16
+ cmd.params = {"world" => "world"}
17
+ cmd.run
18
+
19
+ assert_equal("hello world",
20
+ File.read(tmp.path))
21
+ ensure
22
+ tmp.close
23
+ tmp.unlink
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,58 @@
1
+ require 'test_helper'
2
+ require 'tempfile'
3
+
4
+ module Pups
5
+ class MergeCommandTest < MiniTest::Test
6
+ def test_deep_merge_arrays
7
+ a = {a: {a: ["hi",1]}}
8
+ b = {a: {a: ["hi",2]}}
9
+ c = {a: {}}
10
+
11
+ d = Pups::MergeCommand.deep_merge(a,b,:merge_arrays)
12
+ d = Pups::MergeCommand.deep_merge(d,c,:merge_arrays)
13
+
14
+ assert_equal(["hi", 1,"hi", 2], d[:a][:a])
15
+ end
16
+
17
+ def test_merges
18
+
19
+ source = <<YAML
20
+ user:
21
+ name: "bob"
22
+ password: "xyz"
23
+ YAML
24
+
25
+ f = Tempfile.new("test")
26
+ f.write source
27
+ f.close
28
+
29
+ merge = <<YAML
30
+ user:
31
+ name: "bob2"
32
+ YAML
33
+
34
+ MergeCommand.from_str("#{f.path} $yaml", {"yaml" => YAML.load(merge) }).run
35
+
36
+ changed = YAML.load_file(f.path)
37
+
38
+ assert_equal({"user" => {
39
+ "name" => "bob2",
40
+ "password" => "xyz"
41
+ }}, changed)
42
+
43
+ def test_deep_merge_nil
44
+ a = {param: {venison: "yes please"}}
45
+ b = {param: nil}
46
+
47
+ r1 = Pups::MergeCommand.deep_merge(a,b)
48
+ r2 = Pups::MergeCommand.deep_merge(b,a)
49
+
50
+ assert_equal({venison: "yes please"}, r1[:param])
51
+ assert_equal({venison: "yes please"}, r2[:param])
52
+ end
53
+
54
+ ensure
55
+ f.unlink
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,112 @@
1
+ require 'test_helper'
2
+ require 'tempfile'
3
+
4
+ module Pups
5
+ class ReplaceCommandTest < MiniTest::Test
6
+
7
+ def test_simple
8
+ command = ReplaceCommand.new({})
9
+ command.text = "hello world"
10
+ command.from = /he[^o]+o/
11
+ command.to = "world"
12
+
13
+ assert_equal("world world", command.replaced_text)
14
+ end
15
+
16
+ def test_reverse
17
+ source = <<SCR
18
+ 1 one thousand 1
19
+ 1 one thousand 1
20
+ 1 one thousand 1
21
+ SCR
22
+
23
+ f = Tempfile.new("test")
24
+ f.write source
25
+ f.close
26
+
27
+ hash = {
28
+ "filename" => f.path,
29
+ "from" => "/one t.*d/",
30
+ "to" => "hello world",
31
+ "direction" => "reverse"
32
+ }
33
+
34
+ command = ReplaceCommand.from_hash(hash, {})
35
+
36
+ assert_equal("1 one thousand 1\n1 one thousand 1\n1 hello world 1\n", command.replaced_text)
37
+ ensure
38
+ f.unlink
39
+ end
40
+
41
+ def test_global
42
+ source = <<SCR
43
+ one
44
+ one
45
+ one
46
+ SCR
47
+
48
+ f = Tempfile.new("test")
49
+ f.write source
50
+ f.close
51
+
52
+ hash = {
53
+ "filename" => f.path,
54
+ "from" => "/one/",
55
+ "to" => "two",
56
+ "global" => "true"
57
+ }
58
+
59
+ command = ReplaceCommand.from_hash(hash, {})
60
+
61
+ assert_equal("two\ntwo\ntwo\n", command.replaced_text)
62
+ ensure
63
+ f.unlink
64
+
65
+ end
66
+
67
+ def test_replace_with_env
68
+ source = "123"
69
+
70
+ f = Tempfile.new("test")
71
+ f.write source
72
+ f.close
73
+
74
+ hash = {
75
+ "filename" => f.path,
76
+ "from" => "123",
77
+ "to" => "hello $hellos"
78
+ }
79
+
80
+ command = ReplaceCommand.from_hash(hash, {"hello" => "world"})
81
+ assert_equal("hello worlds", command.replaced_text)
82
+
83
+ ensure
84
+ f.unlink
85
+ end
86
+
87
+ def test_parse
88
+
89
+ source = <<SCR
90
+ this {
91
+ is a test
92
+ }
93
+ SCR
94
+
95
+ f = Tempfile.new("test")
96
+ f.write source
97
+ f.close
98
+
99
+ hash = {
100
+ "filename" => f.path,
101
+ "from" => "/this[^\}]+\}/m",
102
+ "to" => "hello world"
103
+ }
104
+
105
+ command = ReplaceCommand.from_hash(hash, {})
106
+
107
+ assert_equal("hello world", command.replaced_text.strip)
108
+ ensure
109
+ f.unlink
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,4 @@
1
+ require 'pups'
2
+ require 'minitest/pride'
3
+
4
+
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pups
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Sam Saffron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
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: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
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: guard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Process orchestrator
84
+ email:
85
+ - sam.saffron@gmail.com
86
+ executables:
87
+ - pups
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - Guardfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/pups
98
+ - lib/pups.rb
99
+ - lib/pups/cli.rb
100
+ - lib/pups/command.rb
101
+ - lib/pups/config.rb
102
+ - lib/pups/exec_command.rb
103
+ - lib/pups/file_command.rb
104
+ - lib/pups/merge_command.rb
105
+ - lib/pups/replace_command.rb
106
+ - lib/pups/runit.rb
107
+ - lib/pups/version.rb
108
+ - pups.gemspec
109
+ - test/config_test.rb
110
+ - test/exec_command_test.rb
111
+ - test/file_command_test.rb
112
+ - test/merge_command_test.rb
113
+ - test/replace_command_test.rb
114
+ - test/test_helper.rb
115
+ homepage: ''
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.2.2
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Process orchestrator
139
+ test_files:
140
+ - test/config_test.rb
141
+ - test/exec_command_test.rb
142
+ - test/file_command_test.rb
143
+ - test/merge_command_test.rb
144
+ - test/replace_command_test.rb
145
+ - test/test_helper.rb