dockhand 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +18 -0
- data/Rakefile +12 -0
- data/exe/dockhand +5 -0
- data/lib/dockhand/command.rb +253 -0
- data/lib/dockhand/version.rb +5 -0
- data/lib/dockhand.rb +4 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fb3565b6fe684a13404f48ae012095c671d1edd8b343cdae561b577dfec059e0
|
4
|
+
data.tar.gz: a848ca844f4e11abde42e535ba457a74469a86d4036c1a118a4607e7eb3e7b7c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc42d59510ccfd9e21b85cbdbcd448b907928e869b12dfa7a29eef7d04c0905ac9a711c9f5f1a6e4e21458f9e3efef86ac3f4c72674e9c18a2855b674d729d41
|
7
|
+
data.tar.gz: 34e9c0b08b8fe38a5b1c9b27ccbf74dc792466623922da5d5bae7a1e60afd362a1fb7b5b9cf69dd6beae61bb4a7957dea115253bc7312e8d3c7bb0b92d9a90ed
|
data/CHANGELOG.md
ADDED
File without changes
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Jonathan Hefner
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# dockhand
|
2
|
+
|
3
|
+
Toolkit for building Rails app Docker images.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Install the [`dockhand` gem](https://rubygems.org/gems/dockhand).
|
9
|
+
|
10
|
+
|
11
|
+
## Contributing
|
12
|
+
|
13
|
+
Run `rake test` to run the tests.
|
14
|
+
|
15
|
+
|
16
|
+
## License
|
17
|
+
|
18
|
+
[MIT License](LICENSE.txt)
|
data/Rakefile
ADDED
data/exe/dockhand
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler"
|
4
|
+
require "fileutils"
|
5
|
+
require "json"
|
6
|
+
require "pathname"
|
7
|
+
require "shellwords"
|
8
|
+
require "tmpdir"
|
9
|
+
require "thor"
|
10
|
+
|
11
|
+
class Dockhand::Command < Thor
|
12
|
+
include Thor::Actions
|
13
|
+
|
14
|
+
desc "install-packages [PACKAGES...]",
|
15
|
+
"Install apt packages."
|
16
|
+
method_option :buildtime, type: :boolean,
|
17
|
+
desc: "Include buildtime packages (e.g. `build-essential`)."
|
18
|
+
method_option :gem_buildtime, type: :boolean,
|
19
|
+
desc: "Include gem-related buildtime packages (e.g. `libsqlite3-dev` if using SQLite)."
|
20
|
+
method_option :gem_runtime, type: :boolean,
|
21
|
+
desc: "Include gem-related runtime packages (e.g. `libsqlite3-0` if using SQLite)."
|
22
|
+
method_option :clean, type: :boolean,
|
23
|
+
desc: "Clean apt cache directories after installing packages."
|
24
|
+
def install_packages(*packages)
|
25
|
+
packages.concat(essential_buildtime_packages) if options[:buildtime]
|
26
|
+
packages.concat(gem_buildtime_packages) if options[:gem_buildtime]
|
27
|
+
packages.concat(gem_runtime_packages) if options[:gem_runtime]
|
28
|
+
|
29
|
+
unless packages.empty?
|
30
|
+
run "apt-get update -qq"
|
31
|
+
run "apt-get install --no-install-recommends --yes", *packages
|
32
|
+
end
|
33
|
+
|
34
|
+
FileUtils.rm_rf(["/var/cache/apt", "/var/lib/apt"]) if options[:clean]
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
desc "transmute-to-artifacts [PATHS...]",
|
39
|
+
"Move files and directories to an artifacts directory, and replace the originals with symlinks."
|
40
|
+
method_option :artifacts_dir, default: "/artifacts",
|
41
|
+
desc: "The artifacts directory."
|
42
|
+
def transmute_to_artifacts(*paths)
|
43
|
+
paths.each do |path|
|
44
|
+
path = File.expand_path(path)
|
45
|
+
artifacts_dir = File.expand_path(options[:artifacts_dir])
|
46
|
+
artifacts_subpath = "#{artifacts_dir}/#{path}"
|
47
|
+
|
48
|
+
FileUtils.mkdir_p(File.dirname(artifacts_subpath))
|
49
|
+
FileUtils.mv(path, artifacts_subpath)
|
50
|
+
FileUtils.ln_s(artifacts_subpath, path)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
desc "install-gems",
|
56
|
+
"Install gems with Bundler."
|
57
|
+
method_option :clean, type: :boolean,
|
58
|
+
desc: "Clean Bundler cache after installing gems."
|
59
|
+
def install_gems
|
60
|
+
# Support for `BUNDLE_ONLY` was recently added in Bundler 2.4.0, but we can
|
61
|
+
# support `BUNDLE_ONLY` for older Bundler versions by converting the value
|
62
|
+
# to `BUNDLE_WITHOUT` (and updating `BUNDLE_WITH`).
|
63
|
+
#
|
64
|
+
# TODO Remove this hack when Bundler >= 2.4.0 is more widespread.
|
65
|
+
settings = [:without, :with].to_h { |key| [key, Bundler.settings[key].map(&:to_s)] }
|
66
|
+
only = Array(Bundler.settings[:only]).join(":").split(/\W/)
|
67
|
+
unless only.empty?
|
68
|
+
settings[:without] |= Bundler.definition.groups.map(&:to_s) - only
|
69
|
+
settings[:with] &= only
|
70
|
+
end
|
71
|
+
|
72
|
+
settings.each do |key, values|
|
73
|
+
run "bundle config set --local #{key} #{values.join(":").inspect}"
|
74
|
+
end
|
75
|
+
run "bundle install", env: { "BUNDLE_FROZEN" => "1" }
|
76
|
+
FileUtils.rm_rf("#{Bundler.bundle_path}/cache") if options[:clean]
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
desc "install-node",
|
81
|
+
"Install Node.js."
|
82
|
+
method_option :optional, type: :boolean,
|
83
|
+
desc: "Skips install if a .node-version or package.json file is not present."
|
84
|
+
method_option :prefix, default: "/usr/local",
|
85
|
+
desc: "The destination superdirectory. Files will be installed in `bin/`, `lib/`, etc."
|
86
|
+
def install_node
|
87
|
+
version_file = Dir["{.node-version,.nvmrc}"].first
|
88
|
+
|
89
|
+
if !version_file && !package_json.dig("engines", "node")
|
90
|
+
return if options[:optional] && !package_json_path
|
91
|
+
raise <<~ERROR
|
92
|
+
Missing Node.js version from `.node-version`, `.nvmrc`, or `package.json`.
|
93
|
+
|
94
|
+
You can create a version file by running the following command in the same directory as `package.json`:
|
95
|
+
|
96
|
+
$ node --version > .node-version
|
97
|
+
ERROR
|
98
|
+
end
|
99
|
+
|
100
|
+
Dir.mktmpdir do |tmp|
|
101
|
+
installer = "#{tmp}/n"
|
102
|
+
get "https://raw.githubusercontent.com/tj/n/HEAD/bin/n", installer
|
103
|
+
FileUtils.chmod("a+x", installer)
|
104
|
+
run installer, "lts" if !version_file && !which("node")
|
105
|
+
run installer, "auto", env: { "N_PREFIX" => options[:prefix] }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
desc "install-node-modules",
|
111
|
+
"Install Node.js modules using Yarn, NPM, or PNPM."
|
112
|
+
method_option :optional, type: :boolean,
|
113
|
+
desc: "Skips install if a `yarn.lock`, `package-lock.json`, or `pnpm-lock.yaml` file is not present."
|
114
|
+
def install_node_modules
|
115
|
+
lock_file = Dir["{yarn.lock,package-lock.json,pnpm-lock.yaml}"].first
|
116
|
+
|
117
|
+
if !lock_file
|
118
|
+
return if options[:optional] && !package_json_path
|
119
|
+
raise "Missing Node.js modules lock file (`yarn.lock`, `package-lock.json`, or `pnpm-lock.yaml`)"
|
120
|
+
end
|
121
|
+
|
122
|
+
run "npm install --global corepack" unless which("corepack")
|
123
|
+
run "corepack enable"
|
124
|
+
|
125
|
+
case lock_file
|
126
|
+
when "yarn.lock"
|
127
|
+
run "yarn install --frozen-lockfile"
|
128
|
+
when "package-lock.json"
|
129
|
+
run "npm ci"
|
130
|
+
when "pnpm-lock.yaml"
|
131
|
+
run "pnpm install --frozen-lockfile"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
desc "prepare-rails-app",
|
137
|
+
"Precompile assets, precompile code with Bootsnap, and normalize binstubs."
|
138
|
+
method_option :clean, type: :boolean,
|
139
|
+
desc: "Clean asset precompilation cache after precompiling."
|
140
|
+
def prepare_rails_app
|
141
|
+
Pathname.glob("bin/**/*") { |path| normalize_binstub(path) if path.file? }
|
142
|
+
run "bundle exec bootsnap precompile --gemfile app/ lib/" if gem?("bootsnap")
|
143
|
+
run "bin/rails assets:precompile", env: secret_key_base_dummy if rake_task?("assets:precompile")
|
144
|
+
FileUtils.rm_rf("tmp/cache/assets") if options[:clean]
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
desc "rails-entrypoint",
|
149
|
+
"Entrypoint for a Rails application."
|
150
|
+
def rails_entrypoint(*args)
|
151
|
+
if File.exist?("bin/docker-entrypoint")
|
152
|
+
exec("bin/docker-entrypoint", *args)
|
153
|
+
else
|
154
|
+
run "bin/rails db:prepare" if /\brails s(erver)?$/.match?(args[0..1].join(" "))
|
155
|
+
exec(*args)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
GEM_RUNTIME_PACKAGES = {
|
161
|
+
"mysql2" => %w[default-mysql-client],
|
162
|
+
"pg" => %w[postgresql-client],
|
163
|
+
"ruby-vips" => %w[libvips],
|
164
|
+
"sqlite3" => %w[libsqlite3-0],
|
165
|
+
}
|
166
|
+
|
167
|
+
GEM_BUILDTIME_PACKAGES = GEM_RUNTIME_PACKAGES.merge(
|
168
|
+
"mysql2" => %w[default-libmysqlclient-dev],
|
169
|
+
"pg" => %w[libpq-dev],
|
170
|
+
"sqlite3" => %w[libsqlite3-dev],
|
171
|
+
)
|
172
|
+
|
173
|
+
def essential_buildtime_packages
|
174
|
+
%w[build-essential pkg-config git python-is-python3 curl]
|
175
|
+
end
|
176
|
+
|
177
|
+
def gem_buildtime_packages
|
178
|
+
GEM_BUILDTIME_PACKAGES.slice(*gems).values.flatten
|
179
|
+
end
|
180
|
+
|
181
|
+
def gem_runtime_packages
|
182
|
+
GEM_RUNTIME_PACKAGES.slice(*gems).values.flatten
|
183
|
+
end
|
184
|
+
|
185
|
+
def gems
|
186
|
+
@gems ||= Bundler.settings.temporary(frozen: true) do
|
187
|
+
Bundler.definition.resolve.for(Bundler.definition.requested_dependencies).map(&:name)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def gem?(name)
|
192
|
+
gems.include?(name)
|
193
|
+
end
|
194
|
+
|
195
|
+
def rails_version
|
196
|
+
Bundler.definition.specs.find { |spec| spec.name == "rails" }.version.to_s
|
197
|
+
end
|
198
|
+
|
199
|
+
def secret_key_base_dummy
|
200
|
+
if !ENV["SECRET_KEY_BASE"] && !ENV["RAILS_MASTER_KEY"] && !File.exist?("config/master.key")
|
201
|
+
rails_version < "7.1" ? { "SECRET_KEY_BASE" => "1" } : { "SECRET_KEY_BASE_DUMMY" => "1" }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def rake_task?(name)
|
206
|
+
!`rake --tasks '^#{name}$'`.empty?
|
207
|
+
end
|
208
|
+
|
209
|
+
def package_json_path
|
210
|
+
(@package_json_paths ||= Pathname.glob("{.,*}/package.json")).first
|
211
|
+
end
|
212
|
+
|
213
|
+
def package_json
|
214
|
+
@package_json ||= package_json_path ? JSON.load_file(package_json_path) : {}
|
215
|
+
end
|
216
|
+
|
217
|
+
def normalize_binstub(path)
|
218
|
+
path.open("r+") do |file|
|
219
|
+
shebang = file.read(2)
|
220
|
+
|
221
|
+
if shebang == "#!"
|
222
|
+
shebang << file.gets.chomp!
|
223
|
+
if shebang.include?("ruby")
|
224
|
+
shebang = "#!/usr/bin/env #{File.basename Thor::Util.ruby_command}"
|
225
|
+
end
|
226
|
+
|
227
|
+
content = file.read
|
228
|
+
content.delete!("\r")
|
229
|
+
|
230
|
+
file.rewind
|
231
|
+
file.truncate(file.write(shebang, "\n", content))
|
232
|
+
path.chmod(0755 & ~File.umask)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def which(bin)
|
238
|
+
path = `sh -c 'command -v #{bin}'`
|
239
|
+
path unless path.empty?
|
240
|
+
end
|
241
|
+
|
242
|
+
def run(*cmd, env: nil)
|
243
|
+
cmd[0] = Shellwords.split(cmd[0]) if cmd[0].include?(" ")
|
244
|
+
cmd.flatten!
|
245
|
+
cmd.compact!
|
246
|
+
cmd.unshift(env) if env
|
247
|
+
system(*cmd, exception: true)
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.exit_on_failure?
|
251
|
+
true
|
252
|
+
end
|
253
|
+
end
|
data/lib/dockhand.rb
ADDED
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dockhand
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Hefner
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-03-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- jonathan@hefner.pro
|
30
|
+
executables:
|
31
|
+
- dockhand
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- CHANGELOG.md
|
36
|
+
- Gemfile
|
37
|
+
- LICENSE.txt
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- exe/dockhand
|
41
|
+
- lib/dockhand.rb
|
42
|
+
- lib/dockhand/command.rb
|
43
|
+
- lib/dockhand/version.rb
|
44
|
+
homepage: https://github.com/jonathanhefner/dockhand
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata:
|
48
|
+
homepage_uri: https://github.com/jonathanhefner/dockhand
|
49
|
+
source_code_uri: https://github.com/jonathanhefner/dockhand
|
50
|
+
changelog_uri: https://github.com/jonathanhefner/dockhand/blob/HEAD/CHANGELOG.md
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '2.7'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubygems_version: 3.4.1
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: Toolkit for building Rails app Docker images
|
70
|
+
test_files: []
|