Skip to content

Commit

Permalink
feat: implement command execution via through shell
Browse files Browse the repository at this point in the history
  • Loading branch information
bibendi committed Mar 30, 2021
1 parent 5845518 commit 71baf4e
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 71 deletions.
15 changes: 8 additions & 7 deletions lib/dip/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@ class Command
def_delegators self, :shell, :subshell

class ExecRunner
def self.call(cmd, argv, env: {}, **options)
::Process.exec(env, cmd, *argv, options)
def self.call(cmdline, env: {}, **options)
::Process.exec(env, cmdline, options)
end
end

class SubshellRunner
def self.call(cmd, argv, env: {}, panic: true, **options)
return if ::Kernel.system(env, cmd, *argv, options)
raise Dip::Error, "Command '#{([cmd] + argv).join(' ')}' executed with error." if panic
def self.call(cmdline, env: {}, panic: true, **options)
return if ::Kernel.system(env, cmdline, options)
raise Dip::Error, "Command '#{cmdline}' executed with error." if panic
end
end

class << self
def shell(cmd, argv = [], subshell: false, **options)
cmd = Dip.env.interpolate(cmd)
argv = argv.map { |arg| Dip.env.interpolate(arg) }
cmdline = [cmd, *argv].compact.join(" ")

puts [Dip.env.vars, cmd, argv].inspect if Dip.debug?
puts [Dip.env.vars, cmdline].inspect if Dip.debug?

runner = subshell ? SubshellRunner : ExecRunner
runner.call(cmd, argv, env: Dip.env.vars, **options)
runner.call(cmdline, env: Dip.env.vars, **options)
end

def subshell(*args, **kwargs)
Expand Down
12 changes: 8 additions & 4 deletions lib/dip/commands/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,15 @@ def compose_arguments

compose_argv << command.fetch(:service)

unless (cmd = command[:command].to_s).empty?
compose_argv.concat(cmd.shellsplit)
unless (cmd = command[:command]).empty?
compose_argv << cmd
end

compose_argv.concat(argv.any? ? argv : command[:default_args])
if argv.any?
compose_argv.concat(argv)
elsif !(default_args = command[:default_args]).empty?
compose_argv << default_args
end

compose_argv
end
Expand All @@ -57,7 +61,7 @@ def run_vars
run_vars = Dip::RunVars.env
return [] unless run_vars

run_vars.map { |k, v| ["-e", "#{k}=#{v}"] }.flatten
run_vars.map { |k, v| ["-e", "#{k}=#{Shellwords.escape(v)}"] }.flatten
end

def published_ports
Expand Down
17 changes: 2 additions & 15 deletions lib/dip/interaction_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def build_command(entry)
{
description: entry[:description],
service: entry.fetch(:service),
command: entry[:command],
default_args: prepare_default_args(entry[:default_args]),
command: entry[:command].to_s.strip,
default_args: entry[:default_args].to_s.strip,
environment: entry[:environment] || {},
compose: {
method: entry.dig(:compose, :method) || entry[:compose_method] || "run",
Expand All @@ -76,19 +76,6 @@ def sub_command_defaults!(entry)
entry[:description] ||= nil
end

def prepare_default_args(args)
return [] if args.nil?

case args
when Array
args
when String
args.shellsplit
else
raise ArgumentError, "Unknown type for default_args: #{args.inspect}"
end
end

def compose_run_options(value)
return [] unless value

Expand Down
49 changes: 28 additions & 21 deletions spec/lib/dip/commands/dns_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,81 +8,88 @@
let(:cli) { Dip::CLI::DNS }

describe Dip::Commands::DNS::Up do
let(:volume) { "--volume /var/run/docker.sock:/var/run/docker.sock:ro" }
let(:net) { "--net frontend" }
let(:name) { "--name dnsdock" }
let(:domain) { "--domain=docker" }
let(:port) { "--publish 53/udp" }
let(:image) { "aacebedo/dnsdock:latest-amd64" }
let(:cmd) { "run --detach #{volume} --restart always #{port} #{net} #{name} #{image} #{domain}" }

context "when without arguments" do
before { cli.start "up".shellsplit }
it { expected_subshell("docker", ["network", "create", "frontend"]) }
it do
expected_subshell(
"docker",
["run", "--detach", "--volume", "/var/run/docker.sock:/var/run/docker.sock:ro", "--restart", "always",
"--publish", "53/udp", "--net", "frontend", "--name", "dnsdock", "aacebedo/dnsdock:latest-amd64",
"--domain=docker"]
)
end
it { expected_subshell("docker", cmd) }
end

context "when option `name` is present" do
let(:name) { "--name foo" }
before { cli.start "up --name foo".shellsplit }
it { expected_subshell("docker", array_including("--name", "foo")) }
it { expected_subshell("docker", cmd) }
end

context "when option `socket` is present" do
let(:volume) { "--volume foo:/var/run/docker.sock:ro" }
before { cli.start "up --socket foo".shellsplit }
it { expected_subshell("docker", array_including("--volume", "foo:/var/run/docker.sock:ro")) }
it { expected_subshell("docker", cmd) }
end

context "when option `net` is present" do
let(:net) { "--net foo" }
before { cli.start "up --net foo".shellsplit }
it { expected_subshell("docker", ["network", "create", "foo"]) }
it { expected_subshell("docker", array_including("--net", "foo")) }
it { expected_subshell("docker", cmd) }
end

context "when option `publish` is present" do
let(:port) { "--publish foo" }
before { cli.start "up --publish foo".shellsplit }
it { expected_subshell("docker", array_including("--publish", "foo")) }
it { expected_subshell("docker", cmd) }
end

context "when option `image` is present" do
let(:image) { "foo" }
before { cli.start "up --image foo".shellsplit }
it { expected_subshell("docker", array_including("foo")) }
it { expected_subshell("docker", cmd) }
end

context "when option `domain` is present" do
let(:domain) { "--domain=foo" }
before { cli.start "up --domain foo".shellsplit }
it { expected_subshell("docker", array_including("--domain=foo")) }
it { expected_subshell("docker", cmd) }
end
end

describe Dip::Commands::DNS::Down do
context "when without arguments" do
before { cli.start "down".shellsplit }
it { expected_subshell("docker", ["stop", "dnsdock"]) }
it { expected_subshell("docker", ["rm", "-v", "dnsdock"]) }
it { expected_subshell("docker", "stop dnsdock") }
it { expected_subshell("docker", "rm -v dnsdock") }
end

context "when option `name` is present" do
before { cli.start "down --name foo".shellsplit }
it { expected_subshell("docker", ["stop", "foo"]) }
it { expected_subshell("docker", ["rm", "-v", "foo"]) }
it { expected_subshell("docker", "stop foo") }
it { expected_subshell("docker", "rm -v foo") }
end
end

describe Dip::Commands::DNS::IP do
context "when without arguments" do
before { cli.start "ip".shellsplit }
it do
expected_subshell("docker", array_including("inspect", "--format", /Networks.frontend.IPAddress/, "dnsdock"))
expected_subshell("docker", "inspect --format {{ .NetworkSettings.Networks.frontend.IPAddress }} dnsdock")
end
end

context "when option `name` is present" do
before { cli.start "ip --name foo".shellsplit }
it { expected_subshell("docker", array_including("foo")) }
it { expected_subshell("docker", "inspect --format {{ .NetworkSettings.Networks.frontend.IPAddress }} foo") }
end

context "when option `net` is present" do
before { cli.start "ip --net foo".shellsplit }
it { expected_subshell("docker", array_including(/Networks.foo.IPAddress/)) }
it { expected_subshell("docker", "inspect --format {{ .NetworkSettings.Networks.foo.IPAddress }} dnsdock") }
end
end
end
20 changes: 9 additions & 11 deletions spec/lib/dip/commands/nginx_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,50 @@
it do
expected_subshell(
"docker",
["run", "--detach", "--volume", "/var/run/docker.sock:/tmp/docker.sock:ro",
"--restart", "always", "--publish", "80:80", "--net", "frontend", "--name", "nginx",
"--label", "com.dnsdock.alias=docker", "bibendi/nginx-proxy:latest"]
"run --detach --volume /var/run/docker.sock:/tmp/docker.sock:ro --restart always --publish 80:80 --net frontend --name nginx --label com.dnsdock.alias=docker bibendi/nginx-proxy:latest"
)
end
end

context "when option `name` is present" do
before { cli.start "up --name foo".shellsplit }
it { expected_subshell("docker", array_including("--name", "foo")) }
it { expected_subshell("docker", "run --detach --volume /var/run/docker.sock:/tmp/docker.sock:ro --restart always --publish 80:80 --net frontend --name foo --label com.dnsdock.alias=docker bibendi/nginx-proxy:latest") }
end

context "when option `socket` is present" do
before { cli.start "up --socket foo".shellsplit }
it { expected_subshell("docker", array_including("--volume", "foo:/tmp/docker.sock:ro")) }
it { expected_subshell("docker", "run --detach --volume foo:/tmp/docker.sock:ro --restart always --publish 80:80 --net frontend --name nginx --label com.dnsdock.alias=docker bibendi/nginx-proxy:latest") }
end

context "when option `net` is present" do
before { cli.start "up --net foo".shellsplit }
it { expected_subshell("docker", ["network", "create", "foo"]) }
it { expected_subshell("docker", array_including("--net", "foo")) }
it { expected_subshell("docker", "run --detach --volume /var/run/docker.sock:/tmp/docker.sock:ro --restart always --publish 80:80 --net foo --name nginx --label com.dnsdock.alias=docker bibendi/nginx-proxy:latest") }
end

context "when option `publish` is present" do
before { cli.start "up --publish 80:80".shellsplit }
it { expected_subshell("docker", array_including("--publish", "80:80")) }
it { expected_subshell("docker", "run --detach --volume /var/run/docker.sock:/tmp/docker.sock:ro --restart always --publish 80:80 --net frontend --name nginx --label com.dnsdock.alias=docker bibendi/nginx-proxy:latest") }

context "when more than one port given" do
before { cli.start "up --publish 80:80 443:443".shellsplit }
it { expected_subshell("docker", array_including("--publish", "80:80", "--publish", "443:443")) }
it { expected_subshell("docker", "run --detach --volume /var/run/docker.sock:/tmp/docker.sock:ro --restart always --publish 80:80 --net frontend --name nginx --label com.dnsdock.alias=docker bibendi/nginx-proxy:latest") }
end
end

context "when option `image` is present" do
before { cli.start "up --image foo".shellsplit }
it { expected_subshell("docker", array_including("foo")) }
it { expected_subshell("docker", "run --detach --volume /var/run/docker.sock:/tmp/docker.sock:ro --restart always --publish 80:80 --net frontend --name nginx --label com.dnsdock.alias=docker foo") }
end

context "when option `domain` is present" do
before { cli.start "up --domain foo".shellsplit }
it { expected_subshell("docker", array_including("com.dnsdock.alias=foo")) }
it { expected_subshell("docker", "run --detach --volume /var/run/docker.sock:/tmp/docker.sock:ro --restart always --publish 80:80 --net frontend --name nginx --label com.dnsdock.alias=foo bibendi/nginx-proxy:latest") }
end

context "when option `certs` is present" do
before { cli.start "up --certs /home/whoami/certs_storage".shellsplit }
it { expected_subshell("docker", array_including("--volume", "/home/whoami/certs_storage:/etc/nginx/certs")) }
it { expected_subshell("docker", "run --detach --volume /var/run/docker.sock:/tmp/docker.sock:ro --volume /home/whoami/certs_storage:/etc/nginx/certs --restart always --publish 80:80 --net frontend --name nginx --label com.dnsdock.alias=docker bibendi/nginx-proxy:latest") }
end
end

Expand Down
18 changes: 7 additions & 11 deletions spec/lib/dip/commands/ssh_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,24 @@

before { cli.start "up".shellsplit }

it { expected_subshell("docker", array_including("volume", "create")) }
it { expected_subshell("docker", array_including("run", "--name=ssh-agent", "whilp/ssh-agent")) }
it do
expected_subshell("docker",
array_including("run", "--volume", "/user:/user", "--interactive", "--tty",
"whilp/ssh-agent", "ssh-add", "/user/.ssh/id_rsa"))
end
it { expected_subshell("docker", "volume create --name ssh_data") }
it { expected_subshell("docker", "run --detach --volume ssh_data:/ssh --name=ssh-agent whilp/ssh-agent") }
it { expected_subshell("docker", "run --rm --volume ssh_data:/ssh --volume /user:/user --interactive --tty whilp/ssh-agent ssh-add /user/.ssh/id_rsa") }
end

context "when option `key` is present" do
before { cli.start "up --key /foo/bar-baz-rsa".shellsplit }
it { expected_subshell("docker", array_including("/foo/bar-baz-rsa")) }
it { expected_subshell("docker", "run --rm --volume ssh_data:/ssh --volume /root:/root --interactive --tty whilp/ssh-agent ssh-add /foo/bar-baz-rsa") }
end

context "when option `volume` is present" do
before { cli.start "up --volume /foo/.ssh".shellsplit }
it { expected_subshell("docker", array_including("--volume", "/foo/.ssh:/foo/.ssh")) }
it { expected_subshell("docker", "run --rm --volume ssh_data:/ssh --volume /foo/.ssh:/foo/.ssh --interactive --tty whilp/ssh-agent ssh-add /root/.ssh/id_rsa") }
end

context "when option `user` is present" do
before { cli.start "up -u 1000".shellsplit }
it { expected_subshell("docker", array_including("-u", "1000")) }
it { expected_subshell("docker", "run -u 1000 --detach --volume ssh_data:/ssh --name=ssh-agent whilp/ssh-agent") }
end
end

Expand All @@ -49,6 +45,6 @@
describe Dip::Commands::SSH::Status do
before { cli.start "status".shellsplit }

it { expected_subshell("docker", array_including("inspect", "ssh-agent")) }
it { expected_subshell("docker", "inspect --format {{.State.Status}} ssh-agent") }
end
end
6 changes: 4 additions & 2 deletions spec/support/shared_contexts/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@

def expected_exec(cmd, argv, options = kind_of(Hash))
argv = Array(argv) if argv.is_a?(String)
expect(exec_runner).to have_received(:call).with(cmd, argv, options)
cmdline = [cmd, *argv].join(" ")
expect(exec_runner).to have_received(:call).with(cmdline, options)
end

def expected_subshell(cmd, argv, options = kind_of(Hash))
argv = Array(argv) if argv.is_a?(String)
expect(subshell_runner).to have_received(:call).with(cmd, argv, options)
cmdline = [cmd, *argv].join(" ")
expect(subshell_runner).to have_received(:call).with(cmdline, options)
end

0 comments on commit 71baf4e

Please sign in to comment.