devup 0.2.0 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16b7a8648ef05f7206cc02900450f8f303fc948a0976ac959472451eeef1988f
4
- data.tar.gz: a58834288dcd48d1cde4e353f1a77fb40b15419751896bdd7680b71be64a7266
3
+ metadata.gz: d83b3c98d30a71d2414a1fc5106d9a907612911f4b05a2246404f08215be203c
4
+ data.tar.gz: 772cee331bdcd822f497cc7121a6d6201b971b65b930e47864770d7cbb30df62
5
5
  SHA512:
6
- metadata.gz: 340f23ba29e0cd7e9c187f3ddb20d5e7c825503c5095fd4e3acd244ae2f714f89c2cfe636533207391292685244f4459b70af87a09ad82e7e8b3d9e70639aead
7
- data.tar.gz: d596434a6a8450bf01b433a2f2e4c3abbed3f465046463954595badb82468fabd0f3fc6479d0fe9935dc1bbd48b246e11aabd723251b81b2d288ff7d103b822c
6
+ metadata.gz: e29107cb8acd259d0e1f038e93cd7809e5b933abdedace59e17001e974220632ed95fe14aadb8e28007c6164665fbd4b4398268b1b476d35042c89457dde78b7
7
+ data.tar.gz: 7145ada30c07f14547ea5fc4c3d6ef9222a7dee288bb2d38b547a1907d844646e737d43a8f740668739465de60217d7e1a0501601618bb5e270c77e99903e713
data/.gitignore CHANGED
@@ -14,6 +14,8 @@
14
14
  # Editor config
15
15
  .vimrc
16
16
 
17
+ .env.services
17
18
  spec/dummy/.env.services
19
+ spec/dummy_rails/.env.services
18
20
 
19
21
  devup-*.gem
@@ -0,0 +1,2 @@
1
+ ignore:
2
+ - 'spec/dummy_rails/**/*'
@@ -5,6 +5,8 @@ language: ruby
5
5
  cache: bundler
6
6
  rvm:
7
7
  - 2.7.1
8
+ - 2.6.6
9
+ - 2.5.8
8
10
  services:
9
11
  - docker
10
12
  before_install:
data/Gemfile CHANGED
@@ -8,3 +8,4 @@ gem "rspec", "~> 3.0"
8
8
  gem "standard"
9
9
  gem "simplecov", require: false
10
10
  gem "byebug"
11
+ gem "lefthook"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- devup (0.2.0)
4
+ devup (0.3.3)
5
5
  dotenv
6
6
 
7
7
  GEM
@@ -14,6 +14,7 @@ GEM
14
14
  dotenv (2.7.5)
15
15
  jaro_winkler (1.5.4)
16
16
  json (2.3.0)
17
+ lefthook (0.7.2)
17
18
  parallel (1.19.1)
18
19
  parser (2.7.1.0)
19
20
  ast (~> 2.4.0)
@@ -60,6 +61,7 @@ PLATFORMS
60
61
  DEPENDENCIES
61
62
  byebug
62
63
  devup!
64
+ lefthook
63
65
  rake (~> 12.0)
64
66
  rspec (~> 3.0)
65
67
  simplecov
data/README.md CHANGED
@@ -1,96 +1,112 @@
1
- # DevUp!
1
+ # DevUp! [![Travis (.com) branch](https://img.shields.io/travis/com/sergio-fry/devup/master)](https://travis-ci.com/github/sergio-fry/devup) [![Gem](https://img.shields.io/gem/v/devup)](https://rubygems.org/gems/devup) [![Code Climate coverage](https://img.shields.io/codeclimate/coverage/sergio-fry/devup)](https://codeclimate.com/github/sergio-fry/devup) [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/sergio-fry/devup)](https://codeclimate.com/github/sergio-fry/devup) [![Gem](https://img.shields.io/gem/dt/devup)](https://rubygems.org/gems/devup)
2
2
 
3
- [![Travis (.com) branch](https://img.shields.io/travis/com/sergio-fry/devup/master)](https://travis-ci.com/github/sergio-fry/devup)
4
- [![Gem](https://img.shields.io/gem/v/devup)](https://rubygems.org/gems/devup)
5
- [![Code Climate coverage](https://img.shields.io/codeclimate/coverage/sergio-fry/devup)](https://codeclimate.com/github/sergio-fry/devup)
6
- [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/sergio-fry/devup)](https://codeclimate.com/github/sergio-fry/devup)
7
- [![Gem](https://img.shields.io/gem/dt/devup)](https://rubygems.org/gems/devup)
3
+ Describe development dependencies with docker-compose. It is not required to remember any fancy command to start docker. Just start developing your app. Rails is a first-class citizen, but could be used without ruby.
8
4
 
9
- **DevUp!** is a tool to run dev dependencies. It builds ENV vars with dynamic exposed ports for services defined in a docker-compose.yml to access from application.
10
5
 
11
- ![demo](demo.gif)
6
+ ## Requirements
12
7
 
13
- ## Installation
8
+ * Docker (>= 19.03.8)
9
+ * Docker Compose (>= 1.25.5)
14
10
 
15
- $ gem install devup
16
11
 
17
- ## Usage
18
12
 
13
+ ## Rails
19
14
 
20
- Create a docker-compose.yml with app dependencies like:
15
+ Create a docker-compose.devup.yml with app dependencies like:
21
16
 
22
17
  ```yaml
23
- version: '3'
18
+ version: "3"
24
19
 
25
20
  services:
26
21
  postgres:
27
- image: postgres
22
+ image: postgres:10-alpine
28
23
  ports:
29
24
  - "5432"
30
25
  ```
31
26
 
32
- Add devup/dotenv to your Gemfile:
33
-
34
- gem "devup"
27
+ Add **DevUp!** to your Gemfile
35
28
 
36
- For each service from docker-compose.yml **DevUp!** will export ENV variable like
29
+ ```ruby
30
+ gem "devup", group: [:development, :test]
31
+ ```
37
32
 
38
- POSTGRES_HOST=0.0.0.0
39
- POSTGRES_PORT=5432
33
+ and
40
34
 
41
- ### Rails
35
+ $ bundle install
42
36
 
43
37
 
44
38
  Update your database.yml to use ENV:
45
39
 
46
40
  ```yaml
47
- default: &default
48
- adapter: postgresql
49
- encoding: unicode
50
- host: <%= ENV.fetch("POSTGRES_HOST") %>
51
- port: <%= ENV.fetch("POSTGRES_PORT") %>
52
- username: postgres
53
- password:
41
+ test:
42
+ url: <%= ENV.fetch("DATABASE_URL") %>
54
43
 
55
44
  development:
56
- <<: *default
57
- database: development
45
+ url: <%= ENV.fetch("DATABASE_URL") %>
58
46
 
59
- test:
60
- <<: *default
61
- database: test
62
47
  ```
63
48
 
64
49
 
65
- You are ready to start rails
50
+ You are ready to use rails with PostgreSQL configured
51
+
52
+ $ RAILS_ENV=test bundle exec rake db:create
53
+ DevUp! INFO starting up...
54
+ DevUp! INFO up
66
55
 
67
- $ bundle exec rake db:create db:migrate
68
- $ bundle exec rails server
56
+ $ Created database 'dummy_rails_test'
69
57
 
70
58
 
71
- ### Without Rails
59
+ ## Without Rails
60
+
61
+ ENV vars from .env.services are loaded with dotenv automatically.
72
62
 
73
63
 
74
64
  ```ruby
75
65
  require "devup"
76
66
  require "sequel"
77
67
 
78
- DB = Sequel.connect("postgres://postgres@#{ENV.fetch( "POSTGRES_HOST" )}:#{ENV.fetch("POSTGRES_PORT")}/database_name")
68
+ DB = Sequel.connect(ENV.fetch("DATABASE_URL"))
79
69
  ```
80
70
 
81
71
 
82
- ### Without Ruby (PHP, nodejs, Java, ...)
72
+ ## Without Ruby (PHP, nodejs, Java, ...)
73
+
74
+ Install DevUp!
75
+
76
+ $ gem install devup
83
77
 
84
78
  Start up services
85
79
 
86
80
  $ devup
81
+ DevUp! INFO starting up...
82
+ DevUp! INFO up
83
+
84
+ $ cat .env.services
85
+ export POSTGRES_HOST=0.0.0.0
86
+ export POSTGRES_PORT=32944
87
+ export POSTGRES_PORT_5432=32944
88
+ export MEMCACHED_HOST=0.0.0.0
89
+ export MEMCACHED_PORT=32943
90
+ ...
87
91
 
88
- Load ENV vars from generated .env.services
92
+ Use your favourite dotenv extension to load vars from .env.services ([node-dotenv](https://www.npmjs.com/package/dotenv), [python-dotenv](https://pypi.org/project/python-dotenv/), [phpdotenv](https://github.com/vlucas/phpdotenv), ...)
93
+
94
+ Or load ENV vars manually
89
95
 
90
96
  $ source .env.services
91
97
 
92
98
  Now you can run app
93
99
 
100
+ ## How To
101
+
102
+ ### Disable **DevUp!**
103
+
104
+ If you don't want devup to setup your dev services, you can disable it by using `DEVUP_ENABLED=false`. Just add it to .env.local file.
105
+
106
+ ### Debug
107
+
108
+ $ export DEVUP_LOG_LEVEL=debug
109
+ $ devup
94
110
 
95
111
  ## Development
96
112
 
@@ -0,0 +1,8 @@
1
+ version: '3'
2
+
3
+ services:
4
+ postgres:
5
+ image: postgres:11
6
+ ports:
7
+ - "5432"
8
+
data/exe/devup CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  require "devup/environment"
4
4
 
5
- devup = Devup::Environment.new pwd: `pwd`
5
+ devup = Devup::Environment.new(
6
+ pwd: `pwd`,
7
+ logger: Devup::Logger.build(ENV.fetch("DEVUP_LOG_LEVEL", "info"))
8
+ )
6
9
 
7
10
  # TODO: use dry-cli here
8
11
  case ARGV[0]
@@ -0,0 +1,7 @@
1
+ pre-commit:
2
+ parallel: true
3
+ commands:
4
+ lint:
5
+ files: git diff --name-only --staged
6
+ glob: "*.rb"
7
+ run: standardrb --force-exclusion {files}
@@ -1,2 +1,15 @@
1
- devup = Devup::Environment.new pwd: `pwd`
2
- devup.up
1
+ if ENV.fetch("DEVUP_ENABLED", "true") == "true"
2
+ devup = Devup::Environment.new(
3
+ pwd: `pwd`,
4
+ logger: Devup::Logger.build(ENV.fetch("DEVUP_LOG_LEVEL", "info"))
5
+ )
6
+ devup.up
7
+
8
+ begin
9
+ require "spring/commands"
10
+
11
+ Spring.watch devup.root.join("docker-compose.yml")
12
+ rescue LoadError, ArgumentError
13
+ # Spring is not available
14
+ end
15
+ end
@@ -1,12 +1,22 @@
1
1
  require "yaml"
2
2
 
3
+ require "devup/compose/ps"
4
+
3
5
  module Devup
4
6
  class Compose
5
- attr_reader :path, :project
7
+ attr_reader :path, :project, :logger, :shell
8
+
9
+ class Error < StandardError; end
6
10
 
7
- def initialize(path, project: "devup")
11
+ def initialize(path, project: "devup", logger:, shell:)
8
12
  @path = path
9
13
  @project = project
14
+ @logger = logger
15
+ @shell = shell
16
+ end
17
+
18
+ def check
19
+ true
10
20
  end
11
21
 
12
22
  def services
@@ -14,11 +24,17 @@ module Devup
14
24
  end
15
25
 
16
26
  def service_ports(name)
17
- config["services"][name]["ports"]
27
+ config["services"][name]["ports"].map { |el| el.to_s.split(":")[-1].to_i }
28
+ end
29
+
30
+ def port_mapping(service, port)
31
+ exec("port #{service} #{port}").split(":")[-1].strip.to_i
18
32
  end
19
33
 
20
34
  def up
21
35
  exec "up -d --remove-orphans"
36
+
37
+ wait_alive 3
22
38
  end
23
39
 
24
40
  def stop
@@ -29,11 +45,33 @@ module Devup
29
45
  exec "rm -f"
30
46
  end
31
47
 
32
- def exec(cmd)
33
- `docker-compose -p #{project} -f #{path} #{cmd}`
48
+ private
49
+
50
+ def wait_alive(timeout, retry_sleep: 0.3)
51
+ start = Time.now
52
+
53
+ loop {
54
+ break if alive?
55
+
56
+ if Time.now - start > timeout
57
+ logger.error "can't run services"
58
+ break
59
+ end
60
+ sleep retry_sleep
61
+ }
34
62
  end
35
63
 
36
- private
64
+ def alive?
65
+ ComposeHelpers::Ps.new(exec("ps")).up?
66
+ end
67
+
68
+ def exec(cmd)
69
+ resp = shell.exec "docker-compose -p #{project} -f #{path} #{cmd}"
70
+
71
+ raise(Error) unless resp.success?
72
+
73
+ resp.data
74
+ end
37
75
 
38
76
  def config
39
77
  YAML.safe_load(config_content)
@@ -0,0 +1,30 @@
1
+ module Devup
2
+ module ComposeHelpers
3
+ class Ps
4
+ attr_reader :output
5
+ def initialize(output)
6
+ @output = output
7
+ end
8
+
9
+ def up?
10
+ service_lines.map { |line|
11
+ line.match(/Up/) && !line.match(/Exit/)
12
+ }.all?
13
+ end
14
+
15
+ def port_mapping(port)
16
+ m = output.match(/\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}:(\d+)->#{port}\/tcp/)
17
+
18
+ return if m.nil?
19
+
20
+ m[1].to_i
21
+ end
22
+
23
+ private
24
+
25
+ def service_lines
26
+ output.split("\n")[2..-1]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,52 +1,51 @@
1
1
  require "yaml"
2
2
 
3
+ require "devup/logger"
3
4
  require "devup/compose"
4
5
  require "devup/service"
6
+ require "devup/service_presenter"
7
+ require "devup/shell"
5
8
 
6
9
  module Devup
7
10
  class Environment
8
- attr_reader :pwd, :compose
11
+ attr_reader :pwd, :logger, :shell
9
12
 
10
- def initialize(pwd:, compose: nil)
13
+ def initialize(pwd:, compose: nil, logger: Logger.build, shell: Shell.new(pwd: pwd, logger: logger))
11
14
  @pwd = pwd.to_s.strip
12
- @compose = compose || Compose.new(root.join("docker-compose.yml"), project: project)
15
+ @compose = compose
16
+ @logger = logger
17
+ @shell = shell
13
18
  end
14
19
 
15
20
  def project
16
21
  pwd.split("/")[-1].strip
17
22
  end
18
23
 
19
- def vars
20
- compose.services.map { |name| Service.new(compose, name) }.map { |service|
21
- res = []
22
-
23
- res << {"#{service.name}_HOST".upcase => "0.0.0.0"}
24
-
25
- if service.ports.size > 0
26
- res << {"#{service.name}_PORT".upcase => service.ports.first.to}
27
-
28
- service.ports.each do |port|
29
- res << {"#{service.name}_PORT_#{port.from}".upcase => port.to}
30
- end
31
- end
32
-
33
- res
34
- }.flatten
35
- end
36
-
37
24
  def env
38
- vars.reduce({}, :merge).map { |k, v| "export #{k}=#{v}" }.join("\n")
25
+ services.map { |s| service_env(s) }.join("\n\n")
39
26
  end
40
27
 
41
28
  def up
29
+ logger.info "starting up..."
30
+ check
42
31
  compose.up
43
32
  write_dotenv
33
+ logger.info "up"
34
+ rescue => ex
35
+ clear_dotenv
36
+ logger.debug ex
37
+ logger.error "halted"
44
38
  end
45
39
 
46
40
  def down
41
+ logger.info "shutting down..."
47
42
  compose.stop
48
43
  compose.rm
49
44
  clear_dotenv
45
+ logger.info "down"
46
+ rescue => ex
47
+ logger.debug ex
48
+ logger.info "halted"
50
49
  end
51
50
 
52
51
  def root
@@ -55,6 +54,29 @@ module Devup
55
54
 
56
55
  private
57
56
 
57
+ def check
58
+ raise if missing_config
59
+ compose.check
60
+ end
61
+
62
+ def missing_config
63
+ if File.exist?(compose.path)
64
+ false
65
+ else
66
+ logger.error "missing #{compose.path}"
67
+
68
+ true
69
+ end
70
+ end
71
+
72
+ def services
73
+ @services ||= compose.services.map { |name| Service.new(compose, name) }
74
+ end
75
+
76
+ def service_env(service)
77
+ ServicePresenter.new(service, project: project).call
78
+ end
79
+
58
80
  def write_dotenv
59
81
  File.open(root.join(".env.services"), "w") { |f| f.write dotenv }
60
82
  end
@@ -70,10 +92,21 @@ module Devup
70
92
  # Home: https://github.com/sergio-fry/devup #
71
93
  ####################################################
72
94
  # START
95
+
73
96
  #{env}
97
+
74
98
  # END
75
99
 
76
100
  DOTENV
77
101
  end
102
+
103
+ def compose
104
+ @compose ||= begin
105
+ Compose.new(
106
+ root.join("docker-compose.devup.yml"),
107
+ project: project, logger: logger, shell: shell
108
+ )
109
+ end
110
+ end
78
111
  end
79
112
  end
@@ -0,0 +1,14 @@
1
+ require "logger"
2
+
3
+ module Devup
4
+ class Logger < ::Logger
5
+ def self.build(level = :info)
6
+ formatter = ->(severity, _time, _progname, msg) { "DevUp! #{severity} #{msg}\n" }
7
+ logger = new(STDOUT, formatter: formatter)
8
+
9
+ logger.level = level
10
+
11
+ logger
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require "socket"
2
+ require "timeout"
3
+
4
+ module Devup
5
+ class PortChecker
6
+ def call(port)
7
+ s = TCPSocket.new("0.0.0.0", port)
8
+ s.write "1"
9
+ s.close
10
+
11
+ true
12
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
13
+ false
14
+ end
15
+ end
16
+ end
@@ -10,10 +10,16 @@ module Devup
10
10
  end
11
11
 
12
12
  def ports
13
- compose.service_ports(name).map { |el| el.to_s.split(":")[-1] }.map do |from|
13
+ @ports ||= fetch_ports
14
+ end
15
+
16
+ private
17
+
18
+ def fetch_ports
19
+ compose.service_ports(name).map do |from|
14
20
  OpenStruct.new(
15
21
  from: from,
16
- to: compose.exec("port #{name} #{from}").split(":")[-1].strip
22
+ to: compose.port_mapping(name, from)
17
23
  )
18
24
  end
19
25
  end
@@ -0,0 +1,63 @@
1
+ module Devup
2
+ class ServicePresenter
3
+ attr_reader :service, :project
4
+
5
+ def initialize(service, project: nil)
6
+ @service = service
7
+ @project = project
8
+ end
9
+
10
+ def call
11
+ res = []
12
+
13
+ res << "# #{service.name}"
14
+
15
+ if has_ports?
16
+ res << host_env
17
+ res << ports_env
18
+ else
19
+ res << "# no exposed ports"
20
+ end
21
+
22
+ res.join "\n"
23
+ end
24
+
25
+ private
26
+
27
+ def host_env
28
+ "export #{service.name.upcase}_HOST=0.0.0.0"
29
+ end
30
+
31
+ def ports_env
32
+ res = []
33
+
34
+ res << port_env(to: service.ports.first.to) if service.ports.size == 1
35
+
36
+ service.ports.each do |port|
37
+ res << port_env(from: port.from, to: port.to)
38
+ end
39
+
40
+ res.join "\n"
41
+ end
42
+
43
+ def port_env(from: nil, to:)
44
+ if from.nil?
45
+ "export #{service.name.upcase}_PORT=#{to}"
46
+ else
47
+ "export #{service.name.upcase}_PORT_#{from}=#{to}"
48
+ end
49
+ end
50
+
51
+ def has_ports?
52
+ service.ports.size > 0
53
+ end
54
+
55
+ def port_to(from)
56
+ service.ports.find { |p| p.from == from }&.to
57
+ end
58
+
59
+ def database_name
60
+ "db"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,28 @@
1
+ require "open3"
2
+
3
+ module Devup
4
+ class Shell
5
+ attr_reader :pwd, :logger
6
+
7
+ def initialize(pwd:, logger:)
8
+ @pwd = pwd
9
+ @logger = logger
10
+ end
11
+
12
+ Result = Struct.new(:data, :status) {
13
+ def success?
14
+ status
15
+ end
16
+ }
17
+
18
+ def exec(cmd)
19
+ logger.debug "$ #{cmd}"
20
+
21
+ output, error, status = Open3.capture3(cmd + ";")
22
+
23
+ logger.error(error) unless status.success?
24
+
25
+ Result.new(output, status.success?)
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module Devup
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergei O. Udalov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-05 00:00:00.000000000 Z
11
+ date: 2020-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -35,6 +35,7 @@ files:
35
35
  - ".gitignore"
36
36
  - ".rspec"
37
37
  - ".ruby-version"
38
+ - ".standard.yml"
38
39
  - ".travis.yml"
39
40
  - ".vimrc"
40
41
  - CODE_OF_CONDUCT.md
@@ -45,15 +46,21 @@ files:
45
46
  - Rakefile
46
47
  - bin/console
47
48
  - bin/setup
48
- - demo.gif
49
49
  - devup.gemspec
50
+ - docker-compose.devup.yml
50
51
  - exe/devup
52
+ - lefthook.yml
51
53
  - lib/devup.rb
52
54
  - lib/devup/boot.rb
53
55
  - lib/devup/compose.rb
56
+ - lib/devup/compose/ps.rb
54
57
  - lib/devup/dotenv.rb
55
58
  - lib/devup/environment.rb
59
+ - lib/devup/logger.rb
60
+ - lib/devup/port_checker.rb
56
61
  - lib/devup/service.rb
62
+ - lib/devup/service_presenter.rb
63
+ - lib/devup/shell.rb
57
64
  - lib/devup/version.rb
58
65
  homepage: https://github.com/sergio-fry/devup
59
66
  licenses:
data/demo.gif DELETED
Binary file