dockhand 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in dockhand.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
data/exe/dockhand ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "dockhand"
5
+ Dockhand::Command.start
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dockhand
4
+ VERSION = "0.1.0"
5
+ end
data/lib/dockhand.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dockhand/version"
4
+ require_relative "dockhand/command"
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: []