pups 1.0.3 → 1.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 +4 -4
- data/.github/workflows/ci.yml +29 -0
- data/.github/workflows/lint.yml +27 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +2 -0
- data/Guardfile +3 -1
- data/README.md +40 -3
- data/Rakefile +8 -6
- data/bin/pups +4 -4
- data/lib/pups.rb +21 -11
- data/lib/pups/cli.rb +61 -26
- data/lib/pups/command.rb +14 -11
- data/lib/pups/config.rb +136 -93
- data/lib/pups/docker.rb +73 -0
- data/lib/pups/exec_command.rb +93 -89
- data/lib/pups/file_command.rb +28 -32
- data/lib/pups/merge_command.rb +48 -46
- data/lib/pups/replace_command.rb +36 -34
- data/lib/pups/runit.rb +23 -24
- data/lib/pups/version.rb +3 -1
- data/pups.gemspec +21 -16
- data/test/cli_test.rb +117 -0
- data/test/config_test.rb +192 -30
- data/test/docker_test.rb +157 -0
- data/test/exec_command_test.rb +29 -33
- data/test/file_command_test.rb +8 -9
- data/test/merge_command_test.rb +32 -32
- data/test/replace_command_test.rb +42 -44
- data/test/test_helper.rb +3 -0
- metadata +70 -6
data/test/docker_test.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'test_helper'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'shellwords'
|
5
|
+
|
6
|
+
module Pups
|
7
|
+
class DockerTest < MiniTest::Test
|
8
|
+
def test_gen_env_arguments
|
9
|
+
yaml = <<~YAML
|
10
|
+
env:
|
11
|
+
foo: 1
|
12
|
+
bar: 2
|
13
|
+
baz: 'hello_{{spam}}'
|
14
|
+
env_template:
|
15
|
+
spam: 'eggs'
|
16
|
+
YAML
|
17
|
+
|
18
|
+
config = Config.load_config(yaml)
|
19
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
20
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
21
|
+
assert_equal("--env foo=1 --env bar=2 --env baz=hello_eggs", args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_gen_env_arguments_empty
|
25
|
+
yaml = <<~YAML
|
26
|
+
env:
|
27
|
+
foo: 1
|
28
|
+
bar: 2
|
29
|
+
baz: ''
|
30
|
+
YAML
|
31
|
+
|
32
|
+
config = Config.load_config(yaml)
|
33
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
34
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
35
|
+
assert_equal("--env foo=1 --env bar=2", args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_gen_env_arguments_escaped
|
39
|
+
yaml = <<~YAML
|
40
|
+
env:
|
41
|
+
password: "{{spam}}*`echo`@e$t| = >>$()&list;#"
|
42
|
+
env_template:
|
43
|
+
spam: 'eggs'
|
44
|
+
YAML
|
45
|
+
|
46
|
+
config = Config.load_config(yaml)
|
47
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
48
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
49
|
+
assert_equal("--env password=\"#{Shellwords.escape('eggs*`echo`@e$t| = >>$()&list;#')}\"", args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_gen_env_arguments_quoted_with_a_space
|
53
|
+
yaml = <<~YAML
|
54
|
+
env:
|
55
|
+
a_variable: here is a sentence
|
56
|
+
YAML
|
57
|
+
|
58
|
+
config = Config.load_config(yaml)
|
59
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
60
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
61
|
+
assert_equal('--env a_variable="here\ is\ a\ sentence"', args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_gen_env_arguments_newline
|
65
|
+
pw = <<~PW
|
66
|
+
this password is
|
67
|
+
a weird one
|
68
|
+
PW
|
69
|
+
|
70
|
+
yaml = <<~YAML
|
71
|
+
env:
|
72
|
+
password: "#{pw}"
|
73
|
+
env_template:
|
74
|
+
spam: 'eggs'
|
75
|
+
YAML
|
76
|
+
|
77
|
+
config = Config.load_config(yaml)
|
78
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
79
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
80
|
+
assert_equal('--env password="this\ password\ is\ a\ weird\ one\ "', args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_gen_expose_arguments
|
84
|
+
yaml = <<~YAML
|
85
|
+
expose:
|
86
|
+
- "2222:22"
|
87
|
+
- "127.0.0.1:20080:80"
|
88
|
+
- 5555
|
89
|
+
YAML
|
90
|
+
|
91
|
+
config = Config.load_config(yaml)
|
92
|
+
args = Docker.generate_expose_arguments(config.config["expose"])
|
93
|
+
assert_equal("--publish 2222:22 --publish 127.0.0.1:20080:80 --expose 5555", args)
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_gen_volume_arguments
|
97
|
+
yaml = <<~YAML
|
98
|
+
volumes:
|
99
|
+
- volume:
|
100
|
+
host: /var/discourse/shared
|
101
|
+
guest: /shared
|
102
|
+
- volume:
|
103
|
+
host: /bar
|
104
|
+
guest: /baz
|
105
|
+
YAML
|
106
|
+
|
107
|
+
config = Config.load_config(yaml)
|
108
|
+
args = Docker.generate_volume_arguments(config.config["volumes"])
|
109
|
+
assert_equal("--volume /var/discourse/shared:/shared --volume /bar:/baz", args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_gen_link_arguments
|
113
|
+
yaml = <<~YAML
|
114
|
+
links:
|
115
|
+
- link:
|
116
|
+
name: postgres
|
117
|
+
alias: postgres
|
118
|
+
- link:
|
119
|
+
name: foo
|
120
|
+
alias: bar
|
121
|
+
YAML
|
122
|
+
|
123
|
+
config = Config.load_config(yaml)
|
124
|
+
args = Docker.generate_link_arguments(config.config["links"])
|
125
|
+
assert_equal("--link postgres:postgres --link foo:bar", args)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_gen_label_arguments
|
129
|
+
yaml = <<~YAML
|
130
|
+
env_template:
|
131
|
+
config: my_app
|
132
|
+
labels:
|
133
|
+
monitor: "true"
|
134
|
+
app_name: "{{config}}_discourse"
|
135
|
+
YAML
|
136
|
+
|
137
|
+
config = Config.load_config(yaml)
|
138
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["labels"])
|
139
|
+
args = Docker.generate_label_arguments(config.config["labels"])
|
140
|
+
assert_equal("--label monitor=true --label app_name=my_app_discourse", args)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_gen_label_arguments_escaped
|
144
|
+
yaml = <<~YAML
|
145
|
+
labels:
|
146
|
+
app_name: "{{config}}'s_di$course"
|
147
|
+
env_template:
|
148
|
+
config: my_app
|
149
|
+
YAML
|
150
|
+
|
151
|
+
config = Config.load_config(yaml)
|
152
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["labels"])
|
153
|
+
args = Docker.generate_label_arguments(config.config["labels"])
|
154
|
+
assert_equal("--label app_name=#{Shellwords.escape("my_app's_di$course")}", args)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/test/exec_command_test.rb
CHANGED
@@ -1,113 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'tempfile'
|
3
5
|
|
4
6
|
module Pups
|
5
7
|
class ExecCommandTest < MiniTest::Test
|
6
|
-
|
7
|
-
def from_str(str, params={})
|
8
|
+
def from_str(str, params = {})
|
8
9
|
ExecCommand.from_str(str, params).commands
|
9
10
|
end
|
10
11
|
|
11
|
-
def from_hash(hash, params={})
|
12
|
+
def from_hash(hash, params = {})
|
12
13
|
ExecCommand.from_hash(hash, params).commands
|
13
14
|
end
|
14
15
|
|
15
16
|
def test_simple_str_command
|
16
|
-
assert_equal([
|
17
|
-
|
17
|
+
assert_equal(['do_something'],
|
18
|
+
from_str('do_something'))
|
18
19
|
end
|
19
20
|
|
20
21
|
def test_simple_str_command_with_param
|
21
|
-
assert_equal([
|
22
|
-
|
22
|
+
assert_equal(['hello world'],
|
23
|
+
from_str('hello $bob', { 'bob' => 'world' }))
|
23
24
|
end
|
24
25
|
|
25
26
|
def test_nested_command
|
26
|
-
assert_equal([
|
27
|
-
|
27
|
+
assert_equal(['first'],
|
28
|
+
from_hash('cmd' => 'first'))
|
28
29
|
end
|
29
30
|
|
30
31
|
def test_multi_commands
|
31
|
-
assert_equal([
|
32
|
-
|
32
|
+
assert_equal(%w[first second],
|
33
|
+
from_hash('cmd' => %w[first second]))
|
33
34
|
end
|
34
35
|
|
35
36
|
def test_multi_commands_with_home
|
36
|
-
assert_equal([
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
assert_equal(['cd /home/sam && first',
|
38
|
+
'cd /home/sam && second'],
|
39
|
+
from_hash('cmd' => %w[first second],
|
40
|
+
'cd' => '/home/sam'))
|
40
41
|
end
|
41
42
|
|
42
43
|
def test_exec_works
|
43
|
-
ExecCommand.from_str(
|
44
|
+
ExecCommand.from_str('ls', {}).run
|
44
45
|
end
|
45
46
|
|
46
47
|
def test_fails_for_bad_command
|
47
48
|
assert_raises(Errno::ENOENT) do
|
48
|
-
ExecCommand.from_str(
|
49
|
+
ExecCommand.from_str('boom', {}).run
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
53
|
def test_backgroud_task_do_not_fail
|
53
54
|
cmd = ExecCommand.new({})
|
54
55
|
cmd.background = true
|
55
|
-
cmd.add(
|
56
|
+
cmd.add('sleep 10 && exit 1')
|
56
57
|
cmd.run
|
57
58
|
end
|
58
59
|
|
59
60
|
def test_raise_on_fail
|
60
61
|
cmd = ExecCommand.new({})
|
61
|
-
cmd.add(
|
62
|
+
cmd.add('chgrp -a')
|
62
63
|
cmd.raise_on_fail = false
|
63
64
|
cmd.run
|
64
65
|
end
|
65
66
|
|
66
67
|
def test_stdin
|
67
|
-
|
68
68
|
`touch test_file`
|
69
69
|
cmd = ExecCommand.new({})
|
70
|
-
cmd.add(
|
71
|
-
cmd.stdin =
|
70
|
+
cmd.add('read test ; echo $test > test_file')
|
71
|
+
cmd.stdin = 'hello'
|
72
72
|
cmd.run
|
73
73
|
|
74
|
-
assert_equal("hello\n", File.read(
|
75
|
-
|
74
|
+
assert_equal("hello\n", File.read('test_file'))
|
76
75
|
ensure
|
77
|
-
File.delete(
|
76
|
+
File.delete('test_file')
|
78
77
|
end
|
79
78
|
|
80
79
|
def test_fails_for_non_zero_exit
|
81
80
|
assert_raises(Pups::ExecError) do
|
82
|
-
ExecCommand.from_str(
|
81
|
+
ExecCommand.from_str('chgrp -a', {}).run
|
83
82
|
end
|
84
83
|
end
|
85
84
|
|
86
|
-
|
87
85
|
def test_can_terminate_async
|
88
86
|
cmd = ExecCommand.new({})
|
89
87
|
cmd.background = true
|
90
|
-
pid = cmd.spawn(
|
88
|
+
pid = cmd.spawn('sleep 10 && exit 1')
|
91
89
|
ExecCommand.terminate_async
|
92
90
|
assert_raises(Errno::ECHILD) do
|
93
|
-
Process.waitpid(pid,Process::WNOHANG)
|
91
|
+
Process.waitpid(pid, Process::WNOHANG)
|
94
92
|
end
|
95
93
|
end
|
96
94
|
|
97
95
|
def test_can_terminate_rogues
|
98
96
|
cmd = ExecCommand.new({})
|
99
97
|
cmd.background = true
|
100
|
-
pid = cmd.spawn(
|
98
|
+
pid = cmd.spawn('trap "echo TERM && sleep 100" TERM ; sleep 100')
|
101
99
|
# we need to give bash enough time to trap
|
102
100
|
sleep 0.01
|
103
101
|
|
104
102
|
ExecCommand.terminate_async(wait: 0.1)
|
105
103
|
|
106
104
|
assert_raises(Errno::ECHILD) do
|
107
|
-
Process.waitpid(pid,Process::WNOHANG)
|
105
|
+
Process.waitpid(pid, Process::WNOHANG)
|
108
106
|
end
|
109
|
-
|
110
107
|
end
|
111
|
-
|
112
108
|
end
|
113
109
|
end
|
data/test/file_command_test.rb
CHANGED
@@ -1,27 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'tempfile'
|
3
5
|
|
4
6
|
module Pups
|
5
7
|
class FileCommandTest < MiniTest::Test
|
6
|
-
|
7
8
|
def test_simple_file_creation
|
8
|
-
tmp = Tempfile.new(
|
9
|
-
tmp.write(
|
9
|
+
tmp = Tempfile.new('test')
|
10
|
+
tmp.write('x')
|
10
11
|
tmp.close
|
11
12
|
|
12
|
-
|
13
13
|
cmd = FileCommand.new
|
14
14
|
cmd.path = tmp.path
|
15
|
-
cmd.contents =
|
16
|
-
cmd.params = {
|
15
|
+
cmd.contents = 'hello $world'
|
16
|
+
cmd.params = { 'world' => 'world' }
|
17
17
|
cmd.run
|
18
18
|
|
19
|
-
assert_equal(
|
20
|
-
|
19
|
+
assert_equal('hello world',
|
20
|
+
File.read(tmp.path))
|
21
21
|
ensure
|
22
22
|
tmp.close
|
23
23
|
tmp.unlink
|
24
24
|
end
|
25
|
-
|
26
25
|
end
|
27
26
|
end
|
data/test/merge_command_test.rb
CHANGED
@@ -1,56 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'tempfile'
|
3
5
|
|
4
6
|
module Pups
|
5
7
|
class MergeCommandTest < MiniTest::Test
|
6
8
|
def test_deep_merge_arrays
|
7
|
-
a = {a: {a: [
|
8
|
-
b = {a: {a: [
|
9
|
-
c = {a: {}}
|
9
|
+
a = { a: { a: ['hi', 1] } }
|
10
|
+
b = { a: { a: ['hi', 2] } }
|
11
|
+
c = { a: {} }
|
10
12
|
|
11
|
-
d = Pups::MergeCommand.deep_merge(a,b
|
12
|
-
d = Pups::MergeCommand.deep_merge(d,c
|
13
|
+
d = Pups::MergeCommand.deep_merge(a, b, :merge_arrays)
|
14
|
+
d = Pups::MergeCommand.deep_merge(d, c, :merge_arrays)
|
13
15
|
|
14
|
-
assert_equal([
|
16
|
+
assert_equal(['hi', 1, 'hi', 2], d[:a][:a])
|
15
17
|
end
|
16
18
|
|
17
19
|
def test_merges
|
20
|
+
source = <<~YAML
|
21
|
+
user:
|
22
|
+
name: "bob"
|
23
|
+
password: "xyz"
|
24
|
+
YAML
|
18
25
|
|
19
|
-
|
20
|
-
user:
|
21
|
-
name: "bob"
|
22
|
-
password: "xyz"
|
23
|
-
YAML
|
24
|
-
|
25
|
-
f = Tempfile.new("test")
|
26
|
+
f = Tempfile.new('test')
|
26
27
|
f.write source
|
27
28
|
f.close
|
28
29
|
|
29
|
-
merge =
|
30
|
-
user:
|
31
|
-
|
32
|
-
YAML
|
30
|
+
merge = <<~YAML
|
31
|
+
user:
|
32
|
+
name: "bob2"
|
33
|
+
YAML
|
33
34
|
|
34
|
-
|
35
|
+
MergeCommand.from_str("#{f.path} $yaml", { 'yaml' => YAML.safe_load(merge) }).run
|
35
36
|
|
36
|
-
|
37
|
+
changed = YAML.load_file(f.path)
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
assert_equal({ 'user' => {
|
40
|
+
'name' => 'bob2',
|
41
|
+
'password' => 'xyz'
|
42
|
+
} }, changed)
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
def test_deep_merge_nil
|
45
|
+
a = { param: { venison: 'yes please' } }
|
46
|
+
b = { param: nil }
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
assert_equal({venison: "yes please"}, r1[:param])
|
51
|
-
assert_equal({venison: "yes please"}, r2[:param])
|
52
|
-
end
|
48
|
+
r1 = Pups::MergeCommand.deep_merge(a, b)
|
49
|
+
r2 = Pups::MergeCommand.deep_merge(b, a)
|
53
50
|
|
51
|
+
assert_equal({ venison: 'yes please' }, r1[:param])
|
52
|
+
assert_equal({ venison: 'yes please' }, r2[:param])
|
53
|
+
end
|
54
54
|
ensure
|
55
55
|
f.unlink
|
56
56
|
end
|
@@ -1,34 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'tempfile'
|
3
5
|
|
4
6
|
module Pups
|
5
7
|
class ReplaceCommandTest < MiniTest::Test
|
6
|
-
|
7
8
|
def test_simple
|
8
9
|
command = ReplaceCommand.new({})
|
9
|
-
command.text =
|
10
|
+
command.text = 'hello world'
|
10
11
|
command.from = /he[^o]+o/
|
11
|
-
command.to =
|
12
|
+
command.to = 'world'
|
12
13
|
|
13
|
-
assert_equal(
|
14
|
+
assert_equal('world world', command.replaced_text)
|
14
15
|
end
|
15
16
|
|
16
17
|
def test_reverse
|
17
|
-
source =
|
18
|
-
1 one thousand 1
|
19
|
-
1 one thousand 1
|
20
|
-
1 one thousand 1
|
21
|
-
SCR
|
18
|
+
source = <<~SCR
|
19
|
+
1 one thousand 1
|
20
|
+
1 one thousand 1
|
21
|
+
1 one thousand 1
|
22
|
+
SCR
|
22
23
|
|
23
|
-
f = Tempfile.new(
|
24
|
+
f = Tempfile.new('test')
|
24
25
|
f.write source
|
25
26
|
f.close
|
26
27
|
|
27
28
|
hash = {
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
'filename' => f.path,
|
30
|
+
'from' => '/one t.*d/',
|
31
|
+
'to' => 'hello world',
|
32
|
+
'direction' => 'reverse'
|
32
33
|
}
|
33
34
|
|
34
35
|
command = ReplaceCommand.from_hash(hash, {})
|
@@ -39,21 +40,21 @@ SCR
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def test_global
|
42
|
-
source =
|
43
|
-
one
|
44
|
-
one
|
45
|
-
one
|
46
|
-
SCR
|
43
|
+
source = <<~SCR
|
44
|
+
one
|
45
|
+
one
|
46
|
+
one
|
47
|
+
SCR
|
47
48
|
|
48
|
-
f = Tempfile.new(
|
49
|
+
f = Tempfile.new('test')
|
49
50
|
f.write source
|
50
51
|
f.close
|
51
52
|
|
52
53
|
hash = {
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
'filename' => f.path,
|
55
|
+
'from' => '/one/',
|
56
|
+
'to' => 'two',
|
57
|
+
'global' => 'true'
|
57
58
|
}
|
58
59
|
|
59
60
|
command = ReplaceCommand.from_hash(hash, {})
|
@@ -61,50 +62,47 @@ SCR
|
|
61
62
|
assert_equal("two\ntwo\ntwo\n", command.replaced_text)
|
62
63
|
ensure
|
63
64
|
f.unlink
|
64
|
-
|
65
65
|
end
|
66
66
|
|
67
67
|
def test_replace_with_env
|
68
|
-
source =
|
68
|
+
source = '123'
|
69
69
|
|
70
|
-
f = Tempfile.new(
|
70
|
+
f = Tempfile.new('test')
|
71
71
|
f.write source
|
72
72
|
f.close
|
73
73
|
|
74
74
|
hash = {
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
'filename' => f.path,
|
76
|
+
'from' => '123',
|
77
|
+
'to' => 'hello $hellos'
|
78
78
|
}
|
79
79
|
|
80
|
-
command = ReplaceCommand.from_hash(hash, {
|
81
|
-
assert_equal(
|
82
|
-
|
80
|
+
command = ReplaceCommand.from_hash(hash, { 'hello' => 'world' })
|
81
|
+
assert_equal('hello worlds', command.replaced_text)
|
83
82
|
ensure
|
84
83
|
f.unlink
|
85
84
|
end
|
86
85
|
|
87
86
|
def test_parse
|
87
|
+
source = <<~SCR
|
88
|
+
this {
|
89
|
+
is a test
|
90
|
+
}
|
91
|
+
SCR
|
88
92
|
|
89
|
-
|
90
|
-
this {
|
91
|
-
is a test
|
92
|
-
}
|
93
|
-
SCR
|
94
|
-
|
95
|
-
f = Tempfile.new("test")
|
93
|
+
f = Tempfile.new('test')
|
96
94
|
f.write source
|
97
95
|
f.close
|
98
96
|
|
99
97
|
hash = {
|
100
|
-
|
101
|
-
|
102
|
-
|
98
|
+
'filename' => f.path,
|
99
|
+
'from' => "/this[^\}]+\}/m",
|
100
|
+
'to' => 'hello world'
|
103
101
|
}
|
104
102
|
|
105
103
|
command = ReplaceCommand.from_hash(hash, {})
|
106
104
|
|
107
|
-
assert_equal(
|
105
|
+
assert_equal('hello world', command.replaced_text.strip)
|
108
106
|
ensure
|
109
107
|
f.unlink
|
110
108
|
end
|