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/lib/pups/replace_command.rb
CHANGED
@@ -1,43 +1,45 @@
|
|
1
|
-
|
2
|
-
attr_accessor :text, :from, :to, :filename, :direction, :global
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
3
|
+
module Pups
|
4
|
+
class ReplaceCommand < Pups::Command
|
5
|
+
attr_accessor :text, :from, :to, :filename, :direction, :global
|
14
6
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
7
|
+
def self.from_hash(hash, params)
|
8
|
+
replacer = new(params)
|
9
|
+
replacer.from = guess_replace_type(hash['from'])
|
10
|
+
replacer.to = guess_replace_type(hash['to'])
|
11
|
+
replacer.text = File.read(hash['filename'])
|
12
|
+
replacer.filename = hash['filename']
|
13
|
+
replacer.direction = hash['direction'].to_sym if hash['direction']
|
14
|
+
replacer.global = hash['global'].to_s == 'true'
|
15
|
+
replacer
|
16
|
+
end
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
def self.guess_replace_type(item)
|
19
|
+
# evaling to get all the regex flags easily
|
20
|
+
item[0] == '/' ? eval(item) : item
|
21
|
+
end
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
if String === to
|
27
|
-
new_to = interpolate_params(to)
|
23
|
+
def initialize(params)
|
24
|
+
@params = params
|
28
25
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
|
27
|
+
def replaced_text
|
28
|
+
new_to = to
|
29
|
+
new_to = interpolate_params(to) if to.is_a?(String)
|
30
|
+
if global
|
31
|
+
text.gsub(from, new_to)
|
32
|
+
elsif direction == :reverse
|
33
|
+
index = text.rindex(from)
|
34
|
+
text[0..index - 1] << text[index..-1].sub(from, new_to)
|
35
|
+
else
|
36
|
+
text.sub(from, new_to)
|
37
|
+
end
|
36
38
|
end
|
37
|
-
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
def run
|
41
|
+
Pups.log.info("Replacing #{from} with #{to} in #{filename}")
|
42
|
+
File.open(filename, 'w') { |f| f.write replaced_text }
|
43
|
+
end
|
42
44
|
end
|
43
45
|
end
|
data/lib/pups/runit.rb
CHANGED
@@ -1,40 +1,39 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module Pups
|
4
|
+
class Runit
|
5
|
+
attr_accessor :env, :exec, :cd, :name
|
4
6
|
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
end
|
5
10
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
File.open(run, "w") do |f|
|
14
|
-
f.write(run_script)
|
11
|
+
def setup
|
12
|
+
`mkdir -p /etc/service/#{name}`
|
13
|
+
run = "/etc/service/#{name}/run"
|
14
|
+
File.open(run, 'w') do |f|
|
15
|
+
f.write(run_script)
|
16
|
+
end
|
17
|
+
`chmod +x #{run}`
|
15
18
|
end
|
16
|
-
`chmod +x #{run}`
|
17
|
-
end
|
18
19
|
|
19
|
-
|
20
|
-
"#!/bin/bash
|
20
|
+
def run_script
|
21
|
+
"#!/bin/bash
|
21
22
|
exec 2>&1
|
22
23
|
#{env_script}
|
23
24
|
#{cd_script}
|
24
25
|
#{exec}
|
25
26
|
"
|
26
|
-
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
def cd_script
|
30
|
+
"cd #{@cd}" if @cd
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
@env.map do |k,v|
|
33
|
+
def env_script
|
34
|
+
@env&.map do |k, v|
|
35
35
|
"export #{k}=#{v}"
|
36
|
-
end
|
36
|
+
end&.join("\n")
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
40
39
|
end
|
data/lib/pups/version.rb
CHANGED
data/pups.gemspec
CHANGED
@@ -1,26 +1,31 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'pups/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = 'pups'
|
8
9
|
spec.version = Pups::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
10
|
+
spec.authors = ['Sam Saffron']
|
11
|
+
spec.email = ['sam.saffron@gmail.com']
|
12
|
+
spec.description = 'Simple docker image creator'
|
13
|
+
spec.summary = 'Toolkit for orchestrating a composed docker image'
|
14
|
+
spec.homepage = ''
|
15
|
+
spec.license = 'MIT'
|
15
16
|
|
16
|
-
spec.files = `git ls-files`.split(
|
17
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
20
|
+
spec.require_paths = ['lib']
|
20
21
|
|
21
|
-
spec.add_development_dependency
|
22
|
-
spec.add_development_dependency
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
22
|
+
spec.add_development_dependency 'bundler'
|
23
|
+
spec.add_development_dependency 'guard'
|
24
|
+
spec.add_development_dependency 'guard-minitest'
|
25
|
+
spec.add_development_dependency 'minitest'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'rubocop'
|
28
|
+
spec.add_development_dependency 'rubocop-discourse'
|
29
|
+
spec.add_development_dependency 'rubocop-minitest'
|
30
|
+
spec.add_development_dependency 'rubocop-rake'
|
26
31
|
end
|
data/test/cli_test.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
module Pups
|
8
|
+
class CliTest < MiniTest::Test
|
9
|
+
def test_cli_option_parsing_stdin
|
10
|
+
options = Cli.parse_args(['--stdin'])
|
11
|
+
assert_equal(true, options[:stdin])
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_cli_option_parsing_none
|
15
|
+
options = Cli.parse_args([])
|
16
|
+
assert_nil(options[:stdin])
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_cli_read_config_from_file
|
20
|
+
# for testing output
|
21
|
+
f = Tempfile.new('test_output')
|
22
|
+
f.close
|
23
|
+
|
24
|
+
# for testing input
|
25
|
+
cf = Tempfile.new('test_config')
|
26
|
+
cf.puts <<~YAML
|
27
|
+
params:
|
28
|
+
run: #{f.path}
|
29
|
+
run:
|
30
|
+
- exec: echo hello world >> #{f.path}
|
31
|
+
YAML
|
32
|
+
cf.close
|
33
|
+
|
34
|
+
Cli.run([cf.path])
|
35
|
+
assert_equal('hello world', File.read(f.path).strip)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_cli_ignore_config_element
|
39
|
+
# for testing output
|
40
|
+
f = Tempfile.new('test_output')
|
41
|
+
f.close
|
42
|
+
|
43
|
+
# for testing input
|
44
|
+
cf = Tempfile.new('test_config')
|
45
|
+
cf.puts <<~YAML
|
46
|
+
env:
|
47
|
+
MY_IGNORED_VAR: a_word
|
48
|
+
params:
|
49
|
+
a_param_var: another_word
|
50
|
+
run:
|
51
|
+
- exec: echo repeating $MY_IGNORED_VAR and also $a_param_var >> #{f.path}
|
52
|
+
YAML
|
53
|
+
cf.close
|
54
|
+
|
55
|
+
Cli.run(["--ignore", "env,params", cf.path])
|
56
|
+
assert_equal('repeating and also', File.read(f.path).strip)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_cli_gen_docker_run_args_ignores_other_config
|
60
|
+
# When generating the docker run arguments it should ignore other template configuration
|
61
|
+
# like 'run' directives.
|
62
|
+
|
63
|
+
# for testing output
|
64
|
+
f = Tempfile.new("test_output")
|
65
|
+
f.close
|
66
|
+
|
67
|
+
# for testing input
|
68
|
+
cf = Tempfile.new("test_config")
|
69
|
+
cf.puts <<~YAML
|
70
|
+
env:
|
71
|
+
foo: 1
|
72
|
+
bar: 5
|
73
|
+
baz: 'hello_{{spam}}'
|
74
|
+
env_template:
|
75
|
+
spam: 'eggs'
|
76
|
+
config: my_app
|
77
|
+
params:
|
78
|
+
run: #{f.path}
|
79
|
+
run:
|
80
|
+
- exec: echo hello world >> #{f.path}
|
81
|
+
expose:
|
82
|
+
- "2222:22"
|
83
|
+
- "127.0.0.1:20080:80"
|
84
|
+
- 5555
|
85
|
+
volumes:
|
86
|
+
- volume:
|
87
|
+
host: /var/discourse/shared
|
88
|
+
guest: /shared
|
89
|
+
- volume:
|
90
|
+
host: /bar
|
91
|
+
guest: /baz
|
92
|
+
links:
|
93
|
+
- link:
|
94
|
+
name: postgres
|
95
|
+
alias: postgres
|
96
|
+
- link:
|
97
|
+
name: foo
|
98
|
+
alias: bar
|
99
|
+
labels:
|
100
|
+
monitor: "true"
|
101
|
+
app_name: "{{config}}_discourse"
|
102
|
+
YAML
|
103
|
+
cf.close
|
104
|
+
|
105
|
+
expected = []
|
106
|
+
expected << "--env foo=1 --env bar=5 --env baz=hello_eggs"
|
107
|
+
expected << "--publish 2222:22 --publish 127.0.0.1:20080:80 --expose 5555"
|
108
|
+
expected << "--volume /var/discourse/shared:/shared --volume /bar:/baz"
|
109
|
+
expected << "--link postgres:postgres --link foo:bar"
|
110
|
+
expected << "--label monitor=true --label app_name=my_app_discourse"
|
111
|
+
expected.sort!
|
112
|
+
|
113
|
+
assert_equal("", File.read(f.path).strip)
|
114
|
+
assert_output(expected.join(" ")) { Cli.run(["--gen-docker-run-args", cf.path]) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/test/config_test.rb
CHANGED
@@ -1,55 +1,217 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'tempfile'
|
3
5
|
|
4
6
|
module Pups
|
5
7
|
class ConfigTest < MiniTest::Test
|
6
|
-
|
7
8
|
def test_config_from_env
|
8
|
-
ENV[
|
9
|
+
ENV['HELLO'] = 'world'
|
9
10
|
config = Config.new({})
|
10
|
-
assert_equal(
|
11
|
+
assert_equal('world', config.params['$ENV_HELLO'])
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
+
def test_env_param
|
15
|
+
ENV['FOO'] = 'BAR'
|
16
|
+
config = <<~YAML
|
17
|
+
env:
|
18
|
+
BAR: baz
|
19
|
+
hello: WORLD
|
20
|
+
one: 1
|
21
|
+
YAML
|
14
22
|
|
15
|
-
|
16
|
-
|
23
|
+
config = Config.new(YAML.safe_load(config))
|
24
|
+
%w[BAR hello one].each { |e| ENV.delete(e) }
|
25
|
+
assert_equal('BAR', config.params['$ENV_FOO'])
|
26
|
+
assert_equal('baz', config.params['$ENV_BAR'])
|
27
|
+
assert_equal('WORLD', config.params['$ENV_hello'])
|
28
|
+
assert_equal('1', config.params['$ENV_one'])
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_env_with_template
|
32
|
+
ENV['FOO'] = 'BAR'
|
33
|
+
config = <<~YAML
|
34
|
+
env:
|
35
|
+
greeting: "{{hello}}, {{planet}}!"
|
36
|
+
one: 1
|
37
|
+
other: "where are we on {{planet}}?"
|
38
|
+
env_template:
|
39
|
+
planet: pluto
|
40
|
+
hello: hola
|
41
|
+
YAML
|
42
|
+
config_hash = YAML.safe_load(config)
|
43
|
+
|
44
|
+
config = Config.new(config_hash)
|
45
|
+
%w[greeting one other].each { |e| ENV.delete(e) }
|
46
|
+
assert_equal('hola, pluto!', config.params['$ENV_greeting'])
|
47
|
+
assert_equal('1', config.params['$ENV_one'])
|
48
|
+
assert_equal('BAR', config.params['$ENV_FOO'])
|
49
|
+
assert_equal('where are we on pluto?', config.params['$ENV_other'])
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_label_with_template
|
53
|
+
ENV["FOO"] = "BAR"
|
54
|
+
config = <<~YAML
|
55
|
+
env:
|
56
|
+
greeting: "{{hello}}, {{planet}}!"
|
57
|
+
one: 1
|
58
|
+
other: "where are we on {{planet}}?"
|
59
|
+
env_template:
|
60
|
+
planet: pluto
|
61
|
+
hello: hola
|
62
|
+
config: various
|
63
|
+
labels:
|
64
|
+
app_name: "{{config}}_discourse"
|
65
|
+
YAML
|
66
|
+
config_hash = YAML.load(config)
|
67
|
+
|
68
|
+
config = Config.new(config_hash)
|
69
|
+
%w[greeting one other].each { |e| ENV.delete(e) }
|
70
|
+
assert_equal("various_discourse", config.config['labels']['app_name'])
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_env_with_ENV_templated_variable
|
74
|
+
ENV['env_template_config'] = 'my_application'
|
75
|
+
config = <<~YAML
|
76
|
+
env:
|
77
|
+
greeting: "{{hello}}, {{planet}}!"
|
78
|
+
one: 1
|
79
|
+
other: "building {{config}}"
|
80
|
+
env_template:
|
81
|
+
planet: pluto
|
82
|
+
hello: hola
|
83
|
+
YAML
|
84
|
+
config_hash = YAML.safe_load(config)
|
85
|
+
|
86
|
+
config = Config.new(config_hash)
|
87
|
+
%w[greeting one other].each { |e| ENV.delete(e) }
|
88
|
+
assert_equal('hola, pluto!', config.params['$ENV_greeting'])
|
89
|
+
assert_equal('1', config.params['$ENV_one'])
|
90
|
+
assert_equal('building my_application', config.params['$ENV_other'])
|
91
|
+
ENV["env_template_config"] = nil
|
92
|
+
end
|
17
93
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
run:
|
22
|
-
- exec: echo hello world >> #{f.path}
|
23
|
-
YAML
|
94
|
+
def test_integration
|
95
|
+
f = Tempfile.new('test')
|
96
|
+
f.close
|
24
97
|
|
25
|
-
|
26
|
-
|
98
|
+
config = <<~YAML
|
99
|
+
env:
|
100
|
+
PLANET: world
|
101
|
+
params:
|
102
|
+
run: #{f.path}
|
103
|
+
greeting: hello
|
104
|
+
run:
|
105
|
+
- exec: echo $greeting $PLANET >> #{f.path}
|
106
|
+
YAML
|
27
107
|
|
108
|
+
Config.new(YAML.safe_load(config)).run
|
109
|
+
ENV.delete('PLANET')
|
110
|
+
assert_equal('hello world', File.read(f.path).strip)
|
28
111
|
ensure
|
29
112
|
f.unlink
|
30
113
|
end
|
31
114
|
|
32
115
|
def test_hooks
|
33
|
-
yaml =
|
34
|
-
run:
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
hooks:
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
YAML
|
116
|
+
yaml = <<~YAML
|
117
|
+
run:
|
118
|
+
- exec: 1
|
119
|
+
- exec:
|
120
|
+
hook: middle
|
121
|
+
cmd: 2
|
122
|
+
- exec: 3
|
123
|
+
hooks:
|
124
|
+
after_middle:
|
125
|
+
- exec: 2.1
|
126
|
+
before_middle:
|
127
|
+
- exec: 1.9
|
128
|
+
YAML
|
46
129
|
|
47
130
|
config = Config.load_config(yaml).config
|
48
|
-
assert_equal({
|
49
|
-
assert_equal({
|
131
|
+
assert_equal({ 'exec' => 1.9 }, config['run'][1])
|
132
|
+
assert_equal({ 'exec' => 2.1 }, config['run'][3])
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_ignored_elements
|
136
|
+
f = Tempfile.new('test')
|
137
|
+
f.close
|
138
|
+
|
139
|
+
yaml = <<~YAML
|
140
|
+
env:
|
141
|
+
PLANET: world
|
142
|
+
params:
|
143
|
+
greeting: hello
|
144
|
+
run:
|
145
|
+
- exec: 1
|
146
|
+
- exec:
|
147
|
+
hook: middle
|
148
|
+
cmd: 2
|
149
|
+
- exec: 3
|
150
|
+
- exec: echo $greeting $PLANET >> #{f.path}
|
151
|
+
hooks:
|
152
|
+
after_middle:
|
153
|
+
- exec: 2.1
|
154
|
+
before_middle:
|
155
|
+
- exec: 1.9
|
156
|
+
YAML
|
50
157
|
|
158
|
+
conf = Config.load_config(yaml, %w[hooks params])
|
159
|
+
config = conf.config
|
160
|
+
assert_equal({ 'exec' => 1 }, config['run'][0])
|
161
|
+
assert_equal({ 'exec' => { 'hook' => 'middle', 'cmd' => 2 } }, config['run'][1])
|
162
|
+
assert_equal({ 'exec' => 3 }, config['run'][2])
|
163
|
+
assert_equal({ 'exec' => "echo $greeting $PLANET >> #{f.path}" }, config['run'][3])
|
51
164
|
|
165
|
+
# $greet from params will be an empty var as it was ignored
|
166
|
+
conf.run
|
167
|
+
ENV.delete('PLANET')
|
168
|
+
assert_equal('world', File.read(f.path).strip)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_generate_docker_run_arguments
|
172
|
+
yaml = <<~YAML
|
173
|
+
env:
|
174
|
+
foo: 1
|
175
|
+
bar: 2
|
176
|
+
baz: 'hello_{{spam}}'
|
177
|
+
env_template:
|
178
|
+
spam: 'eggs'
|
179
|
+
config: my_app
|
180
|
+
expose:
|
181
|
+
- "2222:22"
|
182
|
+
- "127.0.0.1:20080:80"
|
183
|
+
- 5555
|
184
|
+
volumes:
|
185
|
+
- volume:
|
186
|
+
host: /var/discourse/shared
|
187
|
+
guest: /shared
|
188
|
+
- volume:
|
189
|
+
host: /bar
|
190
|
+
guest: /baz
|
191
|
+
links:
|
192
|
+
- link:
|
193
|
+
name: postgres
|
194
|
+
alias: postgres
|
195
|
+
- link:
|
196
|
+
name: foo
|
197
|
+
alias: bar
|
198
|
+
labels:
|
199
|
+
monitor: "true"
|
200
|
+
app_name: "{{config}}_discourse"
|
201
|
+
YAML
|
202
|
+
|
203
|
+
config = Config.load_config(yaml)
|
204
|
+
args = config.generate_docker_run_arguments
|
205
|
+
|
206
|
+
expected = []
|
207
|
+
expected << "--env foo=1 --env bar=2 --env baz=hello_eggs"
|
208
|
+
expected << "--publish 2222:22 --publish 127.0.0.1:20080:80 --expose 5555"
|
209
|
+
expected << "--volume /var/discourse/shared:/shared --volume /bar:/baz"
|
210
|
+
expected << "--link postgres:postgres --link foo:bar"
|
211
|
+
expected << "--label monitor=true --label app_name=my_app_discourse"
|
212
|
+
expected.sort!
|
213
|
+
|
214
|
+
assert_equal(expected.join(" "), args)
|
52
215
|
end
|
53
216
|
end
|
54
217
|
end
|
55
|
-
|