diff --git a/README.md b/README.md index e5c661e..e8cd271 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The dip is a CLI dev–tool that provides native-like interaction with a Dockeri ## Presentations and examples - [Local development with Docker containers](https://slides.com/bibendi/dip) -- Dockerized Ruby on Rails applications: [one](https://github.com/lewagon/rails-k8s-demo), [two](https://github.com/bibendi/dip-example-rails), [three](https://github.com/evilmartians/evil_chat) +- [Dockerized Ruby on Rails application](https://SberMarket-Tech/outbox-example-apps) - Dockerized Node.js application: [one](https://github.com/bibendi/twinkle.js), [two](https://github.com/bibendi/yt-graphql-react-event-booking-api) - [Dockerized Ruby gem](https://github.com/bibendi/schked) - [Dockerizing Ruby and Rails development](https://evilmartians.com/chronicles/ruby-on-whales-docker-for-ruby-rails-development) @@ -20,7 +20,13 @@ The dip is a CLI dev–tool that provides native-like interaction with a Dockeri [![asciicast](https://asciinema.org/a/210236.svg)](https://asciinema.org/a/210236) -## Integration with shell +## Installation + +```sh +gem install dip +``` + +### Integration with shell Dip can be injected into the current shell (ZSH or Bash). @@ -51,14 +57,6 @@ VERSION=20180515103400 rails db:migrate:down You could add this `eval` at the end of your `~/.zshrc`, or `~/.bashrc`, or `~/.bash_profile`. After that, it will be automatically applied when you open your preferred terminal. -## Installation - -```sh -gem install dip -``` - -The compiled binary is no more provided since version 7, because of new version of [Ruby Packer](https://github.com/pmq20/ruby-packer) not released for a long time with recent Ruby version. Also there was a lot of work to prepare each release of Dip for MacOS version. - ## Usage ```sh @@ -240,19 +238,16 @@ services: The container will run using the same user ID as your host machine. - ### dip run Run commands defined within the `interaction` section of dip.yml A command will be executed by specified runner. Dip has three types of them: -- `docker-compose` runner — used when the `service` option is defined. +- `docker compose` runner — used when the `service` option is defined. - `kubectl` runner — used when the `pod` option is defined. - `local` runner — used when the previous ones are not defined. -If you are still using `docker-compose` binary (i.e., prior to Compose V2 changes), a command would be run through it. You can disable using of Compose V2 by passing an environment variable `DIP_COMPOSE_V2=false dip run`. - ```sh dip run rails c dip run rake db:migrate @@ -304,7 +299,7 @@ Run commands each by each from `provision` section of dip.yml ### dip compose -Run docker-compose commands that are configured according to the application's dip.yml: +Run Docker Compose commands that are configured according to the application's dip.yml: ```sh dip compose COMMAND [OPTIONS] @@ -312,6 +307,35 @@ dip compose COMMAND [OPTIONS] dip compose up -d redis ``` +### dip infra + +Runs shared Docker Compose services that are used by the current application. Useful for microservices. + +There are several official infrastructure services available: +- [dip-postgres](https://github.com/bibendi/dip-postgres) +- [dip-kafka](https://github.com/bibendi/dip-kafka) +- [dip-nginx](https://github.com/bibendi/dip-nginx) + +```yaml +# dip.yml +infra: + foo: + git: https://github.com/owner/foo.git + ref: latest # default, optional + bar: + path: ~/path/to/bar +``` + +Repositories will be pulled to a `~/.dip/infra` folder. For example, for the `foo` service it would be like this: `~/.dip/infra/foo/latest` and clonned with the following command: `git clone -b --single-branch --depth 1`. + +Available CLI commands: + +- `dip infra update` pulls updates from sources +- `dip infra up` starts all infra services +- `dip infra up -n kafka` starts a specific infra service +- `dip infra down` stops all infra services +- `dip infra down -n kafka` stops a specific infra service + ### dip ktl Run kubectl commands that are configured according to the application's dip.yml: @@ -356,88 +380,10 @@ dip ssh up -u 1000 This especially helpful if you have something like this in your docker-compose.yml: -``` +```yml services: web: user: "1000:1000" - -``` - -### dip nginx - -Runs Nginx server container based on [nginxproxy/nginx-proxy](https://github.com/nginx-proxy/nginx-proxy) image. An application's docker-compose.yml should contain environment variable `VIRTUAL_HOST` and `VIRTUAL_PATH` and connects to external network `frontend`. - -foo-project/docker-compose.yml - -```yml -services: - foo-web: - image: company/foo_image - environment: - - VIRTUAL_HOST=*.bar-app.docker - - VIRTUAL_PATH=/ - networks: - - default - - frontend - dns: $DIP_DNS - -networks: - frontend: - external: - name: frontend -``` - -baz-project/docker-compose.yml - -```yml -services: - baz-web: - image: company/baz_image - environment: - - VIRTUAL_HOST=*.bar-app.docker - - VIRTUAL_PATH=/api/v1/baz_service,/api/v2/baz_service - networks: - - default - - frontend - dns: $DIP_DNS - -networks: - frontend: - external: - name: frontend -``` - -```sh -dip nginx up -cd foo-project && dip compose up -cd baz-project && dip compose up -curl www.bar-app.docker/api/v1/quz -curl www.bar-app.docker/api/v1/baz_service/qzz -``` - -#### Pass SSL certificates - -```sh -dip nginx up -c $HOME/ssl_certificates -``` - -#### Publish more than one port to localhost - -Just pass a list, separated by a space: - -```sh -dip nginx up -p 80:80 443:443 -``` - -### dip dns - -Runs a DNS server container based on https://github.com/aacebedo/dnsdock. It is used for container to container requests through Nginx. An application's docker-compose.yml should define `dns` configuration with environment variable `$DIP_DNS` and connect to external network `frontend`. `$DIP_DNS` will be automatically assigned by dip. - -```sh -dip dns up - -cd foo-project -dip compose exec foo-web curl http://www.bar-app.docker/api/v1/baz_service ``` ## Changelog diff --git a/dip.gemspec b/dip.gemspec index a7d7a60..6c1463f 100644 --- a/dip.gemspec +++ b/dip.gemspec @@ -11,9 +11,9 @@ Gem::Specification.new do |spec| spec.authors = ["bibendi"] spec.email = ["merkushin.m.s@gmail.com"] - spec.summary = "Ruby gem CLI tool for better interacting docker-compose files." + spec.summary = "Ruby gem CLI tool for better interacting Docker Compose files." spec.description = "DIP - Docker Interaction Process." \ - "CLI tool for better development experience when interacting with docker and docker-compose." + "CLI tool for better development experience when interacting with docker and Docker Compose." spec.homepage = "https://github.com/bibendi/dip" # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' @@ -45,4 +45,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rubocop-rspec", "~> 2.2" spec.add_development_dependency "simplecov", "~> 0.16" spec.add_development_dependency "test-unit", "~> 3" + spec.add_development_dependency "fakefs" end diff --git a/docs/docker-for-mac-install.md b/docs/docker-for-mac-install.md deleted file mode 100644 index a871835..0000000 --- a/docs/docker-for-mac-install.md +++ /dev/null @@ -1,28 +0,0 @@ -# Docker for Mac - -Download and install [Docker for Mac](https://www.docker.com/docker-mac). - -**WARNING**: Latest Docker for Mac 17.12.0-ce-mac46 seems to [break d4m-nfs](https://github.com/IFSight/d4m-nfs/issues/55). - -# d4m-nfs - -For the best i/o performance git clone latest [IFSight/d4m-nfs](https://github.com/IFSight/d4m-nfs). - -- Remove all shared paths from Docker for Mac Preferences except `/tmp`. -- Run `echo '/Users:/Users:0:0' > ./etc/d4m-nfs-mounts.txt` -- Run `./d4m-nfs.sh` after each reboot. - -# Create resolver - -```sh - sudo touch /etc/resolver/docker - echo "nameserver 127.0.0.1" | sudo tee -a /etc/resolver/docker -``` - -# Dnsmasq - -```sh - brew install dnsmasq - echo 'address=/docker/127.0.0.1' >> $(brew --prefix)/etc/dnsmasq.conf - brew services restart dnsmasq -``` diff --git a/docs/docker-ubuntu-install.md b/docs/docker-ubuntu-install.md deleted file mode 100644 index 3bfc03d..0000000 --- a/docs/docker-ubuntu-install.md +++ /dev/null @@ -1,17 +0,0 @@ -# Docker - -https://docs.docker.com/install/linux/docker-ce/ubuntu/ - -# Docker Compose - -https://docs.docker.com/compose/install/ - -# Dnsmasq - -**WARNING**: Latest Ubuntu 18.04 already runs own local dns resolver at *.localhost. Dnsmasq is not needed. In that case you should run `dip dns` and `dip nginx` with option `--domain localhost`. - -```sh -sudo apt-get install dnsmasq -echo "address=/docker/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf -sudo service dnsmasq restart -``` diff --git a/lib/dip.rb b/lib/dip.rb index a342924..3e62fb3 100644 --- a/lib/dip.rb +++ b/lib/dip.rb @@ -18,6 +18,10 @@ def bin_path $PROGRAM_NAME.start_with?("./") ? File.expand_path($PROGRAM_NAME) : "dip" end + def home_path + @home_path ||= File.expand_path(ENV.fetch("DIP_HOME", "~/.dip")) + end + %w[test debug].each do |key| define_method("#{key}?") do ENV["DIP_ENV"] == key diff --git a/lib/dip/cli.rb b/lib/dip/cli.rb index 9913f53..15e66df 100644 --- a/lib/dip/cli.rb +++ b/lib/dip/cli.rb @@ -5,7 +5,7 @@ module Dip class CLI < Thor - TOP_LEVEL_COMMANDS = %w[help version ls compose up stop down run provision ssh dns nginx console].freeze + TOP_LEVEL_COMMANDS = %w[help version ls compose up stop down run provision ssh infra console].freeze class << self # Hackery. Take the run method away from Thor so that we can redefine it. @@ -47,30 +47,30 @@ def ls Dip::Commands::List.new.execute end - desc "compose CMD [OPTIONS]", "Run docker-compose commands" + desc "compose CMD [OPTIONS]", "Run Docker Compose commands" def compose(*argv) require_relative "commands/compose" Dip::Commands::Compose.new(*argv).execute end - desc "build [OPTIONS] SERVICE", "Run `docker-compose build` command" + desc "build [OPTIONS] SERVICE", "Run `docker compose build` command" def build(*argv) compose("build", *argv) end - desc "up [OPTIONS] SERVICE", "Run `docker-compose up` command" + desc "up [OPTIONS] SERVICE", "Run `docker compose up` command" def up(*argv) compose("up", *argv) end - desc "stop [OPTIONS] SERVICE", "Run `docker-compose stop` command" + desc "stop [OPTIONS] SERVICE", "Run `docker compose stop` command" def stop(*argv) compose("stop", *argv) end - desc "down [OPTIONS]", "Run `docker-compose down` command" + desc "down [OPTIONS]", "Run `docker compose down` command" method_option :help, aliases: "-h", type: :boolean, desc: "Display usage information" - method_option :all, aliases: "-A", type: :boolean, desc: "Shutdown all running docker-compose projects" + method_option :all, aliases: "-A", type: :boolean, desc: "Shutdown all running Docker Compose projects" def down(*argv) if options[:help] invoke :help, ["down"] @@ -121,13 +121,9 @@ def provision desc "ssh", "ssh-agent container commands" subcommand :ssh, Dip::CLI::SSH - require_relative "cli/dns" - desc "dns", "DNS server for automatic docker container discovery" - subcommand :dns, Dip::CLI::DNS - - require_relative "cli/nginx" - desc "nginx", "Nginx reverse proxy server" - subcommand :nginx, Dip::CLI::Nginx + require_relative "cli/infra" + desc "infra", "Infrastructure services" + subcommand :infra, Dip::CLI::Infra require_relative "cli/console" desc "console", "Integrate Dip commands into shell (only ZSH and Bash are supported)" diff --git a/lib/dip/cli/infra.rb b/lib/dip/cli/infra.rb new file mode 100644 index 0000000..9eef090 --- /dev/null +++ b/lib/dip/cli/infra.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "thor" +require_relative "base" +require_relative "../commands/infra" +require_relative "../commands/infra/service" + +module Dip + class CLI + class Infra < Base + desc "update", "Pull infra services updates" + method_option :help, aliases: "-h", type: :boolean, + desc: "Display usage information" + method_option :name, aliases: "-n", type: :array, default: [], + desc: "Update infra service, all if empty" + def update + if options[:help] + invoke :help, ["update"] + else + lookup_services(options[:name]).each do |service| + Commands::Infra::Update.new(service: service).execute + end + end + end + + desc "up", "Run infra services" + method_option :help, aliases: "-h", type: :boolean, + desc: "Display usage information" + method_option :name, aliases: "-n", type: :array, default: [], + desc: "Start specific infra service, all if empty" + method_option :update, type: :boolean, default: true, + desc: "Pull infra services updates" + def up(*compose_argv) + if options[:help] + invoke :help, ["up"] + else + lookup_services(options[:name]).each do |service| + if options[:update] + Commands::Infra::Update.new(service: service).execute + end + + Commands::Infra::Up.new(*compose_argv, service: service).execute + end + end + end + + desc "down", "Stop infra services" + method_option :help, aliases: "-h", type: :boolean, + desc: "Display usage information" + method_option :name, aliases: "-n", type: :array, default: [], + desc: "Stop specific infra service, all if empty" + def down(*compose_argv) + if options[:help] + invoke :help, ["down"] + else + lookup_services(options[:name]).each do |service| + Commands::Infra::Down.new(*compose_argv, service: service).execute + end + end + end + + private + + def lookup_services(names) + names = Array(names).map(&:to_sym) + + Dip.config.infra.each_with_object([]) do |(name, params), memo| + next if !names.empty? && !names.include?(name) + memo << Commands::Infra::Service.new(name, **params) + end + end + end + end +end diff --git a/lib/dip/cli/nginx.rb b/lib/dip/cli/nginx.rb deleted file mode 100644 index 1876206..0000000 --- a/lib/dip/cli/nginx.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -require "thor" -require_relative "base" -require_relative "../commands/nginx" - -module Dip - class CLI - # See more https://github.com/nginx-proxy/nginx-proxy - class Nginx < Base - desc "up", "Run nginx container" - method_option :help, aliases: "-h", type: :boolean, - desc: "Display usage information" - method_option :name, aliases: "-n", type: :string, default: "nginx", - desc: "Container name" - method_option :socket, aliases: "-s", type: :string, default: "/var/run/docker.sock", - desc: "Path to docker socket" - method_option :net, aliases: "-t", type: :string, default: "frontend", - desc: "Container network name" - method_option :publish, aliases: "-p", type: :array, default: ["80:80"], - desc: "Container port(s). For more than one port, separate them by a space" - method_option :image, aliases: "-i", type: :string, default: "nginxproxy/nginx-proxy:latest", - desc: "Docker image name" - method_option :domain, aliases: "-d", type: :string, default: "docker", - desc: "Top level domain" - method_option :certs, aliases: "-c", type: :string, desc: "Path to ssl certificates" - def up - if options[:help] - invoke :help, ["up"] - else - Dip::Commands::Nginx::Up.new( - name: options.fetch(:name), - socket: options.fetch(:socket), - net: options.fetch(:net), - publish: options.fetch(:publish), - image: options.fetch(:image), - domain: options.fetch(:domain), - certs: options[:certs] - ).execute - end - end - - desc "down", "Stop nginx container" - method_option :help, aliases: "-h", type: :boolean, - desc: "Display usage information" - method_option :name, aliases: "-n", type: :string, default: "nginx", - desc: "Container name" - def down - if options[:help] - invoke :help, ["down"] - else - Dip::Commands::Nginx::Down.new( - name: options.fetch(:name) - ).execute - end - end - - desc "restart", "Stop and start nginx container" - method_option :help, aliases: "-h", type: :boolean, - desc: "Display usage information" - def restart(*args) - if options[:help] - invoke :help, ["restart"] - else - Dip::CLI::Nginx.start(["down"] + args) - sleep 1 - Dip::CLI::Nginx.start(["up"] + args) - end - end - end - end -end diff --git a/lib/dip/commands/compose.rb b/lib/dip/commands/compose.rb index 5724e8e..5ee7f01 100644 --- a/lib/dip/commands/compose.rb +++ b/lib/dip/commands/compose.rb @@ -21,15 +21,15 @@ def initialize(*argv, shell: true) def execute Dip.env["DIP_DNS"] ||= find_dns + set_infra_env + compose_argv = Array(find_files) + Array(cli_options) + argv if (override_command = compose_command_override) override_command, *override_args = override_command.split(" ") exec_program(override_command, override_args.concat(compose_argv), shell: shell) - elsif compose_v2? - exec_program("docker", compose_argv.unshift("compose"), shell: shell) else - exec_program("docker-compose", compose_argv, shell: shell) + exec_program("docker", compose_argv.unshift("compose"), shell: shell) end end @@ -76,17 +76,16 @@ def find_dns end end - def compose_v2? - if %w[false no 0].include?(Dip.env["DIP_COMPOSE_V2"]) || Dip.test? - return false - end - - !!exec_subprocess("docker", "compose version", panic: false, out: File::NULL, err: File::NULL) - end - def compose_command_override Dip.env["DIP_COMPOSE_COMMAND"] || config[:command] end + + def set_infra_env + Dip.config.infra.each do |name, params| + service = Commands::Infra::Service.new(name, **params) + Dip.env[service.network_env_var] = service.network_name + end + end end end end diff --git a/lib/dip/commands/infra.rb b/lib/dip/commands/infra.rb new file mode 100644 index 0000000..445c301 --- /dev/null +++ b/lib/dip/commands/infra.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "shellwords" +require "fileutils" +require_relative "../command" + +module Dip + module Commands + module Infra + class Update < Dip::Command + def initialize(service:) + @service = service + end + + def execute + return unless @service.git + + if Dir.exist?(@service.location) + pull + else + clone + end + end + + private + + def pull + Dir.chdir(@service.location) do + exec_subprocess("git", "checkout .") + exec_subprocess("git", "pull --rebase") + end + end + + def clone + FileUtils.mkdir_p(@service.location) + + Dir.chdir(@service.location) do + args = [ + "clone", + "--single-branch", + "--depth 1", + "--branch #{Shellwords.escape(@service.ref)}", + Shellwords.escape(@service.git), + Shellwords.escape(@service.location) + ] + exec_subprocess("git", args) + end + end + end + + class Up < Dip::Command + def initialize(*compose_argv, service:) + @compose_argv = compose_argv.compact + @service = service + end + + def execute + Dir.chdir(@service.location) do + exec_subprocess("docker", "network create #{@service.network_name}", panic: false, err: File::NULL) + + argv = %w[compose up --detach] + @compose_argv + exec_subprocess("docker", argv, env: @service.env) + end + end + end + + class Down < Dip::Command + def initialize(*compose_argv, service:) + @compose_argv = compose_argv.compact + @service = service + end + + def execute + Dir.chdir(@service.location) do + argv = %w[compose down] + @compose_argv + exec_subprocess("docker", argv, env: @service.env) + end + end + end + end + end +end diff --git a/lib/dip/commands/infra/service.rb b/lib/dip/commands/infra/service.rb new file mode 100644 index 0000000..c465a6a --- /dev/null +++ b/lib/dip/commands/infra/service.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "fileutils" + +module Dip + module Commands + module Infra + class Service + attr_reader :name, :git, :ref, :location, :project_name, :network_name, :network_env_var + + def initialize(name, git: nil, ref: nil, path: nil) + if git.nil? && path.nil? + raise ArgumentError, "Infra service `#{name}` configuration error: git or path must be defined" + end + + @name = name + @git = git + @ref = ref || "latest" + @location = if git + "#{Dip.home_path}/infra/#{@name}/#{@ref}" + else + File.expand_path(path) + end + @project_name = "dip-infra-#{name}-#{@ref}" + @network_name = "dip-net-#{name}-#{@ref}" + @network_env_var = "DIP_INFRA_NETWORK_#{@name.to_s.upcase.tr("-", "_")}" + end + + def env + { + "COMPOSE_PROJECT_NAME" => project_name, + "DIP_INFRA_NETWORK_NAME" => network_name + } + end + end + end + end +end diff --git a/lib/dip/commands/nginx.rb b/lib/dip/commands/nginx.rb deleted file mode 100644 index 6078e5f..0000000 --- a/lib/dip/commands/nginx.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require "shellwords" -require_relative "../command" - -module Dip - module Commands - module Nginx - class Up < Dip::Command - def initialize(name:, socket:, net:, publish:, image:, domain:, certs:) - @name = name - @socket = socket - @net = net - @publish = publish - @image = image - @domain = domain - @certs = certs - end - - def execute - exec_subprocess("docker", "network create #{@net}", panic: false, err: File::NULL) - exec_subprocess("docker", "run #{container_args} #{@image}") - end - - private - - def container_args - result = %w[--detach] - result << "--volume #{@socket}:/tmp/docker.sock:ro" - result << "--volume #{@certs}:/etc/nginx/certs" unless @certs.to_s.empty? - result << "--restart always" - result << Array(@publish).map { |p| "--publish #{p}" }.join(" ") - result << "--net #{@net}" - result << "--name #{@name}" - result << "--label com.dnsdock.alias=#{@domain}" - result.join(" ") - end - end - - class Down < Dip::Command - def initialize(name:) - @name = name - end - - def execute - exec_subprocess("docker", "stop #{@name}", panic: false, out: File::NULL, err: File::NULL) - exec_subprocess("docker", "rm -v #{@name}", panic: false, out: File::NULL, err: File::NULL) - end - end - end - end -end diff --git a/lib/dip/commands/runners/docker_compose_runner.rb b/lib/dip/commands/runners/docker_compose_runner.rb index 669c536..d0dc010 100644 --- a/lib/dip/commands/runners/docker_compose_runner.rb +++ b/lib/dip/commands/runners/docker_compose_runner.rb @@ -71,7 +71,7 @@ def published_ports def update_command_for_profiles # NOTE: When using profiles, the method is always `up`. - # This is because `docker-compose` does not support profiles + # This is because `docker compose` does not support profiles # for other commands. Also, run options need to be removed # because they are not supported by `up`. command[:compose][:method] = "up" diff --git a/lib/dip/config.rb b/lib/dip/config.rb index 2895df2..7e506b6 100644 --- a/lib/dip/config.rb +++ b/lib/dip/config.rb @@ -17,10 +17,13 @@ class Config environment: {}, compose: {}, kubectl: {}, + infra: {}, interaction: {}, provision: [] }.freeze + TOP_LEVEL_KEYS = %i[environment compose kubectl infra interaction provision].freeze + ConfigKeyMissingError = Class.new(ArgumentError) class ConfigFinder @@ -95,7 +98,7 @@ def to_h config end - %i[environment compose kubectl interaction provision].each do |key| + TOP_LEVEL_KEYS.each do |key| define_method(key) do config[key] || (raise config_missing_error(key)) end diff --git a/lib/dip/version.rb b/lib/dip/version.rb index fd31399..c95e144 100644 --- a/lib/dip/version.rb +++ b/lib/dip/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Dip - VERSION = "7.8.0" + VERSION = "8.0.0" end diff --git a/spec/lib/dip/commands/compose_spec.rb b/spec/lib/dip/commands/compose_spec.rb index 344219c..3bd61b8 100644 --- a/spec/lib/dip/commands/compose_spec.rb +++ b/spec/lib/dip/commands/compose_spec.rb @@ -10,13 +10,13 @@ context "when execute without extra arguments" do before { cli.start "compose run".shellsplit } - it { expected_exec("docker-compose", "run") } + it { expected_exec("docker", "compose run") } end context "when execute with arguments" do before { cli.start "compose run --rm bash".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "bash"]) } + it { expected_exec("docker", ["compose run", "--rm", "bash"]) } end context "when config contains project_name", :config do @@ -24,7 +24,7 @@ before { cli.start "compose run".shellsplit } - it { expected_exec("docker-compose", ["--project-name", "rocket", "run"]) } + it { expected_exec("docker", ["compose --project-name", "rocket", "run"]) } end context "when config contains project_name with env vars", :config, :env do @@ -33,7 +33,7 @@ before { cli.start "compose run".shellsplit } - it { expected_exec("docker-compose", ["--project-name", "rocket-test", "run"]) } + it { expected_exec("docker", ["compose --project-name", "rocket-test", "run"]) } end context "when config contains project_directory", :config do @@ -41,7 +41,7 @@ before { cli.start "compose run".shellsplit } - it { expected_exec("docker-compose", ["--project-directory", "/foo/bar", "run"]) } + it { expected_exec("docker", ["compose --project-directory", "/foo/bar", "run"]) } end context "when config contains project_directory with env vars", :config, :env do @@ -50,7 +50,7 @@ before { cli.start "compose run".shellsplit } - it { expected_exec("docker-compose", ["--project-directory", "/foo-test", "run"]) } + it { expected_exec("docker", ["compose --project-directory", "/foo-test", "run"]) } end context "when compose's config path contains spaces", :config do @@ -70,7 +70,7 @@ cli.start "compose run".shellsplit end - it { expected_exec("docker-compose", ["--file", Shellwords.escape(file), "run"]) } + it { expected_exec("docker", ["compose --file", Shellwords.escape(file), "run"]) } end context "when config contains multiple docker-compose files", :config do @@ -95,7 +95,7 @@ cli.start "compose run".shellsplit end - it { expected_exec("docker-compose", ["--file", global_file, "--file", override_file, "run"]) } + it { expected_exec("docker", ["compose --file", global_file, "--file", override_file, "run"]) } end context "and a file name contains env var", :env do @@ -116,7 +116,7 @@ cli.start "compose run".shellsplit end - it { expected_exec("docker-compose", ["--file", file, "run"]) } + it { expected_exec("docker", ["compose --file", file, "run"]) } end end diff --git a/spec/lib/dip/commands/infra_spec.rb b/spec/lib/dip/commands/infra_spec.rb new file mode 100644 index 0000000..15edcda --- /dev/null +++ b/spec/lib/dip/commands/infra_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "shellwords" +require "dip/cli/infra" +require "dip/commands/infra" + +describe Dip::Commands::Infra, :config do + let(:cli) { Dip::CLI::Infra } + let(:config) { {infra: {nginx: {git: "some@git.repo"}}} } + + describe Dip::Commands::Infra::Update do + let(:folder) { "#{Dip.home_path}/infra/nginx/latest" } + + it "creates folder and clones repo" do + FakeFS do + cli.start "update".shellsplit + expect(Dir.exist?(folder)).to be true + end + + expected_subprocess("git", "clone --single-branch --depth 1 --branch latest some@git.repo #{folder}") + end + + context "when folder is exist" do + it "just checkouts repo" do + FakeFS do + FileUtils.mkdir_p(folder) + cli.start "update".shellsplit + end + + expected_subprocess("git", "checkout .") + expected_subprocess("git", "pull --rebase") + end + end + + describe Dip::Commands::Infra::Up do + it "updates and runs infra services" do + FakeFS do + FileUtils.mkdir_p(folder) + expect_any_instance_of(Dip::Commands::Infra::Update).to receive(:execute) + cli.start "up --some-compose-arg".shellsplit + expected_subprocess("docker", "network create dip-net-nginx-latest") + expected_subprocess("docker", "compose up --detach --some-compose-arg") + end + end + end + + describe Dip::Commands::Infra::Down do + it "stops infra services" do + FakeFS do + FileUtils.mkdir_p(folder) + expect_any_instance_of(Dip::Commands::Infra::Update).not_to receive(:execute) + cli.start "down --some-compose-arg".shellsplit + expected_subprocess("docker", "compose down --some-compose-arg") + end + end + end + end +end diff --git a/spec/lib/dip/commands/nginx_spec.rb b/spec/lib/dip/commands/nginx_spec.rb deleted file mode 100644 index c853d76..0000000 --- a/spec/lib/dip/commands/nginx_spec.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -require "shellwords" -require "dip/cli/nginx" -require "dip/commands/nginx" - -describe Dip::Commands::Nginx do - let(:cli) { Dip::CLI::Nginx } - - describe Dip::Commands::Nginx::Up do - context "when without arguments" do - before { cli.start "up".shellsplit } - - it { expected_subprocess("docker", ["network", "create", "frontend"]) } - - it do - expected_subprocess( - "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 nginxproxy/nginx-proxy:latest" - ) - end - end - - context "when option `name` is present" do - before { cli.start "up --name foo".shellsplit } - - it { - expected_subprocess("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 nginxproxy/nginx-proxy:latest") - } - end - - context "when option `socket` is present" do - before { cli.start "up --socket foo".shellsplit } - - it { - expected_subprocess("docker", - "run --detach --volume foo:/tmp/docker.sock:ro --restart always --publish 80:80 --net frontend --name nginx --label com.dnsdock.alias=docker nginxproxy/nginx-proxy:latest") - } - end - - context "when option `net` is present" do - before { cli.start "up --net foo".shellsplit } - - it { expected_subprocess("docker", ["network", "create", "foo"]) } - - it { - expected_subprocess("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 nginxproxy/nginx-proxy:latest") - } - end - - context "when option `publish` is present" do - before { cli.start "up --publish 80:80".shellsplit } - - it { - expected_subprocess("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 nginxproxy/nginx-proxy:latest") - } - - context "when more than one port given" do - before { cli.start "up --publish 80:80 443:443".shellsplit } - - it { - expected_subprocess("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 nginxproxy/nginx-proxy:latest") - } - end - end - - context "when option `image` is present" do - before { cli.start "up --image foo".shellsplit } - - it { - expected_subprocess("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_subprocess("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 nginxproxy/nginx-proxy:latest") - } - end - - context "when option `certs` is present" do - before { cli.start "up --certs /home/whoami/certs_storage".shellsplit } - - it { - expected_subprocess("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 nginxproxy/nginx-proxy:latest") - } - end - end - - describe Dip::Commands::Nginx::Down do - context "when without arguments" do - before { cli.start "down".shellsplit } - - it { expected_subprocess("docker", ["stop", "nginx"]) } - it { expected_subprocess("docker", ["rm", "-v", "nginx"]) } - end - - context "when option `name` is present" do - before { cli.start "down --name foo".shellsplit } - - it { expected_subprocess("docker", ["stop", "foo"]) } - it { expected_subprocess("docker", ["rm", "-v", "foo"]) } - end - end -end diff --git a/spec/lib/dip/commands/runners/docker_compose_runner_spec.rb b/spec/lib/dip/commands/runners/docker_compose_runner_spec.rb index e2f22b3..9c3522c 100644 --- a/spec/lib/dip/commands/runners/docker_compose_runner_spec.rb +++ b/spec/lib/dip/commands/runners/docker_compose_runner_spec.rb @@ -19,63 +19,63 @@ context "when run bash command" do before { cli.start "run bash".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app"]) } end context "when the command has shell: false option" do before { cli.start "run bash_shell pwd".shellsplit } it do - expect(exec_program_runner).to have_received(:call).with(["docker-compose", "run", "--rm", "app", "bash", "pwd"], kind_of(Hash)) + expect(exec_program_runner).to have_received(:call).with(["docker", "compose", "run", "--rm", "app", "bash", "pwd"], kind_of(Hash)) end end context "when run shorthanded bash command" do before { cli.start ["bash"] } - it { expected_exec("docker-compose", ["run", "--rm", "app"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app"]) } end context "when publish ports" do before { cli.start "run --publish 3:3 -p 5:5 rails s".shellsplit } - it { expected_exec("docker-compose", ["run", "--publish=3:3", "--publish=5:5", "--rm", "app", "rails", "s"]) } + it { expected_exec("docker", ["compose", "run", "--publish=3:3", "--publish=5:5", "--rm", "app", "rails", "s"]) } end context "when publish is part of a command" do before { cli.start "run rails s --publish=3000:3000".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app", "rails", "s", "--publish\\=3000:3000"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app", "rails", "s", "--publish\\=3000:3000"]) } end context "when run psql command without db name" do before { cli.start "run psql".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "postgres", "psql", "-h", "postgres", "db_dev"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "postgres", "psql", "-h", "postgres", "db_dev"]) } end context "when run psql command with db name" do before { cli.start "run psql db_test".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "postgres", "psql", "-h", "postgres", "db_test"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "postgres", "psql", "-h", "postgres", "db_test"]) } end context "when run rails command" do before { cli.start "run rails".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app", "rails"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app", "rails"]) } end context "when run rails command with subcommand" do before { cli.start "run rails console".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app", "rails", "console"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app", "rails", "console"]) } end context "when run rails command with arguments" do before { cli.start "run rails g migration add_index --force".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app", "rails", "g", "migration", "add_index", "--force"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app", "rails", "g", "migration", "add_index", "--force"]) } end # backward compatibility @@ -84,7 +84,7 @@ before { cli.start "run bash".shellsplit } - it { expected_exec("docker-compose", ["run", "--foo", "-bar", "--baz=qux", "--rm", "app"]) } + it { expected_exec("docker", ["compose", "run", "--foo", "-bar", "--baz=qux", "--rm", "app"]) } end context "when config with compose: run_options" do @@ -92,7 +92,7 @@ before { cli.start "run bash".shellsplit } - it { expected_exec("docker-compose", ["run", "--foo", "-bar", "--baz=qux", "--rm", "app"]) } + it { expected_exec("docker", ["compose", "run", "--foo", "-bar", "--baz=qux", "--rm", "app"]) } end # backward compatibility @@ -101,7 +101,7 @@ before { cli.start "run rails server".shellsplit } - it { expected_exec("docker-compose", ["up", "app", "rails", "server"]) } + it { expected_exec("docker", ["compose", "up", "app", "rails", "server"]) } end context "when config with compose: method" do @@ -109,14 +109,14 @@ before { cli.start "run rails server".shellsplit } - it { expected_exec("docker-compose", ["up", "app", "rails", "server"]) } + it { expected_exec("docker", ["compose", "up", "app", "rails", "server"]) } end context "when run vars" do context "when execute through `compose run`" do before { cli.start "FOO=foo run bash".shellsplit } - it { expected_exec("docker-compose", ["run", "-e", "FOO=foo", "--rm", "app"]) } + it { expected_exec("docker", ["compose", "run", "-e", "FOO=foo", "--rm", "app"]) } end context "when execute through `compose up`" do @@ -124,7 +124,7 @@ before { cli.start "FOO=foo run rails server".shellsplit } - it { expected_exec("docker-compose", ["up", "app", "rails", "server"]) } + it { expected_exec("docker", ["compose", "up", "app", "rails", "server"]) } end end @@ -133,7 +133,7 @@ before { cli.start "run rspec".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app", "rspec"], env: hash_including("RAILS_ENV" => "test")) } + it { expected_exec("docker", ["compose", "run", "--rm", "app", "rspec"], env: hash_including("RAILS_ENV" => "test")) } end context "when config with profiles" do @@ -149,7 +149,7 @@ before { cli.start "run stack".shellsplit } - it { expected_exec("docker-compose", ["--profile", "foo", "--profile", "bar", "up"]) } + it { expected_exec("docker", ["compose", "--profile", "foo", "--profile", "bar", "up"]) } end context "when config with subcommands" do @@ -159,19 +159,19 @@ context "and run rails server" do before { cli.start "run rails s".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app", "rails", "server"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app", "rails", "server"]) } end context "when run rails command with other subcommand" do before { cli.start "run rails console".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app", "rails", "console"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app", "rails", "console"]) } end context "when run rails command with arguments" do before { cli.start "run rails s foo --bar".shellsplit } - it { expected_exec("docker-compose", ["run", "--rm", "app", "rails", "server", "foo", "--bar"]) } + it { expected_exec("docker", ["compose", "run", "--rm", "app", "rails", "server", "foo", "--bar"]) } end context "when config with compose_run_options" do @@ -179,7 +179,7 @@ before { cli.start "run rails s".shellsplit } - it { expected_exec("docker-compose", ["run", "--foo", "-bar", "--baz=qux", "--rm", "app", "rails", "s"]) } + it { expected_exec("docker", ["compose", "run", "--foo", "-bar", "--baz=qux", "--rm", "app", "rails", "s"]) } end context "when config with compose_method" do @@ -187,7 +187,7 @@ before { cli.start "run rails s".shellsplit } - it { expected_exec("docker-compose", ["up", "web"]) } + it { expected_exec("docker", ["compose", "up", "web"]) } end context "when config with environment vars" do @@ -199,8 +199,8 @@ before { cli.start "run rails refresh-test-db".shellsplit } it do - expected_exec("docker-compose", - ["run", "--rm", "app", "rake", "db:drop", "db:tests:prepare", "db:migrate"], + expected_exec("docker", + ["compose", "run", "--rm", "app", "rake", "db:drop", "db:tests:prepare", "db:migrate"], env: hash_including("RAILS_ENV" => "test")) end end @@ -217,7 +217,7 @@ before { cli.start "run rails all".shellsplit } - it { expected_exec("docker-compose", ["--profile", "foo", "--profile", "bar", "up", "app"]) } + it { expected_exec("docker", ["compose", "--profile", "foo", "--profile", "bar", "up", "app"]) } end end end diff --git a/spec/lib/dip/config_spec.rb b/spec/lib/dip/config_spec.rb index dd02235..165a749 100644 --- a/spec/lib/dip/config_spec.rb +++ b/spec/lib/dip/config_spec.rb @@ -15,7 +15,7 @@ end end - %i[environment compose interaction provision].each do |key| + %i[environment compose infra interaction provision].each do |key| describe "##{key}" do context "when config file doesn't exist", :env do let(:env) { {"DIP_FILE" => "no.yml"} } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b67df0c..42ba79f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,8 @@ require "dip" require "dip/run_vars" +require "fakefs/safe" + Dir["#{__dir__}/support/**/*.rb"].sort.each { |f| require f } RSpec.configure do |config|