mirror of https://github.com/discourse/pups.git
Compare commits
25 Commits
Author | SHA1 | Date |
---|---|---|
|
332921814b | |
|
f91de7519a | |
|
078edb6407 | |
|
5436aec99e | |
|
1276ccd44d | |
|
d51da1fc1d | |
|
e0ff889553 | |
|
3f02d19ad1 | |
|
9a9514810e | |
|
193242003d | |
|
ebe1069395 | |
|
44198d7704 | |
|
2069d66f39 | |
|
a5c7d9c9c2 | |
|
17f04ecde5 | |
|
f9af90d645 | |
|
b2e71c594d | |
|
d1db0303d0 | |
|
05db637f26 | |
|
262d7eb4f8 | |
|
dadedef18e | |
|
936c3f5da7 | |
|
8f353a3778 | |
|
7db5398b78 | |
|
4cfff79a83 |
|
@ -0,0 +1,62 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: "pups lint"
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 5
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
ruby: ["3.3"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
bundler-cache: true
|
||||
- run: bundle exec rubocop
|
||||
test:
|
||||
name: "pups tests"
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 5
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
ruby: ["3.3"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
bundler-cache: true
|
||||
- name: Run minitest
|
||||
run: |
|
||||
rake test
|
||||
|
||||
publish:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
needs: [test]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Release Gem
|
||||
uses: discourse/publish-rubygems-action@v2
|
||||
env:
|
||||
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
||||
GIT_EMAIL: team@discourse.org
|
||||
GIT_NAME: discoursebot
|
|
@ -0,0 +1,2 @@
|
|||
inherit_gem:
|
||||
rubocop-discourse: default.yml
|
|
@ -0,0 +1,11 @@
|
|||
1.3.0 - 07-08-2025
|
||||
|
||||
- Add --params option
|
||||
|
||||
1.2.0 - 10-22-2023
|
||||
|
||||
- Add --tags and --skip-tags options
|
||||
|
||||
1.0.3 - 09-04-2021
|
||||
|
||||
- Started changelog - release to rubygems
|
2
Gemfile
2
Gemfile
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in pups.gemspec
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
guard :minitest do
|
||||
# with Minitest::Unit
|
||||
watch(%r{^test/(.*)\/?(.*)_test\.rb$})
|
||||
watch(%r{^test/(.*)/?(.*)_test\.rb$})
|
||||
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[2]}_test.rb" }
|
||||
watch(%r{^test/test_helper\.rb$}) { 'test' }
|
||||
end
|
||||
|
|
91
README.md
91
README.md
|
@ -20,6 +20,21 @@ Or install it yourself as:
|
|||
|
||||
pups is a small library that allows you to automate the process of creating Unix images.
|
||||
|
||||
```
|
||||
Usage: pups [options] [FILE|--stdin]
|
||||
--stdin Read input from stdin.
|
||||
--quiet Don't print any logs.
|
||||
--ignore <elements> Ignore specific configuration elements, multiple elements can be provided (comma-delimited).
|
||||
Useful if you want to skip over config in a pups execution.
|
||||
e.g. `--ignore env,params`.
|
||||
--tags <elements> Only run tagged commands.
|
||||
--skip-tags <elements> Run all but listed tagged commands.
|
||||
--gen-docker-run-args Output arguments from the pups configuration for input into a docker run command. All other pups config is ignored.
|
||||
-h, --help
|
||||
```
|
||||
|
||||
pups requires input either via a stdin stream or a filename. The entire input is parsed prior to any templating or command execution.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
|
@ -35,6 +50,60 @@ Running: `pups somefile.yaml` will execute the shell script resulting in a file
|
|||
|
||||
### Features
|
||||
|
||||
#### Filtering run commands by tags
|
||||
|
||||
The `--tags` and `--skip-tags` argument allows pups to target a subset of commands listed in the somefile.yaml. To use this, you may tag your commands in the runblock. `--tags` will only run commands when commands have a matching tag. `--skip-tags` will skip when commands have a matching tag.
|
||||
|
||||
Note, hooks from tagged commands will be present or absent depending on if the tag is filtered out or not as well. A command filtered out by targeting tag will also filter out the command's `before_` and `after_` hooks.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# somefile.yaml
|
||||
|
||||
run:
|
||||
- exec:
|
||||
cmd: /bin/bash -c 'echo hello >> hello'
|
||||
tag: sometag
|
||||
- exec:
|
||||
cmd: /bin/bash -c 'echo hi >> hello'
|
||||
tag: anothertag
|
||||
- exec:
|
||||
cmd: /bin/bash -c 'echo goodbye >> hello'
|
||||
tag: thirdtag
|
||||
```
|
||||
Running: `pups --tags="sometag,anothertag" somefile.yaml` will not run the echo goodbye statement.
|
||||
|
||||
Running: `pups --skip-tags="sometag,anothertag" somefile.yaml` will ONLY run the echo goodbye statement.
|
||||
|
||||
#### Parameter overriding
|
||||
|
||||
The `--params` argument allows pups to dynamically override params set within a configuration for the single pups run.
|
||||
|
||||
Note, it is expected to be of the form `key=value`. If it is malformed, a warning will be thrown.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# somefile.yaml
|
||||
|
||||
params:
|
||||
param1: false_prophet
|
||||
param2: also overridden
|
||||
run:
|
||||
- exec:
|
||||
cmd: /bin/bash -c 'echo $param1 $param2 >> hello'
|
||||
```
|
||||
Running `pups --params="param1=true_value,param2=other_true_value" somefile.yaml` will overwrite param1 and param2 with true_value and other_true_value respectively
|
||||
|
||||
#### Docker run argument generation
|
||||
|
||||
The `--gen-docker-run-args` argument is used to make pups output arguments be in the format of `docker run <arguments output>`. Specifically, pups
|
||||
will take any `env`, `volume`, `labels`, `links`, and `expose` configuration, and coerce that into the format expected by `docker run`. This can be useful
|
||||
when pups is being used to configure an image (e.g. by executing a series of commands) that is then going to be run as a container. That way, the runtime and image
|
||||
configuration can be specified within the same yaml files.
|
||||
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
By default, pups automatically imports your environment variables and includes them as params.
|
||||
|
@ -149,15 +218,31 @@ Will merge the yaml file with the inline contents.
|
|||
|
||||
#### A common environment
|
||||
|
||||
This is implemented in discourse_docker's launcher, not in pups - therefore it does not work in standalone pups.
|
||||
Environment variables can be specified under the `env` key, which will be included in the environment for the template.
|
||||
|
||||
```
|
||||
env:
|
||||
MY_ENV: 1
|
||||
MY_ENV: "a couple of words"
|
||||
run:
|
||||
- exec: echo $MY_ENV > tmpfile
|
||||
```
|
||||
|
||||
All executions will get this environment set up
|
||||
`tmpfile` will contain `a couple of words`.
|
||||
|
||||
You can also specify variables to be templated within the environment, such as:
|
||||
|
||||
```
|
||||
env:
|
||||
greeting: "hello, {{location}}!"
|
||||
env_template:
|
||||
location: world
|
||||
```
|
||||
|
||||
In this example, the `greeting` environment variable will be set to `hello, world!` during initialisation as the `{{location}}` variable will be templated as `world`.
|
||||
Pups will also look in the environment itself at runtime for template variables, prefixed with `env_template_<variable name>`.
|
||||
Note that strings should be quoted to prevent YAML from parsing the `{ }` characters.
|
||||
|
||||
All commands executed will inherit the environment once parsing and variable interpolation has been completed.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
12
Rakefile
12
Rakefile
|
@ -1,6 +1,12 @@
|
|||
require "bundler/gem_tasks"
|
||||
require "rake/testtask"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bundler/gem_tasks'
|
||||
require 'rake/testtask'
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.pattern = "test/*_test.rb"
|
||||
t.libs << 'test'
|
||||
t.libs << 'lib'
|
||||
t.test_files = FileList['test/*_test.rb']
|
||||
end
|
||||
|
||||
task default: :test
|
||||
|
|
8
bin/pups
8
bin/pups
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
|
||||
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
||||
|
||||
require "pups"
|
||||
require "pups/cli"
|
||||
require 'pups'
|
||||
require 'pups/cli'
|
||||
|
||||
Pups::Cli.run(ARGV)
|
||||
|
||||
|
|
12
lib/pups.rb
12
lib/pups.rb
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "logger"
|
||||
require "yaml"
|
||||
|
||||
|
@ -8,7 +10,7 @@ require "pups/exec_command"
|
|||
require "pups/merge_command"
|
||||
require "pups/replace_command"
|
||||
require "pups/file_command"
|
||||
|
||||
require "pups/docker"
|
||||
require "pups/runit"
|
||||
|
||||
module Pups
|
||||
|
@ -18,10 +20,16 @@ module Pups
|
|||
|
||||
def self.log
|
||||
# at the moment docker likes this
|
||||
@logger ||= Logger.new(STDERR)
|
||||
@logger ||= Logger.new($stderr)
|
||||
end
|
||||
|
||||
def self.log=(logger)
|
||||
@logger = logger
|
||||
end
|
||||
|
||||
def self.silence
|
||||
@logger.close if @logger
|
||||
|
||||
@logger = Logger.new(File.open(File::NULL, "w"))
|
||||
end
|
||||
end
|
||||
|
|
122
lib/pups/cli.rb
122
lib/pups/cli.rb
|
@ -1,35 +1,101 @@
|
|||
class Pups::Cli
|
||||
# frozen_string_literal: true
|
||||
|
||||
def self.usage
|
||||
puts "Usage: pups FILE or pups --stdin"
|
||||
exit 1
|
||||
end
|
||||
def self.run(args)
|
||||
if args.length != 1
|
||||
usage
|
||||
end
|
||||
require "optparse"
|
||||
|
||||
Pups.log.info("Loading #{args[0]}")
|
||||
if args[0] == "--stdin"
|
||||
conf = STDIN.readlines.join
|
||||
split = conf.split("_FILE_SEPERATOR_")
|
||||
|
||||
conf = nil
|
||||
split.each do |data|
|
||||
current = YAML.load(data.strip)
|
||||
if conf
|
||||
conf = Pups::MergeCommand.deep_merge(conf, current, :merge_arrays)
|
||||
else
|
||||
conf = current
|
||||
module Pups
|
||||
class Cli
|
||||
def self.opts
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: pups [FILE|--stdin]"
|
||||
opts.on("--stdin", "Read input from stdin.")
|
||||
opts.on("--quiet", "Don't print any logs.")
|
||||
opts.on(
|
||||
"--ignore <element(s)>",
|
||||
Array,
|
||||
"Ignore these template configuration elements, multiple elements can be provided (comma-delimited)."
|
||||
)
|
||||
opts.on(
|
||||
"--gen-docker-run-args",
|
||||
"Output arguments from the pups configuration for input into a docker run command. All other pups config is ignored."
|
||||
)
|
||||
opts.on("--tags <tag(s)>", Array, "Only run tagged commands.")
|
||||
opts.on(
|
||||
"--skip-tags <tag(s)>",
|
||||
Array,
|
||||
"Run all but listed tagged commands."
|
||||
)
|
||||
opts.on(
|
||||
"--params <param(s)>",
|
||||
Array,
|
||||
"Replace params in the config with params listed."
|
||||
)
|
||||
opts.on("-h", "--help") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
config = Pups::Config.new(conf)
|
||||
else
|
||||
config = Pups::Config.load_file(args[0])
|
||||
end
|
||||
config.run
|
||||
ensure
|
||||
Pups::ExecCommand.terminate_async
|
||||
|
||||
def self.parse_args(args)
|
||||
options = {}
|
||||
opts.parse!(args, into: options)
|
||||
options
|
||||
end
|
||||
|
||||
def self.run(args)
|
||||
options = parse_args(args)
|
||||
input_file = options[:stdin] ? "stdin" : args.last
|
||||
unless input_file
|
||||
puts opts.parse!(%w[--help])
|
||||
exit
|
||||
end
|
||||
|
||||
Pups.silence if options[:quiet]
|
||||
|
||||
Pups.log.info("Reading from #{input_file}")
|
||||
|
||||
if options[:stdin]
|
||||
conf = $stdin.readlines.join
|
||||
split = conf.split("_FILE_SEPERATOR_")
|
||||
|
||||
conf = nil
|
||||
split.each do |data|
|
||||
current = YAML.safe_load(data.strip)
|
||||
conf =
|
||||
if conf
|
||||
Pups::MergeCommand.deep_merge(conf, current, :merge_arrays)
|
||||
else
|
||||
current
|
||||
end
|
||||
end
|
||||
|
||||
config =
|
||||
Pups::Config.new(
|
||||
conf,
|
||||
options[:ignore],
|
||||
tags: options[:tags],
|
||||
skip_tags: options[:"skip-tags"],
|
||||
extra_params: options[:params]
|
||||
)
|
||||
else
|
||||
config =
|
||||
Pups::Config.load_file(
|
||||
input_file,
|
||||
options[:ignore],
|
||||
tags: options[:tags],
|
||||
skip_tags: options[:"skip-tags"],
|
||||
extra_params: options[:params]
|
||||
)
|
||||
end
|
||||
|
||||
if options[:"gen-docker-run-args"]
|
||||
print config.generate_docker_run_arguments
|
||||
return
|
||||
end
|
||||
|
||||
config.run
|
||||
ensure
|
||||
Pups::ExecCommand.terminate_async
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
class Pups::Command
|
||||
# frozen_string_literal: true
|
||||
|
||||
def self.run(command,params)
|
||||
case command
|
||||
when String then self.from_str(command,params).run
|
||||
when Hash then self.from_hash(command,params).run
|
||||
module Pups
|
||||
class Command
|
||||
def self.run(command, params)
|
||||
case command
|
||||
when String
|
||||
from_str(command, params).run
|
||||
when Hash
|
||||
from_hash(command, params).run
|
||||
end
|
||||
end
|
||||
|
||||
def self.interpolate_params(cmd, params)
|
||||
Pups::Config.interpolate_params(cmd, params)
|
||||
end
|
||||
|
||||
def interpolate_params(cmd)
|
||||
Pups::Command.interpolate_params(cmd, @params)
|
||||
end
|
||||
end
|
||||
|
||||
def self.interpolate_params(cmd,params)
|
||||
Pups::Config.interpolate_params(cmd,params)
|
||||
end
|
||||
|
||||
def interpolate_params(cmd)
|
||||
Pups::Command.interpolate_params(cmd,@params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,128 +1,256 @@
|
|||
class Pups::Config
|
||||
# frozen_string_literal: true
|
||||
|
||||
attr_reader :config, :params
|
||||
module Pups
|
||||
class Config
|
||||
attr_reader :config, :params
|
||||
|
||||
def self.load_file(config_file)
|
||||
begin
|
||||
new YAML.load_file(config_file)
|
||||
def initialize(
|
||||
config,
|
||||
ignored = nil,
|
||||
tags: nil,
|
||||
skip_tags: nil,
|
||||
extra_params: nil
|
||||
)
|
||||
@config = config
|
||||
|
||||
# remove any ignored config elements prior to any more processing
|
||||
ignored&.each { |e| @config.delete(e) }
|
||||
|
||||
filter_tags(include_tags: tags, exclude_tags: skip_tags)
|
||||
|
||||
# set some defaults to prevent checks in various functions
|
||||
%w[env_template env labels params].each do |key|
|
||||
@config[key] = {} unless @config.has_key?(key)
|
||||
end
|
||||
|
||||
# Order here is important.
|
||||
Pups::Config.combine_template_and_process_env(@config, ENV)
|
||||
Pups::Config.prepare_env_template_vars(@config["env_template"], ENV)
|
||||
|
||||
# Templating is supported in env and label variables.
|
||||
Pups::Config.transform_config_with_templated_vars(
|
||||
@config["env_template"],
|
||||
ENV
|
||||
)
|
||||
Pups::Config.transform_config_with_templated_vars(
|
||||
@config["env_template"],
|
||||
@config["env"]
|
||||
)
|
||||
Pups::Config.transform_config_with_templated_vars(
|
||||
@config["env_template"],
|
||||
@config["labels"]
|
||||
)
|
||||
|
||||
@params = @config["params"]
|
||||
if extra_params
|
||||
extra_params.each do |val|
|
||||
key_val = val.split("=", 2)
|
||||
if key_val.length == 2
|
||||
@params[key_val[0]] = key_val[1]
|
||||
else
|
||||
warn "Malformed param #{val}. Expected param to be of the form `key=value`"
|
||||
end
|
||||
end
|
||||
end
|
||||
ENV.each { |k, v| @params["$ENV_#{k}"] = v }
|
||||
inject_hooks
|
||||
end
|
||||
|
||||
def self.load_file(
|
||||
config_file,
|
||||
ignored = nil,
|
||||
tags: nil,
|
||||
skip_tags: nil,
|
||||
extra_params: nil
|
||||
)
|
||||
Config.new(
|
||||
YAML.load_file(config_file),
|
||||
ignored,
|
||||
tags: tags,
|
||||
skip_tags: skip_tags,
|
||||
extra_params: extra_params
|
||||
)
|
||||
rescue Exception
|
||||
STDERR.puts "Failed to parse #{config_file}"
|
||||
STDERR.puts "This is probably a formatting error in #{config_file}"
|
||||
STDERR.puts "Cannot continue. Edit #{config_file} and try again."
|
||||
warn "Failed to parse #{config_file}"
|
||||
warn "This is probably a formatting error in #{config_file}"
|
||||
warn "Cannot continue. Edit #{config_file} and try again."
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def self.load_config(config)
|
||||
new YAML.load(config)
|
||||
end
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
validate!(@config)
|
||||
@params = @config["params"]
|
||||
@params ||= {}
|
||||
ENV.each do |k,v|
|
||||
@params["$ENV_#{k}"] = v
|
||||
def self.load_config(
|
||||
config,
|
||||
ignored = nil,
|
||||
tags: nil,
|
||||
skip_tags: nil,
|
||||
extra_params: nil
|
||||
)
|
||||
Config.new(
|
||||
YAML.safe_load(config),
|
||||
ignored,
|
||||
tags: tags,
|
||||
skip_tags: skip_tags,
|
||||
extra_params: extra_params
|
||||
)
|
||||
end
|
||||
inject_hooks
|
||||
end
|
||||
|
||||
def validate!(conf)
|
||||
# raise proper errors if nodes are missing etc
|
||||
end
|
||||
|
||||
def inject_hooks
|
||||
return unless hooks = @config["hooks"]
|
||||
|
||||
run = @config["run"]
|
||||
|
||||
positions = {}
|
||||
run.each do |row|
|
||||
if Hash === row
|
||||
command = row.first
|
||||
if Hash === command[1]
|
||||
hook = command[1]["hook"]
|
||||
positions[hook] = row if hook
|
||||
def self.prepare_env_template_vars(env_template, env)
|
||||
# Merge env_template variables from env and templates.
|
||||
env.each do |k, v|
|
||||
if k.include?("env_template_")
|
||||
key = k.gsub("env_template_", "")
|
||||
env_template[key] = v.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hooks.each do |full, list|
|
||||
|
||||
offset = nil
|
||||
name = nil
|
||||
|
||||
if full =~ /^after_/
|
||||
name = full[6..-1]
|
||||
offset = 1
|
||||
end
|
||||
|
||||
if full =~ /^before_/
|
||||
name = full[7..-1]
|
||||
offset = 0
|
||||
end
|
||||
|
||||
index = run.index(positions[name])
|
||||
|
||||
if index && index >= 0
|
||||
run.insert(index + offset, *list)
|
||||
else
|
||||
Pups.log.info "Skipped missing #{full} hook"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
run_commands
|
||||
rescue => e
|
||||
exit_code = 1
|
||||
if Pups::ExecError === e
|
||||
exit_code = e.exit_code
|
||||
end
|
||||
unless exit_code == 77
|
||||
puts
|
||||
puts
|
||||
puts "FAILED"
|
||||
puts "-" * 20
|
||||
puts "#{e.class}: #{e}"
|
||||
puts "Location of failure: #{e.backtrace[0]}"
|
||||
if @last_command
|
||||
puts "#{@last_command[:command]} failed with the params #{@last_command[:params].inspect}"
|
||||
def self.transform_config_with_templated_vars(env_template, to_transform)
|
||||
# Transform any templated variables prior to copying to params.
|
||||
# This has no effect if no env_template was provided.
|
||||
env_template.each do |k, v|
|
||||
to_transform.each do |key, val|
|
||||
if val.to_s.include?("{{#{k}}}")
|
||||
to_transform[key] = val.gsub("{{#{k}}}", v.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
exit exit_code
|
||||
end
|
||||
|
||||
def run_commands
|
||||
@config["run"].each do |item|
|
||||
item.each do |k,v|
|
||||
type = case k
|
||||
when "exec" then Pups::ExecCommand
|
||||
when "merge" then Pups::MergeCommand
|
||||
when "replace" then Pups::ReplaceCommand
|
||||
when "file" then Pups::FileCommand
|
||||
else raise SyntaxError.new("Invalid run command #{k}")
|
||||
end
|
||||
def self.combine_template_and_process_env(config, env)
|
||||
# Merge all template env variables and process env variables, so that env
|
||||
# variables can be provided both by configuration and runtime variables.
|
||||
config["env"].each { |k, v| env[k] = v.to_s }
|
||||
end
|
||||
|
||||
@last_command = { command: k, params: v }
|
||||
type.run(v, @params)
|
||||
# Filter run commands by tag: by default, keep all commands that contain tags.
|
||||
# If skip_tags argument is true, keep all commands that DO NOT contain tags.
|
||||
def filter_tags(include_tags: nil, exclude_tags: nil)
|
||||
if include_tags
|
||||
@config["run"] = @config["run"].select do |row|
|
||||
keep = false
|
||||
command = row.first
|
||||
if command[1].is_a?(Hash)
|
||||
tag = command[1]["tag"]
|
||||
keep = include_tags.include?(tag)
|
||||
end
|
||||
keep
|
||||
end
|
||||
end
|
||||
|
||||
if exclude_tags
|
||||
@config["run"] = @config["run"].select do |row|
|
||||
keep = true
|
||||
command = row.first
|
||||
if command[1].is_a?(Hash)
|
||||
tag = command[1]["tag"]
|
||||
keep = !exclude_tags.include?(tag)
|
||||
end
|
||||
keep
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def interpolate_params(cmd)
|
||||
self.class.interpolate_params(cmd,@params)
|
||||
end
|
||||
def inject_hooks
|
||||
return unless hooks = @config["hooks"]
|
||||
|
||||
def self.interpolate_params(cmd, params)
|
||||
return unless cmd
|
||||
processed = cmd.dup
|
||||
params.each do |k,v|
|
||||
processed.gsub!("$#{k}", v.to_s)
|
||||
run = @config["run"]
|
||||
|
||||
positions = {}
|
||||
run.each do |row|
|
||||
next unless row.is_a?(Hash)
|
||||
|
||||
command = row.first
|
||||
if command[1].is_a?(Hash)
|
||||
hook = command[1]["hook"]
|
||||
positions[hook] = row if hook
|
||||
end
|
||||
end
|
||||
|
||||
hooks.each do |full, list|
|
||||
offset = nil
|
||||
name = nil
|
||||
|
||||
if full =~ /^after_/
|
||||
name = full[6..-1]
|
||||
offset = 1
|
||||
end
|
||||
|
||||
if full =~ /^before_/
|
||||
name = full[7..-1]
|
||||
offset = 0
|
||||
end
|
||||
|
||||
index = run.index(positions[name])
|
||||
|
||||
if index && index >= 0
|
||||
run.insert(index + offset, *list)
|
||||
else
|
||||
Pups.log.info "Skipped missing #{full} hook"
|
||||
end
|
||||
end
|
||||
end
|
||||
processed
|
||||
end
|
||||
|
||||
def generate_docker_run_arguments
|
||||
output = []
|
||||
output << Pups::Docker.generate_env_arguments(config["env"])
|
||||
output << Pups::Docker.generate_link_arguments(config["links"])
|
||||
output << Pups::Docker.generate_expose_arguments(config["expose"])
|
||||
output << Pups::Docker.generate_volume_arguments(config["volumes"])
|
||||
output << Pups::Docker.generate_label_arguments(config["labels"])
|
||||
output.sort!.join(" ").strip
|
||||
end
|
||||
|
||||
def run
|
||||
run_commands
|
||||
rescue StandardError => e
|
||||
exit_code = 1
|
||||
exit_code = e.exit_code if e.is_a?(Pups::ExecError)
|
||||
unless exit_code == 77
|
||||
puts
|
||||
puts
|
||||
puts "FAILED"
|
||||
puts "-" * 20
|
||||
puts "#{e.class}: #{e}"
|
||||
puts "Location of failure: #{e.backtrace[0]}"
|
||||
if @last_command
|
||||
puts "#{@last_command[:command]} failed with the params #{@last_command[:params].inspect}"
|
||||
end
|
||||
end
|
||||
exit exit_code
|
||||
end
|
||||
|
||||
def run_commands
|
||||
@config["run"]&.each do |item|
|
||||
item.each do |k, v|
|
||||
type =
|
||||
case k
|
||||
when "exec"
|
||||
Pups::ExecCommand
|
||||
when "merge"
|
||||
Pups::MergeCommand
|
||||
when "replace"
|
||||
Pups::ReplaceCommand
|
||||
when "file"
|
||||
Pups::FileCommand
|
||||
else
|
||||
raise SyntaxError, "Invalid run command #{k}"
|
||||
end
|
||||
|
||||
@last_command = { command: k, params: v }
|
||||
type.run(v, @params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def interpolate_params(cmd)
|
||||
self.class.interpolate_params(cmd, @params)
|
||||
end
|
||||
|
||||
def self.interpolate_params(cmd, params)
|
||||
return unless cmd
|
||||
|
||||
processed = cmd.dup
|
||||
params.each { |k, v| processed.gsub!("$#{k}", v.to_s) }
|
||||
processed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
require "shellwords"
|
||||
|
||||
class Pups::Docker
|
||||
class << self
|
||||
def generate_env_arguments(config)
|
||||
output = []
|
||||
config&.each do |k, v|
|
||||
if !v.to_s.empty?
|
||||
output << "--env #{k}=#{escape_user_string_literal(v)}"
|
||||
end
|
||||
end
|
||||
normalize_output(output)
|
||||
end
|
||||
|
||||
def generate_link_arguments(config)
|
||||
output = []
|
||||
config&.each do |c|
|
||||
output << "--link #{c["link"]["name"]}:#{c["link"]["alias"]}"
|
||||
end
|
||||
normalize_output(output)
|
||||
end
|
||||
|
||||
def generate_expose_arguments(config)
|
||||
output = []
|
||||
config&.each do |c|
|
||||
if c.to_s.include?(":")
|
||||
output << "--publish #{c}"
|
||||
else
|
||||
output << "--expose #{c}"
|
||||
end
|
||||
end
|
||||
normalize_output(output)
|
||||
end
|
||||
|
||||
def generate_volume_arguments(config)
|
||||
output = []
|
||||
config&.each do |c|
|
||||
output << "--volume #{c["volume"]["host"]}:#{c["volume"]["guest"]}"
|
||||
end
|
||||
normalize_output(output)
|
||||
end
|
||||
|
||||
def generate_label_arguments(config)
|
||||
output = []
|
||||
config&.each do |k, v|
|
||||
output << "--label #{k}=#{escape_user_string_literal(v)}"
|
||||
end
|
||||
normalize_output(output)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def escape_user_string_literal(str)
|
||||
# We need to escape the following strings as they are more likely to contain
|
||||
# special characters than any of the other config variables on a Linux system:
|
||||
# - the value side of an environment variable
|
||||
# - the value side of a label.
|
||||
Shellwords.escape(str)
|
||||
end
|
||||
|
||||
def normalize_output(output)
|
||||
output.empty? ? "" : output.join(" ")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,124 +1,142 @@
|
|||
require 'timeout'
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Pups::ExecCommand < Pups::Command
|
||||
attr_reader :commands, :cd
|
||||
attr_accessor :background, :raise_on_fail, :stdin, :stop_signal
|
||||
require "timeout"
|
||||
require "English"
|
||||
|
||||
def self.terminate_async(opts={})
|
||||
module Pups
|
||||
class ExecCommand < Pups::Command
|
||||
attr_reader :commands, :cd
|
||||
attr_accessor :background, :raise_on_fail, :stdin, :stop_signal
|
||||
|
||||
return unless defined? @@asyncs
|
||||
def self.terminate_async(opts = {})
|
||||
return unless defined?(@@asyncs)
|
||||
|
||||
Pups.log.info("Terminating async processes")
|
||||
Pups.log.info("Terminating async processes")
|
||||
|
||||
@@asyncs.each do |async|
|
||||
Pups.log.info("Sending #{async[:stop_signal]} to #{async[:command]} pid: #{async[:pid]}")
|
||||
Process.kill(async[:stop_signal],async[:pid]) rescue nil
|
||||
@@asyncs.each do |async|
|
||||
Pups.log.info(
|
||||
"Sending #{async[:stop_signal]} to #{async[:command]} pid: #{async[:pid]}"
|
||||
)
|
||||
begin
|
||||
Process.kill(async[:stop_signal], async[:pid])
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@asyncs
|
||||
.map do |async|
|
||||
Thread.new do
|
||||
Timeout.timeout(opts[:wait] || 10) do
|
||||
Process.wait(async[:pid])
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
rescue Timeout::Error
|
||||
Pups.log.info(
|
||||
"#{async[:command]} pid:#{async[:pid]} did not terminate cleanly, forcing termination!"
|
||||
)
|
||||
begin
|
||||
Process.kill("KILL", async[:pid])
|
||||
Process.wait(async[:pid])
|
||||
rescue Errno::ESRCH
|
||||
rescue Errno::ECHILD
|
||||
end
|
||||
end
|
||||
end
|
||||
.each(&:join)
|
||||
end
|
||||
|
||||
@@asyncs.map do |async|
|
||||
Thread.new do
|
||||
begin
|
||||
Timeout.timeout(opts[:wait] || 10) do
|
||||
Process.wait(async[:pid]) rescue nil
|
||||
end
|
||||
rescue Timeout::Error
|
||||
Pups.log.info("#{async[:command]} pid:#{async[:pid]} did not terminate cleanly, forcing termination!")
|
||||
def self.from_hash(hash, params)
|
||||
cmd = new(params, hash["cd"])
|
||||
|
||||
case c = hash["cmd"]
|
||||
when String
|
||||
cmd.add(c)
|
||||
when Array
|
||||
c.each { |i| cmd.add(i) }
|
||||
end
|
||||
|
||||
cmd.background = hash["background"]
|
||||
cmd.stop_signal = hash["stop_signal"] || "TERM"
|
||||
cmd.raise_on_fail = hash["raise_on_fail"] if hash.key? "raise_on_fail"
|
||||
cmd.stdin = interpolate_params(hash["stdin"], params)
|
||||
|
||||
cmd
|
||||
end
|
||||
|
||||
def self.from_str(str, params)
|
||||
cmd = new(params)
|
||||
cmd.add(str)
|
||||
cmd
|
||||
end
|
||||
|
||||
def initialize(params, cd = nil)
|
||||
@commands = []
|
||||
@params = params
|
||||
@cd = interpolate_params(cd)
|
||||
@raise_on_fail = true
|
||||
end
|
||||
|
||||
def add(cmd)
|
||||
@commands << process_params(cmd)
|
||||
end
|
||||
|
||||
def run
|
||||
commands.each do |command|
|
||||
Pups.log.info("> #{command}")
|
||||
pid = spawn(command)
|
||||
Pups.log.info(@result.readlines.join("\n")) if @result
|
||||
end
|
||||
rescue StandardError
|
||||
raise if @raise_on_fail
|
||||
end
|
||||
|
||||
def spawn(command)
|
||||
if background
|
||||
pid = Process.spawn(command)
|
||||
(@@asyncs ||= []) << {
|
||||
pid: pid,
|
||||
command: command,
|
||||
stop_signal: (stop_signal || "TERM")
|
||||
}
|
||||
Thread.new do
|
||||
begin
|
||||
Process.kill("KILL",async[:pid])
|
||||
Process.wait(async[:pid])
|
||||
rescue Errno::ESRCH
|
||||
Process.wait(pid)
|
||||
rescue Errno::ECHILD
|
||||
# already exited so skip
|
||||
end
|
||||
@@asyncs.delete_if { |async| async[:pid] == pid }
|
||||
end
|
||||
return pid
|
||||
end
|
||||
|
||||
IO.popen(command, "w+") do |f|
|
||||
if stdin
|
||||
# need a way to get stdout without blocking
|
||||
Pups.log.info(stdin)
|
||||
f.write stdin
|
||||
f.close
|
||||
else
|
||||
Pups.log.info(f.readlines.join)
|
||||
end
|
||||
end
|
||||
end.each(&:join)
|
||||
|
||||
end
|
||||
|
||||
def self.from_hash(hash, params)
|
||||
cmd = new(params, hash["cd"])
|
||||
|
||||
case c = hash["cmd"]
|
||||
when String then cmd.add(c)
|
||||
when Array then c.each{|i| cmd.add(i)}
|
||||
end
|
||||
|
||||
cmd.background = hash["background"]
|
||||
cmd.stop_signal = hash["stop_signal"] || "TERM"
|
||||
cmd.raise_on_fail = hash["raise_on_fail"] if hash.key? "raise_on_fail"
|
||||
cmd.stdin = interpolate_params(hash["stdin"], params)
|
||||
|
||||
cmd
|
||||
end
|
||||
|
||||
def self.from_str(str, params)
|
||||
cmd = new(params)
|
||||
cmd.add(str)
|
||||
cmd
|
||||
end
|
||||
|
||||
def initialize(params, cd = nil)
|
||||
@commands = []
|
||||
@params = params
|
||||
@cd = interpolate_params(cd)
|
||||
@raise_on_fail = true
|
||||
end
|
||||
|
||||
def add(cmd)
|
||||
@commands << process_params(cmd)
|
||||
end
|
||||
|
||||
def run
|
||||
commands.each do |command|
|
||||
Pups.log.info("> #{command}")
|
||||
pid = spawn(command)
|
||||
Pups.log.info(@result.readlines.join("\n")) if @result
|
||||
pid
|
||||
end
|
||||
rescue
|
||||
raise if @raise_on_fail
|
||||
end
|
||||
|
||||
def spawn(command)
|
||||
if background
|
||||
pid = Process.spawn(command)
|
||||
(@@asyncs ||= []) << {pid: pid, command: command, stop_signal: (stop_signal || "TERM")}
|
||||
Thread.new do
|
||||
begin
|
||||
Process.wait(pid)
|
||||
rescue Errno::ECHILD
|
||||
# already exited so skip
|
||||
end
|
||||
@@asyncs.delete_if{|async| async[:pid] == pid}
|
||||
unless $CHILD_STATUS == 0
|
||||
err =
|
||||
Pups::ExecError.new(
|
||||
"#{command} failed with return #{$CHILD_STATUS.inspect}"
|
||||
)
|
||||
err.exit_code = $CHILD_STATUS.exitstatus
|
||||
raise err
|
||||
end
|
||||
return pid
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
IO.popen(command, "w+") do |f|
|
||||
if stdin
|
||||
# need a way to get stdout without blocking
|
||||
Pups.log.info(stdin)
|
||||
f.write stdin
|
||||
f.close
|
||||
else
|
||||
Pups.log.info(f.readlines.join)
|
||||
end
|
||||
def process_params(cmd)
|
||||
processed = interpolate_params(cmd)
|
||||
@cd ? "cd #{cd} && #{processed}" : processed
|
||||
end
|
||||
|
||||
unless $? == 0
|
||||
err = Pups::ExecError.new("#{command} failed with return #{$?.inspect}")
|
||||
err.exit_code = $?.exitstatus
|
||||
raise err
|
||||
end
|
||||
|
||||
nil
|
||||
|
||||
end
|
||||
|
||||
def process_params(cmd)
|
||||
processed = interpolate_params(cmd)
|
||||
@cd ? "cd #{cd} && #{processed}" : processed
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,37 +1,33 @@
|
|||
class Pups::FileCommand < Pups::Command
|
||||
attr_accessor :path, :contents, :params, :type, :chmod
|
||||
# frozen_string_literal: true
|
||||
|
||||
def self.from_hash(hash, params)
|
||||
command = new
|
||||
command.path = hash["path"]
|
||||
command.contents = hash["contents"]
|
||||
command.chmod = hash["chmod"]
|
||||
command.params = params
|
||||
module Pups
|
||||
class FileCommand < Pups::Command
|
||||
attr_accessor :path, :contents, :params, :type, :chmod, :chown
|
||||
|
||||
command
|
||||
end
|
||||
def self.from_hash(hash, params)
|
||||
command = new
|
||||
command.path = hash["path"]
|
||||
command.contents = hash["contents"]
|
||||
command.chmod = hash["chmod"]
|
||||
command.chown = hash["chown"]
|
||||
command.params = params
|
||||
|
||||
def initialize
|
||||
@params = {}
|
||||
@type = :bash
|
||||
end
|
||||
|
||||
def params=(p)
|
||||
@params = p
|
||||
end
|
||||
|
||||
def run
|
||||
path = interpolate_params(@path)
|
||||
|
||||
`mkdir -p #{File.dirname(path)}`
|
||||
File.open(path, "w") do |f|
|
||||
f.write(interpolate_params(contents))
|
||||
command
|
||||
end
|
||||
if @chmod
|
||||
`chmod #{@chmod} #{path}`
|
||||
end
|
||||
Pups.log.info("File > #{path} chmod: #{@chmod}")
|
||||
end
|
||||
|
||||
def initialize
|
||||
@params = {}
|
||||
@type = :bash
|
||||
end
|
||||
|
||||
def run
|
||||
path = interpolate_params(@path)
|
||||
|
||||
`mkdir -p #{File.dirname(path)}`
|
||||
File.open(path, "w") { |f| f.write(interpolate_params(contents)) }
|
||||
`chmod #{@chmod} #{path}` if @chmod
|
||||
`chown #{@chown} #{path}` if @chown
|
||||
Pups.log.info("File > #{path} chmod: #{@chmod} chown: #{@chown}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,48 +1,53 @@
|
|||
class Pups::MergeCommand < Pups::Command
|
||||
attr_reader :filename
|
||||
attr_reader :merge_hash
|
||||
# frozen_string_literal: true
|
||||
|
||||
def self.from_str(command, params)
|
||||
new(command,params)
|
||||
end
|
||||
module Pups
|
||||
class MergeCommand < Pups::Command
|
||||
attr_reader :filename, :merge_hash
|
||||
|
||||
def self.parse_command(command)
|
||||
split = command.split(" ")
|
||||
raise ArgumentError.new("Invalid merge command #{command}") unless split[-1][0] == "$"
|
||||
def self.from_str(command, params)
|
||||
new(command, params)
|
||||
end
|
||||
|
||||
[split[0..-2].join(" ") , split[-1][1..-1]]
|
||||
end
|
||||
|
||||
def initialize(command, params)
|
||||
@params = params
|
||||
|
||||
filename, target_param = Pups::MergeCommand.parse_command(command)
|
||||
@filename = interpolate_params(filename)
|
||||
@merge_hash = params[target_param]
|
||||
end
|
||||
|
||||
def run
|
||||
merged = self.class.deep_merge(YAML.load_file(@filename), @merge_hash)
|
||||
File.open(@filename,"w"){|f| f.write(merged.to_yaml) }
|
||||
Pups.log.info("Merge: #{@filename} with: \n#{@merge_hash.inspect}")
|
||||
end
|
||||
|
||||
def self.deep_merge(first,second, *args)
|
||||
args ||= []
|
||||
merge_arrays = args.include? :merge_arrays
|
||||
|
||||
merger = proc { |key, v1, v2|
|
||||
if Hash === v1 && Hash === v2
|
||||
v1.merge(v2, &merger)
|
||||
elsif Array === v1 && Array === v2
|
||||
merge_arrays ? v1 + v2 : v2
|
||||
elsif NilClass === v2
|
||||
v1
|
||||
else
|
||||
v2
|
||||
def self.parse_command(command)
|
||||
split = command.split(" ")
|
||||
unless split[-1][0] == "$"
|
||||
raise ArgumentError, "Invalid merge command #{command}"
|
||||
end
|
||||
}
|
||||
first.merge(second, &merger)
|
||||
end
|
||||
|
||||
[split[0..-2].join(" "), split[-1][1..-1]]
|
||||
end
|
||||
|
||||
def initialize(command, params)
|
||||
@params = params
|
||||
|
||||
filename, target_param = Pups::MergeCommand.parse_command(command)
|
||||
@filename = interpolate_params(filename)
|
||||
@merge_hash = params[target_param]
|
||||
end
|
||||
|
||||
def run
|
||||
merged = self.class.deep_merge(YAML.load_file(@filename), @merge_hash)
|
||||
File.open(@filename, "w") { |f| f.write(merged.to_yaml) }
|
||||
Pups.log.info("Merge: #{@filename} with: \n#{@merge_hash.inspect}")
|
||||
end
|
||||
|
||||
def self.deep_merge(first, second, *args)
|
||||
args ||= []
|
||||
merge_arrays = args.include? :merge_arrays
|
||||
|
||||
merger =
|
||||
proc do |_key, v1, v2|
|
||||
if v1.is_a?(Hash) && v2.is_a?(Hash)
|
||||
v1.merge(v2, &merger)
|
||||
elsif v1.is_a?(Array) && v2.is_a?(Array)
|
||||
merge_arrays ? v1 + v2 : v2
|
||||
elsif v2.is_a?(NilClass)
|
||||
v1
|
||||
else
|
||||
v2
|
||||
end
|
||||
end
|
||||
first.merge(second, &merger)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,43 +1,45 @@
|
|||
class Pups::ReplaceCommand < Pups::Command
|
||||
attr_accessor :text, :from, :to, :filename, :direction, :global
|
||||
# frozen_string_literal: true
|
||||
|
||||
def self.from_hash(hash, params)
|
||||
replacer = new(params)
|
||||
replacer.from = guess_replace_type(hash["from"])
|
||||
replacer.to = guess_replace_type(hash["to"])
|
||||
replacer.text = File.read(hash["filename"])
|
||||
replacer.filename = hash["filename"]
|
||||
replacer.direction = hash["direction"].to_sym if hash["direction"]
|
||||
replacer.global = hash["global"].to_s == "true"
|
||||
replacer
|
||||
end
|
||||
module Pups
|
||||
class ReplaceCommand < Pups::Command
|
||||
attr_accessor :text, :from, :to, :filename, :direction, :global
|
||||
|
||||
def self.guess_replace_type(item)
|
||||
# evaling to get all the regex flags easily
|
||||
item[0] == "/" ? eval(item) : item
|
||||
end
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def replaced_text
|
||||
new_to = to
|
||||
if String === to
|
||||
new_to = interpolate_params(to)
|
||||
def self.from_hash(hash, params)
|
||||
replacer = new(params)
|
||||
replacer.from = guess_replace_type(hash["from"])
|
||||
replacer.to = guess_replace_type(hash["to"])
|
||||
replacer.text = File.read(hash["filename"])
|
||||
replacer.filename = hash["filename"]
|
||||
replacer.direction = hash["direction"].to_sym if hash["direction"]
|
||||
replacer.global = hash["global"].to_s == "true"
|
||||
replacer
|
||||
end
|
||||
if global
|
||||
text.gsub(from,new_to)
|
||||
elsif direction == :reverse
|
||||
index = text.rindex(from)
|
||||
text[0..index-1] << text[index..-1].sub(from,new_to)
|
||||
else
|
||||
text.sub(from,new_to)
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
Pups.log.info("Replacing #{from.to_s} with #{to.to_s} in #{filename}")
|
||||
File.open(filename, "w"){|f| f.write replaced_text }
|
||||
def self.guess_replace_type(item)
|
||||
# evaling to get all the regex flags easily
|
||||
item[0] == "/" ? eval(item) : item # rubocop:disable Security/Eval
|
||||
end
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def replaced_text
|
||||
new_to = to
|
||||
new_to = interpolate_params(to) if to.is_a?(String)
|
||||
if global
|
||||
text.gsub(from, new_to)
|
||||
elsif direction == :reverse
|
||||
index = text.rindex(from)
|
||||
text[0..index - 1] << text[index..-1].sub(from, new_to)
|
||||
else
|
||||
text.sub(from, new_to)
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
Pups.log.info("Replacing #{from} with #{to} in #{filename}")
|
||||
File.open(filename, "w") { |f| f.write replaced_text }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,40 +1,35 @@
|
|||
class Pups::Runit
|
||||
# frozen_string_literal: true
|
||||
|
||||
attr_accessor :env, :exec, :cd, :name
|
||||
module Pups
|
||||
class Runit
|
||||
attr_accessor :env, :exec, :cd, :name
|
||||
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def setup
|
||||
`mkdir -p /etc/service/#{name}`
|
||||
run = "/etc/service/#{name}/run"
|
||||
File.open(run, "w") do |f|
|
||||
f.write(run_script)
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
`chmod +x #{run}`
|
||||
end
|
||||
|
||||
def run_script
|
||||
"#!/bin/bash
|
||||
def setup
|
||||
`mkdir -p /etc/service/#{name}`
|
||||
run = "/etc/service/#{name}/run"
|
||||
File.open(run, "w") { |f| f.write(run_script) }
|
||||
`chmod +x #{run}`
|
||||
end
|
||||
|
||||
def run_script
|
||||
"#!/bin/bash
|
||||
exec 2>&1
|
||||
#{env_script}
|
||||
#{cd_script}
|
||||
#{exec}
|
||||
"
|
||||
end
|
||||
end
|
||||
|
||||
def cd_script
|
||||
"cd #{@cd}" if @cd
|
||||
end
|
||||
def cd_script
|
||||
"cd #{@cd}" if @cd
|
||||
end
|
||||
|
||||
def env_script
|
||||
if @env
|
||||
@env.map do |k,v|
|
||||
"export #{k}=#{v}"
|
||||
end.join("\n")
|
||||
def env_script
|
||||
@env&.map { |k, v| "export #{k}=#{v}" }&.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Pups
|
||||
VERSION = "1.0.2"
|
||||
VERSION = "1.3.0"
|
||||
end
|
||||
|
|
39
pups.gemspec
39
pups.gemspec
|
@ -1,26 +1,31 @@
|
|||
# coding: utf-8
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
# frozen_string_literal: true
|
||||
|
||||
lib = File.expand_path('lib', __dir__)
|
||||
$LOAD_PATH.unshift(lib) if !$LOAD_PATH.include?(lib)
|
||||
require 'pups/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "pups"
|
||||
spec.name = 'pups'
|
||||
spec.version = Pups::VERSION
|
||||
spec.authors = ["Sam Saffron"]
|
||||
spec.email = ["sam.saffron@gmail.com"]
|
||||
spec.description = %q{Process orchestrator}
|
||||
spec.summary = %q{Process orchestrator}
|
||||
spec.homepage = ""
|
||||
spec.license = "MIT"
|
||||
spec.authors = ['Sam Saffron']
|
||||
spec.email = ['sam.saffron@gmail.com']
|
||||
spec.description = 'Simple docker image creator'
|
||||
spec.summary = 'Toolkit for orchestrating a composed docker image'
|
||||
spec.homepage = ''
|
||||
spec.license = 'MIT'
|
||||
|
||||
spec.files = `git ls-files`.split($/)
|
||||
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
||||
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
||||
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
||||
spec.require_paths = ["lib"]
|
||||
spec.require_paths = ['lib']
|
||||
|
||||
spec.add_development_dependency "bundler", "~> 1.3"
|
||||
spec.add_development_dependency "rake"
|
||||
spec.add_development_dependency "minitest"
|
||||
spec.add_development_dependency "guard"
|
||||
spec.add_development_dependency "guard-minitest"
|
||||
spec.add_development_dependency 'bundler'
|
||||
spec.add_development_dependency 'guard'
|
||||
spec.add_development_dependency 'guard-minitest'
|
||||
spec.add_development_dependency 'minitest'
|
||||
spec.add_development_dependency 'rake'
|
||||
spec.add_development_dependency 'rubocop'
|
||||
spec.add_development_dependency 'rubocop-discourse'
|
||||
spec.add_development_dependency 'rubocop-minitest'
|
||||
spec.add_development_dependency 'rubocop-rake'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "tempfile"
|
||||
require "stringio"
|
||||
|
||||
module Pups
|
||||
class CliTest < ::Minitest::Test
|
||||
def test_cli_option_parsing_stdin
|
||||
options = Cli.parse_args(["--stdin"])
|
||||
assert_equal(true, options[:stdin])
|
||||
end
|
||||
|
||||
def test_cli_option_parsing_none
|
||||
options = Cli.parse_args([])
|
||||
assert_nil(options[:stdin])
|
||||
end
|
||||
|
||||
def test_cli_read_config_from_file
|
||||
# for testing output
|
||||
f = Tempfile.new("test_output")
|
||||
f.close
|
||||
|
||||
# for testing input
|
||||
cf = Tempfile.new("test_config")
|
||||
cf.puts <<~YAML
|
||||
params:
|
||||
run: #{f.path}
|
||||
run:
|
||||
- exec: echo hello world >> #{f.path}
|
||||
YAML
|
||||
cf.close
|
||||
|
||||
Cli.run([cf.path])
|
||||
assert_equal("hello world", File.read(f.path).strip)
|
||||
end
|
||||
|
||||
def test_cli_ignore_config_element
|
||||
# for testing output
|
||||
f = Tempfile.new("test_output")
|
||||
f.close
|
||||
|
||||
# for testing input
|
||||
cf = Tempfile.new("test_config")
|
||||
cf.puts <<~YAML
|
||||
env:
|
||||
MY_IGNORED_VAR: a_word
|
||||
params:
|
||||
a_param_var: another_word
|
||||
run:
|
||||
- exec: echo repeating $MY_IGNORED_VAR and also $a_param_var >> #{f.path}
|
||||
YAML
|
||||
cf.close
|
||||
|
||||
Cli.run(["--ignore", "env,params", cf.path])
|
||||
assert_equal("repeating and also", File.read(f.path).strip)
|
||||
end
|
||||
|
||||
def test_cli_gen_docker_run_args_ignores_other_config
|
||||
# When generating the docker run arguments it should ignore other template configuration
|
||||
# like 'run' directives.
|
||||
|
||||
# for testing output
|
||||
f = Tempfile.new("test_output")
|
||||
f.close
|
||||
|
||||
# for testing input
|
||||
cf = Tempfile.new("test_config")
|
||||
cf.puts <<~YAML
|
||||
env:
|
||||
foo: 1
|
||||
bar: 5
|
||||
baz: 'hello_{{spam}}'
|
||||
env_template:
|
||||
spam: 'eggs'
|
||||
config: my_app
|
||||
params:
|
||||
run: #{f.path}
|
||||
run:
|
||||
- exec: echo hello world >> #{f.path}
|
||||
expose:
|
||||
- "2222:22"
|
||||
- "127.0.0.1:20080:80"
|
||||
- 5555
|
||||
volumes:
|
||||
- volume:
|
||||
host: /var/discourse/shared
|
||||
guest: /shared
|
||||
- volume:
|
||||
host: /bar
|
||||
guest: /baz
|
||||
links:
|
||||
- link:
|
||||
name: postgres
|
||||
alias: postgres
|
||||
- link:
|
||||
name: foo
|
||||
alias: bar
|
||||
labels:
|
||||
monitor: "true"
|
||||
app_name: "{{config}}_discourse"
|
||||
YAML
|
||||
cf.close
|
||||
|
||||
expected = []
|
||||
expected << "--env foo=1 --env bar=5 --env baz=hello_eggs"
|
||||
expected << "--publish 2222:22 --publish 127.0.0.1:20080:80 --expose 5555"
|
||||
expected << "--volume /var/discourse/shared:/shared --volume /bar:/baz"
|
||||
expected << "--link postgres:postgres --link foo:bar"
|
||||
expected << "--label monitor=true --label app_name=my_app_discourse"
|
||||
expected.sort!
|
||||
|
||||
assert_equal("", File.read(f.path).strip)
|
||||
assert_output(expected.join(" ")) do
|
||||
Cli.run(["--gen-docker-run-args", cf.path])
|
||||
end
|
||||
end
|
||||
|
||||
def test_cli_tags
|
||||
# for testing output
|
||||
f = Tempfile.new("test_output")
|
||||
f.close
|
||||
|
||||
# for testing input
|
||||
cf = Tempfile.new("test_config")
|
||||
cf.puts <<~YAML
|
||||
run:
|
||||
- exec:
|
||||
tag: '1'
|
||||
cmd: echo 1 >> #{f.path}
|
||||
- exec:
|
||||
tag: '2'
|
||||
cmd: echo 2 >> #{f.path}
|
||||
- exec:
|
||||
tag: '3'
|
||||
cmd: echo 3 >> #{f.path}
|
||||
YAML
|
||||
cf.close
|
||||
|
||||
Cli.run(["--tags", "1,3", cf.path])
|
||||
assert_equal("1\n3", File.read(f.path).strip)
|
||||
end
|
||||
|
||||
def test_cli_skip_tags
|
||||
# for testing output
|
||||
f = Tempfile.new("test_output")
|
||||
f.close
|
||||
|
||||
# for testing input
|
||||
cf = Tempfile.new("test_config")
|
||||
cf.puts <<~YAML
|
||||
run:
|
||||
- exec:
|
||||
tag: '1'
|
||||
cmd: echo 1 >> #{f.path}
|
||||
- exec:
|
||||
tag: '2'
|
||||
cmd: echo 2 >> #{f.path}
|
||||
- exec:
|
||||
tag: '3'
|
||||
cmd: echo 3 >> #{f.path}
|
||||
YAML
|
||||
cf.close
|
||||
|
||||
Cli.run(["--skip-tags", "1,3", cf.path])
|
||||
assert_equal("2", File.read(f.path).strip)
|
||||
end
|
||||
|
||||
def test_cli_params
|
||||
# for testing output
|
||||
f = Tempfile.new("test_output")
|
||||
f.close
|
||||
|
||||
# for testing input
|
||||
cf = Tempfile.new("test_config")
|
||||
cf.puts <<~YAML
|
||||
params:
|
||||
one: 0
|
||||
two: 0
|
||||
run:
|
||||
- exec:
|
||||
cmd: echo $one >> #{f.path}
|
||||
- exec:
|
||||
cmd: echo $two >> #{f.path}
|
||||
YAML
|
||||
cf.close
|
||||
|
||||
Cli.run(["--params", "one=1,two=2", cf.path])
|
||||
assert_equal("1\n2", File.read(f.path).strip)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,55 +1,297 @@
|
|||
require 'test_helper'
|
||||
require 'tempfile'
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "tempfile"
|
||||
|
||||
module Pups
|
||||
class ConfigTest < MiniTest::Test
|
||||
|
||||
class ConfigTest < ::Minitest::Test
|
||||
def test_config_from_env
|
||||
ENV["HELLO"] = "world"
|
||||
config = Config.new({})
|
||||
assert_equal("world", config.params["$ENV_HELLO"])
|
||||
end
|
||||
|
||||
def test_integration
|
||||
def test_env_param
|
||||
ENV["FOO"] = "BAR"
|
||||
config = <<~YAML
|
||||
env:
|
||||
BAR: baz
|
||||
hello: WORLD
|
||||
one: 1
|
||||
YAML
|
||||
|
||||
config = Config.new(YAML.safe_load(config))
|
||||
%w[BAR hello one].each { |e| ENV.delete(e) }
|
||||
assert_equal("BAR", config.params["$ENV_FOO"])
|
||||
assert_equal("baz", config.params["$ENV_BAR"])
|
||||
assert_equal("WORLD", config.params["$ENV_hello"])
|
||||
assert_equal("1", config.params["$ENV_one"])
|
||||
end
|
||||
|
||||
def test_env_with_template
|
||||
ENV["FOO"] = "BAR"
|
||||
config = <<~YAML
|
||||
env:
|
||||
greeting: "{{hello}}, {{planet}}!"
|
||||
one: 1
|
||||
other: "where are we on {{planet}}?"
|
||||
env_template:
|
||||
planet: pluto
|
||||
hello: hola
|
||||
YAML
|
||||
config_hash = YAML.safe_load(config)
|
||||
|
||||
config = Config.new(config_hash)
|
||||
%w[greeting one other].each { |e| ENV.delete(e) }
|
||||
assert_equal("hola, pluto!", config.params["$ENV_greeting"])
|
||||
assert_equal("1", config.params["$ENV_one"])
|
||||
assert_equal("BAR", config.params["$ENV_FOO"])
|
||||
assert_equal("where are we on pluto?", config.params["$ENV_other"])
|
||||
end
|
||||
|
||||
def test_label_with_template
|
||||
ENV["FOO"] = "BAR"
|
||||
config = <<~YAML
|
||||
env:
|
||||
greeting: "{{hello}}, {{planet}}!"
|
||||
one: 1
|
||||
other: "where are we on {{planet}}?"
|
||||
env_template:
|
||||
planet: pluto
|
||||
hello: hola
|
||||
config: various
|
||||
labels:
|
||||
app_name: "{{config}}_discourse"
|
||||
YAML
|
||||
config_hash = YAML.load(config)
|
||||
|
||||
config = Config.new(config_hash)
|
||||
%w[greeting one other].each { |e| ENV.delete(e) }
|
||||
assert_equal("various_discourse", config.config["labels"]["app_name"])
|
||||
end
|
||||
|
||||
def test_env_with_ENV_templated_variable
|
||||
ENV["env_template_config"] = "my_application"
|
||||
config = <<~YAML
|
||||
env:
|
||||
greeting: "{{hello}}, {{planet}}!"
|
||||
one: 1
|
||||
other: "building {{config}}"
|
||||
env_template:
|
||||
planet: pluto
|
||||
hello: hola
|
||||
YAML
|
||||
config_hash = YAML.safe_load(config)
|
||||
|
||||
config = Config.new(config_hash)
|
||||
%w[greeting one other].each { |e| ENV.delete(e) }
|
||||
assert_equal("hola, pluto!", config.params["$ENV_greeting"])
|
||||
assert_equal("1", config.params["$ENV_one"])
|
||||
assert_equal("building my_application", config.params["$ENV_other"])
|
||||
ENV["env_template_config"] = nil
|
||||
end
|
||||
|
||||
def test_integration
|
||||
f = Tempfile.new("test")
|
||||
f.close
|
||||
|
||||
config = <<YAML
|
||||
params:
|
||||
run: #{f.path}
|
||||
run:
|
||||
- exec: echo hello world >> #{f.path}
|
||||
YAML
|
||||
config = <<~YAML
|
||||
env:
|
||||
PLANET: world
|
||||
params:
|
||||
run: #{f.path}
|
||||
greeting: hello
|
||||
run:
|
||||
- exec: echo $greeting $PLANET >> #{f.path}
|
||||
YAML
|
||||
|
||||
Config.new(YAML.load(config)).run
|
||||
Config.new(YAML.safe_load(config)).run
|
||||
ENV.delete("PLANET")
|
||||
assert_equal("hello world", File.read(f.path).strip)
|
||||
|
||||
ensure
|
||||
f.unlink
|
||||
end
|
||||
|
||||
def test_hooks
|
||||
yaml = <<YAML
|
||||
run:
|
||||
- exec: 1
|
||||
- exec:
|
||||
hook: middle
|
||||
cmd: 2
|
||||
- exec: 3
|
||||
hooks:
|
||||
after_middle:
|
||||
- exec: 2.1
|
||||
before_middle:
|
||||
- exec: 1.9
|
||||
YAML
|
||||
yaml = <<~YAML
|
||||
run:
|
||||
- exec: 1
|
||||
- exec:
|
||||
hook: middle
|
||||
cmd: 2
|
||||
- exec: 3
|
||||
hooks:
|
||||
after_middle:
|
||||
- exec: 2.1
|
||||
before_middle:
|
||||
- exec: 1.9
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml).config
|
||||
assert_equal({ "exec" => 1.9 }, config["run"][1])
|
||||
assert_equal({ "exec" => 2.1 }, config["run"][3])
|
||||
end
|
||||
|
||||
def test_ignored_elements
|
||||
f = Tempfile.new("test")
|
||||
f.close
|
||||
|
||||
yaml = <<~YAML
|
||||
env:
|
||||
PLANET: world
|
||||
params:
|
||||
greeting: hello
|
||||
run:
|
||||
- exec: 1
|
||||
- exec:
|
||||
hook: middle
|
||||
cmd: 2
|
||||
- exec: 3
|
||||
- exec: echo $greeting $PLANET >> #{f.path}
|
||||
hooks:
|
||||
after_middle:
|
||||
- exec: 2.1
|
||||
before_middle:
|
||||
- exec: 1.9
|
||||
YAML
|
||||
|
||||
conf = Config.load_config(yaml, %w[hooks params])
|
||||
config = conf.config
|
||||
assert_equal({ "exec" => 1 }, config["run"][0])
|
||||
assert_equal(
|
||||
{ "exec" => { "hook" => "middle", "cmd" => 2 } },
|
||||
config["run"][1]
|
||||
)
|
||||
assert_equal({ "exec" => 3 }, config["run"][2])
|
||||
assert_equal(
|
||||
{ "exec" => "echo $greeting $PLANET >> #{f.path}" },
|
||||
config["run"][3]
|
||||
)
|
||||
|
||||
# $greet from params will be an empty var as it was ignored
|
||||
conf.run
|
||||
ENV.delete("PLANET")
|
||||
assert_equal("world", File.read(f.path).strip)
|
||||
end
|
||||
|
||||
def test_generate_docker_run_arguments
|
||||
yaml = <<~YAML
|
||||
env:
|
||||
foo: 1
|
||||
bar: 2
|
||||
baz: 'hello_{{spam}}'
|
||||
env_template:
|
||||
spam: 'eggs'
|
||||
config: my_app
|
||||
expose:
|
||||
- "2222:22"
|
||||
- "127.0.0.1:20080:80"
|
||||
- 5555
|
||||
volumes:
|
||||
- volume:
|
||||
host: /var/discourse/shared
|
||||
guest: /shared
|
||||
- volume:
|
||||
host: /bar
|
||||
guest: /baz
|
||||
links:
|
||||
- link:
|
||||
name: postgres
|
||||
alias: postgres
|
||||
- link:
|
||||
name: foo
|
||||
alias: bar
|
||||
labels:
|
||||
monitor: "true"
|
||||
app_name: "{{config}}_discourse"
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
args = config.generate_docker_run_arguments
|
||||
|
||||
expected = []
|
||||
expected << "--env foo=1 --env bar=2 --env baz=hello_eggs"
|
||||
expected << "--publish 2222:22 --publish 127.0.0.1:20080:80 --expose 5555"
|
||||
expected << "--volume /var/discourse/shared:/shared --volume /bar:/baz"
|
||||
expected << "--link postgres:postgres --link foo:bar"
|
||||
expected << "--label monitor=true --label app_name=my_app_discourse"
|
||||
expected.sort!
|
||||
|
||||
assert_equal(expected.join(" "), args)
|
||||
end
|
||||
|
||||
def test_tag_filtering
|
||||
f = Tempfile.new("test")
|
||||
f.close
|
||||
|
||||
yaml = <<~YAML
|
||||
run:
|
||||
- exec: 1
|
||||
- exec:
|
||||
hook: middle
|
||||
cmd: 2
|
||||
tag: one_tag
|
||||
- exec:
|
||||
cmd: 3
|
||||
tag: two_tag
|
||||
hooks:
|
||||
after_middle:
|
||||
- exec: 2.1
|
||||
before_middle:
|
||||
- exec: 1.9
|
||||
YAML
|
||||
|
||||
# No tagging loads everything
|
||||
conf = Config.load_config(yaml)
|
||||
config = conf.config
|
||||
assert_equal({ "exec" => 1 }, config["run"][0])
|
||||
assert_equal({ "exec" => 1.9 }, config["run"][1])
|
||||
assert_equal(
|
||||
{ "exec" => { "hook" => "middle", "cmd" => 2, "tag" => "one_tag" } },
|
||||
config["run"][2]
|
||||
)
|
||||
assert_equal({ "exec" => 2.1 }, config["run"][3])
|
||||
assert_equal(
|
||||
{ "exec" => { "cmd" => 3, "tag" => "two_tag" } },
|
||||
config["run"][4]
|
||||
)
|
||||
|
||||
# hooks get applied if hook command is not filtered
|
||||
conf = Config.load_config(yaml, tags: ["one_tag"])
|
||||
config = conf.config
|
||||
assert_equal({ "exec" => 1.9 }, config["run"][0])
|
||||
assert_equal(
|
||||
{ "exec" => { "hook" => "middle", "cmd" => 2, "tag" => "one_tag" } },
|
||||
config["run"][1]
|
||||
)
|
||||
assert_equal({ "exec" => 2.1 }, config["run"][2])
|
||||
|
||||
# hooks get filtered out if the main hook command is filtered
|
||||
conf = Config.load_config(yaml, tags: ["two_tag"])
|
||||
config = conf.config
|
||||
assert_equal(
|
||||
{ "exec" => { "cmd" => 3, "tag" => "two_tag" } },
|
||||
config["run"][0]
|
||||
)
|
||||
|
||||
# skip tags filter out commands with tags
|
||||
conf = Config.load_config(yaml, skip_tags: ["one_tag"])
|
||||
config = conf.config
|
||||
assert_equal({ "exec" => 1 }, config["run"][0])
|
||||
assert_equal(
|
||||
{ "exec" => { "cmd" => 3, "tag" => "two_tag" } },
|
||||
config["run"][1]
|
||||
)
|
||||
end
|
||||
|
||||
def test_extra_params
|
||||
config = <<~YAML
|
||||
params:
|
||||
one: 1
|
||||
YAML
|
||||
config = Config.new(YAML.safe_load(config), extra_params: %w[one=2 two=2])
|
||||
assert_equal("2", config.params["one"])
|
||||
assert_equal("2", config.params["two"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tempfile"
|
||||
require "shellwords"
|
||||
|
||||
module Pups
|
||||
class DockerTest < ::Minitest::Test
|
||||
def test_gen_env_arguments
|
||||
yaml = <<~YAML
|
||||
env:
|
||||
foo: 1
|
||||
bar: 2
|
||||
baz: 'hello_{{spam}}'
|
||||
env_template:
|
||||
spam: 'eggs'
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
Config.transform_config_with_templated_vars(
|
||||
config.config["env_template"],
|
||||
config.config["env"]
|
||||
)
|
||||
args = Docker.generate_env_arguments(config.config["env"])
|
||||
assert_equal("--env foo=1 --env bar=2 --env baz=hello_eggs", args)
|
||||
end
|
||||
|
||||
def test_gen_env_arguments_empty
|
||||
yaml = <<~YAML
|
||||
env:
|
||||
foo: 1
|
||||
bar: 2
|
||||
baz: ''
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
Config.transform_config_with_templated_vars(
|
||||
config.config["env_template"],
|
||||
config.config["env"]
|
||||
)
|
||||
args = Docker.generate_env_arguments(config.config["env"])
|
||||
assert_equal("--env foo=1 --env bar=2", args)
|
||||
end
|
||||
|
||||
def test_gen_env_arguments_escaped
|
||||
yaml = <<~YAML
|
||||
env:
|
||||
password: "{{spam}}*`echo`@e$t| = >>$()&list;#"
|
||||
env_template:
|
||||
spam: 'eggs'
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
Config.transform_config_with_templated_vars(
|
||||
config.config["env_template"],
|
||||
config.config["env"]
|
||||
)
|
||||
args = Docker.generate_env_arguments(config.config["env"])
|
||||
assert_equal(
|
||||
"--env password=#{Shellwords.escape("eggs*`echo`@e$t| = >>$()&list;#")}",
|
||||
args
|
||||
)
|
||||
end
|
||||
|
||||
def test_gen_env_arguments_quoted_with_a_space
|
||||
yaml = <<~YAML
|
||||
env:
|
||||
a_variable: here is a sentence
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
Config.transform_config_with_templated_vars(
|
||||
config.config["env_template"],
|
||||
config.config["env"]
|
||||
)
|
||||
args = Docker.generate_env_arguments(config.config["env"])
|
||||
assert_equal('--env a_variable=here\ is\ a\ sentence', args)
|
||||
end
|
||||
|
||||
def test_gen_env_arguments_newline
|
||||
pw = <<~PW
|
||||
this password is
|
||||
a weird one
|
||||
PW
|
||||
|
||||
yaml = <<~YAML
|
||||
env:
|
||||
password: "#{pw}"
|
||||
env_template:
|
||||
spam: 'eggs'
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
Config.transform_config_with_templated_vars(
|
||||
config.config["env_template"],
|
||||
config.config["env"]
|
||||
)
|
||||
args = Docker.generate_env_arguments(config.config["env"])
|
||||
assert_equal('--env password=this\ password\ is\ a\ weird\ one\ ', args)
|
||||
end
|
||||
|
||||
def test_gen_expose_arguments
|
||||
yaml = <<~YAML
|
||||
expose:
|
||||
- "2222:22"
|
||||
- "127.0.0.1:20080:80"
|
||||
- 5555
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
args = Docker.generate_expose_arguments(config.config["expose"])
|
||||
assert_equal(
|
||||
"--publish 2222:22 --publish 127.0.0.1:20080:80 --expose 5555",
|
||||
args
|
||||
)
|
||||
end
|
||||
|
||||
def test_gen_volume_arguments
|
||||
yaml = <<~YAML
|
||||
volumes:
|
||||
- volume:
|
||||
host: /var/discourse/shared
|
||||
guest: /shared
|
||||
- volume:
|
||||
host: /bar
|
||||
guest: /baz
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
args = Docker.generate_volume_arguments(config.config["volumes"])
|
||||
assert_equal(
|
||||
"--volume /var/discourse/shared:/shared --volume /bar:/baz",
|
||||
args
|
||||
)
|
||||
end
|
||||
|
||||
def test_gen_link_arguments
|
||||
yaml = <<~YAML
|
||||
links:
|
||||
- link:
|
||||
name: postgres
|
||||
alias: postgres
|
||||
- link:
|
||||
name: foo
|
||||
alias: bar
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
args = Docker.generate_link_arguments(config.config["links"])
|
||||
assert_equal("--link postgres:postgres --link foo:bar", args)
|
||||
end
|
||||
|
||||
def test_gen_label_arguments
|
||||
yaml = <<~YAML
|
||||
env_template:
|
||||
config: my_app
|
||||
labels:
|
||||
monitor: "true"
|
||||
app_name: "{{config}}_discourse"
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
Config.transform_config_with_templated_vars(
|
||||
config.config["env_template"],
|
||||
config.config["labels"]
|
||||
)
|
||||
args = Docker.generate_label_arguments(config.config["labels"])
|
||||
assert_equal(
|
||||
"--label monitor=true --label app_name=my_app_discourse",
|
||||
args
|
||||
)
|
||||
end
|
||||
|
||||
def test_gen_label_arguments_escaped
|
||||
yaml = <<~YAML
|
||||
labels:
|
||||
app_name: "{{config}}'s_di$course"
|
||||
env_template:
|
||||
config: my_app
|
||||
YAML
|
||||
|
||||
config = Config.load_config(yaml)
|
||||
Config.transform_config_with_templated_vars(
|
||||
config.config["env_template"],
|
||||
config.config["labels"]
|
||||
)
|
||||
args = Docker.generate_label_arguments(config.config["labels"])
|
||||
assert_equal(
|
||||
"--label app_name=#{Shellwords.escape("my_app's_di$course")}",
|
||||
args
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,52 +1,50 @@
|
|||
require 'test_helper'
|
||||
require 'tempfile'
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "tempfile"
|
||||
|
||||
module Pups
|
||||
class ExecCommandTest < MiniTest::Test
|
||||
|
||||
def from_str(str, params={})
|
||||
class ExecCommandTest < ::Minitest::Test
|
||||
def from_str(str, params = {})
|
||||
ExecCommand.from_str(str, params).commands
|
||||
end
|
||||
|
||||
def from_hash(hash, params={})
|
||||
def from_hash(hash, params = {})
|
||||
ExecCommand.from_hash(hash, params).commands
|
||||
end
|
||||
|
||||
def test_simple_str_command
|
||||
assert_equal(["do_something"],
|
||||
from_str("do_something"))
|
||||
assert_equal(["do_something"], from_str("do_something"))
|
||||
end
|
||||
|
||||
def test_simple_str_command_with_param
|
||||
assert_equal(["hello world"],
|
||||
from_str("hello $bob", {"bob" => "world"}))
|
||||
assert_equal(
|
||||
["hello world"],
|
||||
from_str("hello $bob", { "bob" => "world" })
|
||||
)
|
||||
end
|
||||
|
||||
def test_nested_command
|
||||
assert_equal(["first"],
|
||||
from_hash("cmd" => "first"))
|
||||
assert_equal(["first"], from_hash("cmd" => "first"))
|
||||
end
|
||||
|
||||
def test_multi_commands
|
||||
assert_equal(["first","second"],
|
||||
from_hash("cmd" => ["first","second"]))
|
||||
assert_equal(%w[first second], from_hash("cmd" => %w[first second]))
|
||||
end
|
||||
|
||||
def test_multi_commands_with_home
|
||||
assert_equal(["cd /home/sam && first",
|
||||
"cd /home/sam && second"],
|
||||
from_hash("cmd" => ["first","second"],
|
||||
"cd" => "/home/sam"))
|
||||
assert_equal(
|
||||
["cd /home/sam && first", "cd /home/sam && second"],
|
||||
from_hash("cmd" => %w[first second], "cd" => "/home/sam")
|
||||
)
|
||||
end
|
||||
|
||||
def test_exec_works
|
||||
ExecCommand.from_str("ls",{}).run
|
||||
ExecCommand.from_str("ls", {}).run
|
||||
end
|
||||
|
||||
def test_fails_for_bad_command
|
||||
assert_raises(Errno::ENOENT) do
|
||||
ExecCommand.from_str("boom",{}).run
|
||||
end
|
||||
assert_raises(Errno::ENOENT) { ExecCommand.from_str("boom", {}).run }
|
||||
end
|
||||
|
||||
def test_backgroud_task_do_not_fail
|
||||
|
@ -64,7 +62,6 @@ module Pups
|
|||
end
|
||||
|
||||
def test_stdin
|
||||
|
||||
`touch test_file`
|
||||
cmd = ExecCommand.new({})
|
||||
cmd.add("read test ; echo $test > test_file")
|
||||
|
@ -72,42 +69,34 @@ module Pups
|
|||
cmd.run
|
||||
|
||||
assert_equal("hello\n", File.read("test_file"))
|
||||
|
||||
ensure
|
||||
File.delete("test_file")
|
||||
end
|
||||
|
||||
def test_fails_for_non_zero_exit
|
||||
assert_raises(Pups::ExecError) do
|
||||
ExecCommand.from_str("chgrp -a",{}).run
|
||||
ExecCommand.from_str("chgrp -a", {}).run
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_can_terminate_async
|
||||
cmd = ExecCommand.new({})
|
||||
cmd.background = true
|
||||
pid = cmd.spawn("sleep 10 && exit 1")
|
||||
ExecCommand.terminate_async
|
||||
assert_raises(Errno::ECHILD) do
|
||||
Process.waitpid(pid,Process::WNOHANG)
|
||||
end
|
||||
assert_raises(Errno::ECHILD) { Process.waitpid(pid, Process::WNOHANG) }
|
||||
end
|
||||
|
||||
def test_can_terminate_rogues
|
||||
cmd = ExecCommand.new({})
|
||||
cmd.background = true
|
||||
pid = cmd.spawn("trap \"echo TERM && sleep 100\" TERM ; sleep 100")
|
||||
pid = cmd.spawn('trap "echo TERM && sleep 100" TERM ; sleep 100')
|
||||
# we need to give bash enough time to trap
|
||||
sleep 0.01
|
||||
|
||||
ExecCommand.terminate_async(wait: 0.1)
|
||||
|
||||
assert_raises(Errno::ECHILD) do
|
||||
Process.waitpid(pid,Process::WNOHANG)
|
||||
end
|
||||
|
||||
assert_raises(Errno::ECHILD) { Process.waitpid(pid, Process::WNOHANG) }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,27 +1,25 @@
|
|||
require 'test_helper'
|
||||
require 'tempfile'
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "tempfile"
|
||||
|
||||
module Pups
|
||||
class FileCommandTest < MiniTest::Test
|
||||
|
||||
class FileCommandTest < ::Minitest::Test
|
||||
def test_simple_file_creation
|
||||
tmp = Tempfile.new("test")
|
||||
tmp.write("x")
|
||||
tmp.close
|
||||
|
||||
|
||||
cmd = FileCommand.new
|
||||
cmd.path = tmp.path
|
||||
cmd.contents = "hello $world"
|
||||
cmd.params = {"world" => "world"}
|
||||
cmd.params = { "world" => "world" }
|
||||
cmd.run
|
||||
|
||||
assert_equal("hello world",
|
||||
File.read(tmp.path))
|
||||
assert_equal("hello world", File.read(tmp.path))
|
||||
ensure
|
||||
tmp.close
|
||||
tmp.unlink
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,56 +1,59 @@
|
|||
require 'test_helper'
|
||||
require 'tempfile'
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "tempfile"
|
||||
|
||||
module Pups
|
||||
class MergeCommandTest < MiniTest::Test
|
||||
class MergeCommandTest < ::Minitest::Test
|
||||
def test_deep_merge_arrays
|
||||
a = {a: {a: ["hi",1]}}
|
||||
b = {a: {a: ["hi",2]}}
|
||||
c = {a: {}}
|
||||
a = { a: { a: ["hi", 1] } }
|
||||
b = { a: { a: ["hi", 2] } }
|
||||
c = { a: {} }
|
||||
|
||||
d = Pups::MergeCommand.deep_merge(a,b,:merge_arrays)
|
||||
d = Pups::MergeCommand.deep_merge(d,c,:merge_arrays)
|
||||
d = Pups::MergeCommand.deep_merge(a, b, :merge_arrays)
|
||||
d = Pups::MergeCommand.deep_merge(d, c, :merge_arrays)
|
||||
|
||||
assert_equal(["hi", 1,"hi", 2], d[:a][:a])
|
||||
assert_equal(["hi", 1, "hi", 2], d[:a][:a])
|
||||
end
|
||||
|
||||
def test_merges
|
||||
|
||||
source = <<YAML
|
||||
user:
|
||||
name: "bob"
|
||||
password: "xyz"
|
||||
YAML
|
||||
source = <<~YAML
|
||||
user:
|
||||
name: "bob"
|
||||
password: "xyz"
|
||||
YAML
|
||||
|
||||
f = Tempfile.new("test")
|
||||
f.write source
|
||||
f.close
|
||||
|
||||
merge = <<YAML
|
||||
user:
|
||||
name: "bob2"
|
||||
YAML
|
||||
merge = <<~YAML
|
||||
user:
|
||||
name: "bob2"
|
||||
YAML
|
||||
|
||||
MergeCommand.from_str("#{f.path} $yaml", {"yaml" => YAML.load(merge) }).run
|
||||
MergeCommand.from_str(
|
||||
"#{f.path} $yaml",
|
||||
{ "yaml" => YAML.safe_load(merge) }
|
||||
).run
|
||||
|
||||
changed = YAML.load_file(f.path)
|
||||
changed = YAML.load_file(f.path)
|
||||
|
||||
assert_equal({"user" => {
|
||||
"name" => "bob2",
|
||||
"password" => "xyz"
|
||||
}}, changed)
|
||||
assert_equal(
|
||||
{ "user" => { "name" => "bob2", "password" => "xyz" } },
|
||||
changed
|
||||
)
|
||||
|
||||
def test_deep_merge_nil
|
||||
a = {param: {venison: "yes please"}}
|
||||
b = {param: nil}
|
||||
def test_deep_merge_nil
|
||||
a = { param: { venison: "yes please" } }
|
||||
b = { param: nil }
|
||||
|
||||
r1 = Pups::MergeCommand.deep_merge(a,b)
|
||||
r2 = Pups::MergeCommand.deep_merge(b,a)
|
||||
|
||||
assert_equal({venison: "yes please"}, r1[:param])
|
||||
assert_equal({venison: "yes please"}, r2[:param])
|
||||
end
|
||||
r1 = Pups::MergeCommand.deep_merge(a, b)
|
||||
r2 = Pups::MergeCommand.deep_merge(b, a)
|
||||
|
||||
assert_equal({ venison: "yes please" }, r1[:param])
|
||||
assert_equal({ venison: "yes please" }, r2[:param])
|
||||
end
|
||||
ensure
|
||||
f.unlink
|
||||
end
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
require 'test_helper'
|
||||
require 'tempfile'
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "tempfile"
|
||||
|
||||
module Pups
|
||||
class ReplaceCommandTest < MiniTest::Test
|
||||
|
||||
class ReplaceCommandTest < ::Minitest::Test
|
||||
def test_simple
|
||||
command = ReplaceCommand.new({})
|
||||
command.text = "hello world"
|
||||
|
@ -14,11 +15,11 @@ module Pups
|
|||
end
|
||||
|
||||
def test_reverse
|
||||
source = <<SCR
|
||||
1 one thousand 1
|
||||
1 one thousand 1
|
||||
1 one thousand 1
|
||||
SCR
|
||||
source = <<~SCR
|
||||
1 one thousand 1
|
||||
1 one thousand 1
|
||||
1 one thousand 1
|
||||
SCR
|
||||
|
||||
f = Tempfile.new("test")
|
||||
f.write source
|
||||
|
@ -33,17 +34,20 @@ SCR
|
|||
|
||||
command = ReplaceCommand.from_hash(hash, {})
|
||||
|
||||
assert_equal("1 one thousand 1\n1 one thousand 1\n1 hello world 1\n", command.replaced_text)
|
||||
assert_equal(
|
||||
"1 one thousand 1\n1 one thousand 1\n1 hello world 1\n",
|
||||
command.replaced_text
|
||||
)
|
||||
ensure
|
||||
f.unlink
|
||||
end
|
||||
|
||||
def test_global
|
||||
source = <<SCR
|
||||
one
|
||||
one
|
||||
one
|
||||
SCR
|
||||
source = <<~SCR
|
||||
one
|
||||
one
|
||||
one
|
||||
SCR
|
||||
|
||||
f = Tempfile.new("test")
|
||||
f.write source
|
||||
|
@ -61,7 +65,6 @@ SCR
|
|||
assert_equal("two\ntwo\ntwo\n", command.replaced_text)
|
||||
ensure
|
||||
f.unlink
|
||||
|
||||
end
|
||||
|
||||
def test_replace_with_env
|
||||
|
@ -71,26 +74,20 @@ SCR
|
|||
f.write source
|
||||
f.close
|
||||
|
||||
hash = {
|
||||
"filename" => f.path,
|
||||
"from" => "123",
|
||||
"to" => "hello $hellos"
|
||||
}
|
||||
hash = { "filename" => f.path, "from" => "123", "to" => "hello $hellos" }
|
||||
|
||||
command = ReplaceCommand.from_hash(hash, {"hello" => "world"})
|
||||
command = ReplaceCommand.from_hash(hash, { "hello" => "world" })
|
||||
assert_equal("hello worlds", command.replaced_text)
|
||||
|
||||
ensure
|
||||
f.unlink
|
||||
end
|
||||
|
||||
def test_parse
|
||||
|
||||
source = <<SCR
|
||||
this {
|
||||
is a test
|
||||
}
|
||||
SCR
|
||||
source = <<~SCR
|
||||
this {
|
||||
is a test
|
||||
}
|
||||
SCR
|
||||
|
||||
f = Tempfile.new("test")
|
||||
f.write source
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
require 'pups'
|
||||
require 'minitest/pride'
|
||||
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "pups"
|
||||
require "pups/cli"
|
||||
require "minitest/autorun"
|
||||
require "minitest/pride"
|
||||
|
|
Loading…
Reference in New Issue