dockerfile-rails 1.3.0 → 1.4.1

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: 8780a8a7015d339452edf4dff330981a748ad72c72081e0cfa473c835121637c
4
- data.tar.gz: ab37e68fe9d3f82f9368e45738a12f171eaaabcff8b311451d89bdbce1217d95
3
+ metadata.gz: 70cab322c4f52cdddb9934de220be781a87ef306af7a21bf1db8a15c8db16a3a
4
+ data.tar.gz: a8363fcbdd774e3a2b3254f274366c0ad0f700eabdc90502a189c727005de25e
5
5
  SHA512:
6
- metadata.gz: 8a1bce2d42a5916ae7bf4cc49f23d0b9077533e1447015f35bfd4835397438a8bb4bc13e48a774a7e80130cf0f99dada597fda1fa0cada1674e52073d7846fc1
7
- data.tar.gz: 9793fdc06ffe7a84a1e0f40282570ea5e4ad6e08514ff8210f805254a093208cc4b98f683c44fce299dc0b71ff04c2418b847c5e33f05da407cefd617412521b
6
+ metadata.gz: 10fe62c57fe8f2088bbe0b58feafc4199e2591cf214d39e410d1e78be3f5039187ee28ed9279f774eb02ec991bd46318a014fa324bfc57fe16c9398c51844e10
7
+ data.tar.gz: 9bcc4bca316a3c4eb96a00060e1f3cbc45072975a28c16248353947da1e9259121fdbf7213e3a382cd407f48a1eb0d1b3baec65a9461242f1269f376a887d291
data/README.md CHANGED
@@ -52,6 +52,7 @@ Generally the dockerfile generator will be able to determine what dependencies y
52
52
  are actually using. But should you be using DATABASE_URL, for example, at runtime
53
53
  additional support may be needed:
54
54
 
55
+ * `--litefs` - use [LiteFS](https://fly.io/docs/litefs/)
55
56
  * `--mysql` - add mysql libraries
56
57
  * `--postgresql` - add postgresql libraries
57
58
  * `--redis` - add redis libraries
@@ -110,9 +111,9 @@ If you are running a single test, the following environment variables settings m
110
111
  * `TEST_CAPTURE=1` will capture test results.
111
112
  * `TEST_KEEP=1` will leave the test app behind for inspection after the test completes.
112
113
 
113
- ## Links
114
+ ## Historical Links
114
115
 
115
- The following links relate to the current development status with respect to Rails 7.1 and will be removed once that is resolved.
116
+ The following links relate to the coordination between this package and Rails 7.1.
116
117
 
117
118
  * [Preparations for Rails 7.1](https://community.fly.io/t/preparations-for-rails-7-1/9512) - [Fly.io](https://fly.io/)'s plans and initial discussions with DHH
118
119
  * [Rails Dockerfile futures](https://discuss.rubyonrails.org/t/rails-dockerfile-futures/82091/1) - rationale for a generator
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "erb"
4
+ require "json"
4
5
  require_relative "../dockerfile-rails/scanner.rb"
5
6
 
6
7
  class DockerfileGenerator < Rails::Generators::Base
@@ -15,6 +16,7 @@ class DockerfileGenerator < Rails::Generators::Base
15
16
  "jemalloc" => false,
16
17
  "label" => {},
17
18
  "link" => true,
19
+ "litefs" => false,
18
20
  "lock" => true,
19
21
  "max-idle" => nil,
20
22
  "mysql" => false,
@@ -111,6 +113,9 @@ class DockerfileGenerator < Rails::Generators::Base
111
113
  class_option :sqlite3, aliases: "--sqlite", type: :boolean, default: OPTION_DEFAULTS.sqlite3,
112
114
  desc: "include sqlite3 libraries"
113
115
 
116
+ class_option :litefs, type: :boolean, default: OPTION_DEFAULTS.litefs,
117
+ desc: "replicate sqlite3 databases using litefs"
118
+
114
119
  class_option :postgresql, aliases: "--postgres", type: :boolean, default: OPTION_DEFAULTS.postgresql,
115
120
  desc: "include postgresql libraries"
116
121
 
@@ -233,6 +238,23 @@ class DockerfileGenerator < Rails::Generators::Base
233
238
 
234
239
  template "docker-compose.yml.erb", "docker-compose.yml" if options.compose
235
240
 
241
+ if using_litefs?
242
+ template "litefs.yml.erb", "config/litefs.yml"
243
+
244
+ fly_attach_consul
245
+ end
246
+
247
+ if fly_processes # therefore File.exist?('fly.toml')
248
+ if File.stat("fly.toml").size > 0
249
+ template "fly.toml.erb", "fly.toml"
250
+ else
251
+ toml = fly_make_processes(fly_processes)
252
+ if toml != IO.read("fly.toml")
253
+ File.write "fly.toml", toml
254
+ end
255
+ end
256
+ end
257
+
236
258
  if @gemfile.include?("vite_ruby")
237
259
  package = JSON.load_file("package.json")
238
260
  unless package.dig("scripts", "build")
@@ -296,6 +318,10 @@ private
296
318
  options.root?
297
319
  end
298
320
 
321
+ def using_litefs?
322
+ options.litefs?
323
+ end
324
+
299
325
  def using_node?
300
326
  return @using_node if @using_node != nil
301
327
  @using_node = File.exist? "package.json"
@@ -495,6 +521,9 @@ private
495
521
  packages << "default-mysql-client" if options.mysql? || @mysql
496
522
  packages << "libjemalloc2" if options.jemalloc? && !options.fullstaq?
497
523
 
524
+ # litefs
525
+ packages += ["ca-certificates", "fuse3", "sudo"] if options.litefs?
526
+
498
527
  # ActiveStorage preview support
499
528
  packages << "libvips" if @gemfile.include? "ruby-vips"
500
529
 
@@ -601,13 +630,21 @@ private
601
630
  def deploy_env
602
631
  env = {}
603
632
 
604
- env["PORT"] = "3001" if options.nginx? && !using_passenger?
633
+ env["PORT"] = "3001" if (options.nginx? && !using_passenger?) || using_litefs?
605
634
 
606
635
  if Rails::VERSION::MAJOR < 7 || Rails::VERSION::STRING.start_with?("7.0")
607
636
  env["RAILS_LOG_TO_STDOUT"] = "1"
608
637
  env["RAILS_SERVE_STATIC_FILES"] = "true" unless options.nginx?
609
638
  end
610
639
 
640
+ if deploy_database == "sqlite3"
641
+ if using_litefs?
642
+ env["DATABASE_URL"] = "sqlite3:///litefs/production.sqlite3"
643
+ else
644
+ env["DATABASE_URL"] = "sqlite3:///data/production.sqlite3"
645
+ end
646
+ end
647
+
611
648
  if options.yjit?
612
649
  env["RUBY_YJIT_ENABLE"] = "1"
613
650
  end
@@ -639,7 +676,7 @@ private
639
676
 
640
677
  env.merge! @@vars["base"] if @@vars["base"]
641
678
 
642
- env.map { |key, value| "#{key}=#{value.inspect}" }
679
+ env.map { |key, value| "#{key}=#{value.inspect}" }.sort
643
680
  end
644
681
 
645
682
  def base_args
@@ -716,9 +753,11 @@ private
716
753
  end
717
754
 
718
755
  def deploy_database
719
- if options.postgresql? || @postgresql
756
+ # note: as database can be overridden at runtime via DATABASE_URL,
757
+ # use presence of "pg" or "mysql2" in the bundle as evidence of intent.
758
+ if options.postgresql? || @postgresql || @gemfile.include?("pg")
720
759
  "postgresql"
721
- elsif options.mysql? || @mysql
760
+ elsif options.mysql? || @mysql || @gemfile.include?("mysql2")
722
761
  "mysql"
723
762
  else
724
763
  "sqlite3"
@@ -823,6 +862,21 @@ private
823
862
  end
824
863
  end
825
864
 
865
+ def fly_processes
866
+ return unless File.exist? "fly.toml"
867
+ return unless using_sidekiq?
868
+
869
+ if procfile.size > 1
870
+ list = { "app" => "foreman start --procfile=Procfile.prod" }
871
+ else
872
+ list = { "app" => procfile.values.first }
873
+ end
874
+
875
+ list["sidekiq"] = "bundle exec sidekiq"
876
+
877
+ list
878
+ end
879
+
826
880
  def more_docker_ignores
827
881
  more = ""
828
882
 
@@ -853,4 +907,54 @@ private
853
907
  rescue ArgumentError
854
908
  nil
855
909
  end
910
+
911
+ # if running on fly v2, make a best effort to attach consul
912
+ def fly_attach_consul
913
+ # certainly not fly unless there is a fly.toml
914
+ return unless File.exist? "fly.toml"
915
+
916
+ # Check fly.toml to guess if v1 or v2
917
+ toml = File.read("fly.toml")
918
+ return if toml.include?("enable_consul") # v1-ism
919
+ return unless toml.include?("primary_region") # v2
920
+
921
+ # see if flyctl is in the path
922
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR)
923
+ cmds = %w(flyctl)
924
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
925
+ flyctl = Enumerator.product(paths, cmds, exts).
926
+ map { |path, cmd, ext| File.join(path, "#{cmd}#{ext}") }.
927
+ find { |path| File.executable? path }
928
+ return unless flyctl
929
+
930
+ # see if secret is already set?
931
+ begin
932
+ secrets = JSON.parse(`#{flyctl} secrets list --json`)
933
+ return if secrets.any? { |secret| secret["Name"] == "FLY_CONSUL_URL" }
934
+ rescue
935
+ return # likely got an error like "Could not find App"
936
+ end
937
+
938
+ # attach consul
939
+ say_status :execute, "flyctl consul attach", :green
940
+ system "#{flyctl} consul attach"
941
+ end
942
+
943
+ def fly_make_processes(list)
944
+ toml = File.read("fly.toml")
945
+
946
+ if toml.include? "[processes]"
947
+ toml.sub!(/\[processes\].*?(\n\n|\n?\z)/m, "[processes]\n" +
948
+ list.map { |name, cmd| " #{name} = #{cmd.inspect}" }.join("\n") + '\1')
949
+ else
950
+ toml += "\n[processes]\n" +
951
+ list.map { |name, cmd| " #{name} = #{cmd.inspect}\n" }.join
952
+
953
+ app = list.has_key?("app") ? "app" : list.keys.first
954
+
955
+ toml.sub! "[http_service]\n", "\\0 processes = [#{app.inspect}]\n"
956
+ end
957
+
958
+ toml
959
+ end
856
960
  end
@@ -134,6 +134,12 @@ RUN SECRET_KEY_BASE<%= Rails::VERSION::MAJOR<7 || Rails::VERSION::STRING.start_w
134
134
  # Final stage for app image
135
135
  FROM base
136
136
 
137
+ <% end -%>
138
+ <% if using_litefs? -%>
139
+ # Install, configure litefs
140
+ COPY --from=flyio/litefs:0.4.0 /usr/local/bin/litefs /usr/local/bin/litefs
141
+ COPY<% if options.link? %> --link<% end %> config/litefs.yml /etc/litefs.yml
142
+
137
143
  <% end -%>
138
144
  <% unless deploy_args.empty? -%>
139
145
  # Deployment build arguments
@@ -166,7 +172,11 @@ COPY --from=client /rails/<%= api_client_dir %>/build /rails/public
166
172
  <% end -%>
167
173
 
168
174
  <% end -%>
169
- <% unless run_as_root? -%>
175
+ <% if run_as_root? -%>
176
+ <% if deploy_database == 'sqlite3' -%>
177
+ RUN mkdir /data
178
+ <% end -%>
179
+ <% else -%>
170
180
  # Run and own only the runtime files as a non-root user for security
171
181
  <% if options.compose? -%>
172
182
  ARG UID=1000 \
@@ -182,11 +192,23 @@ RUN useradd rails --create-home --shell /bin/bash && \
182
192
  <% if deploy_packages.include?("sudo") && options.sudo? -%>
183
193
  sed -i 's/env_reset/env_keep="*"/' /etc/sudoers && \
184
194
  <% end -%>
195
+ <% if deploy_database == 'sqlite3' -%>
196
+ mkdir /data<% if using_litefs? %> /litefs<% end %> && \
197
+ chown -R rails:rails <%= Dir[*%w(db log storage tmp)].join(" ") %> /data<% if using_litefs? %> /litefs<% end %>
198
+ <% else -%>
185
199
  chown -R rails:rails <%= Dir[*%w(db log storage tmp)].join(" ") %>
186
- <% unless options.swap? or using_passenger? -%>
200
+ <% end -%>
201
+ <% unless options.swap? or using_passenger? or using_litefs? -%>
187
202
  USER rails:rails
188
203
  <% end -%>
189
204
 
205
+ <% end -%>
206
+ <% if using_litefs? and !run_as_root? -%>
207
+ # Authorize rails user to launch litefs
208
+ COPY <<-"EOF" /etc/sudoers.d/rails
209
+ rails ALL=(root) /usr/local/bin/litefs
210
+ EOF
211
+
190
212
  <% end -%>
191
213
  <% unless deploy_env.empty? -%>
192
214
  # Deployment options
@@ -208,11 +230,16 @@ COPY <<-"EOF" /rails/Procfile.prod
208
230
  <% end -%>
209
231
  EOF
210
232
 
233
+ <% end -%>
211
234
  # Start the server by default, this can be overwritten at runtime
212
235
  EXPOSE 3000
236
+ <% if deploy_database == 'sqlite3' -%>
237
+ VOLUME /data
238
+ <% end -%>
239
+ <% unless fly_processes -%>
240
+ <% if procfile.size > 1 -%>
213
241
  CMD ["foreman", "start", "--procfile=Procfile.prod"]
214
242
  <% else -%>
215
- # Start the server by default, this can be overwritten at runtime
216
- EXPOSE 3000
217
243
  CMD <%= procfile.values.first.split(" ").inspect %>
218
244
  <% end -%>
245
+ <% end -%>
@@ -82,7 +82,7 @@ services:
82
82
 
83
83
  sidekiq:
84
84
  build: .
85
- command: bin/sidekiq
85
+ command: bunde exec sidekiq
86
86
  environment:
87
87
  - RAILS_MASTER_KEY=$RAILS_MASTER_KEY
88
88
  - REDIS_URL=redis://redis-db:6379
@@ -14,11 +14,21 @@ if [ $UID -eq 0 ]; then
14
14
  <%= @space %>echo 10 > /proc/sys/vm/swappiness
15
15
  <%= @space %>swapon /swapfile
16
16
  <%= @space %>echo 1 > /proc/sys/vm/overcommit_memory
17
+ <% if using_litefs? -%>
18
+
19
+ <%= @space %># mount litefs
20
+ <%= @space %>litefs mount &
21
+ <% end -%>
17
22
  <% unless run_as_root? or using_passenger? -%>
23
+
18
24
  exec su rails $0 $@
19
25
  fi
20
26
  <% end -%>
21
27
 
28
+ <% elsif using_litefs? -%>
29
+ # mount litefs
30
+ <% unless run_as_root? %>sudo -E <% end %>litefs mount &
31
+
22
32
  <% end -%>
23
33
  <% if options.prepare -%>
24
34
  <% if procfile.size > 1 -%>
@@ -26,7 +36,7 @@ fi
26
36
  if [ "${*}" == "foreman start --procfile=Procfile.prod" ]; then
27
37
  <% else -%>
28
38
  # If running the rails server then create or migrate existing database
29
- if [ "${*}" == <%= procfile.values.first.inspect %> ]; then
39
+ if [ "${*}" == <%= procfile.values.first.inspect %> <% if using_litefs? %>-a "$FLY_REGION" == "$PRIMARY_REGION" <%end%>]; then
30
40
  <% end -%>
31
41
  <% if options.precompile == "defer" -%>
32
42
  ./bin/rails assets:precompile
@@ -0,0 +1 @@
1
+ <%= fly_make_processes(fly_processes) -%>
@@ -0,0 +1,116 @@
1
+ # based on: https://github.com/superfly/litefs/blob/main/cmd/litefs/etc/litefs.yml
2
+
3
+ # The FUSE section handles settings on the FUSE file system. FUSE
4
+ # provides a layer for intercepting SQLite transactions on the
5
+ # primary node so they can be shipped to replica nodes transparently.
6
+ fuse:
7
+ # Required. This is the mount directory that applications will
8
+ # use to access their SQLite databases.
9
+ dir: "/litefs"
10
+
11
+ # Set this flag to true to allow non-root users to access mount.
12
+ # You must set the "user_allow_other" option in /etc/fuse.conf first.
13
+ allow-other: false
14
+
15
+ # The debug flag enables debug logging of all FUSE API calls.
16
+ # This will produce a lot of logging. Not for general use.
17
+ debug: false
18
+
19
+ # The data section specifies where internal LiteFS data is stored
20
+ # and how long to retain the transaction files.
21
+ #
22
+ # Transaction files are used to ship changes to replica nodes so
23
+ # they should persist long enough for replicas to retrieve them,
24
+ # even in the face of a short network interruption or a redeploy.
25
+ # Under high load, these files can grow large so it's not advised
26
+ # to extend retention too long.
27
+ data:
28
+ # Path to internal data storage.
29
+ dir: "/data"
30
+
31
+ # Duration to keep LTX files. Latest LTX file is always kept.
32
+ retention: "10m"
33
+
34
+ # Frequency with which to check for LTX files to delete.
35
+ retention-monitor-interval: "1m"
36
+
37
+ # If true, then LiteFS will not wait until the node becomes the
38
+ # primary or connects to the primary before starting the subprocess.
39
+ skip-sync: false
40
+
41
+ # If true, then LiteFS will not exit if there is a validation
42
+ # issue on startup. This can be useful for debugging issues as
43
+ # it avoids constantly restarting the node on ephemeral hosting.
44
+ exit-on-error: false
45
+
46
+ # This section defines settings for the LiteFS HTTP API server.
47
+ # This API server is how nodes communicate with each other.
48
+ http:
49
+ # Specifies the bind address of the HTTP API server.
50
+ addr: ":20202"
51
+
52
+ # This section defines settings for the option HTTP proxy.
53
+ # This proxy can handle primary forwarding & replica consistency
54
+ # for applications that use a single SQLite database.
55
+ proxy:
56
+ # Specifies the bind address of the proxy server.
57
+ addr: ":3000"
58
+
59
+ # The hostport of the target application.
60
+ target: "localhost:3001"
61
+
62
+ # The name of the database used for TXID tracking.
63
+ db: "production.sqlite3"
64
+
65
+ # If true, enables verbose logging of requests by the proxy.
66
+ debug: false
67
+
68
+ # List of paths that are ignored by the proxy. The asterisk is
69
+ # the only available wildcard. These requests are passed
70
+ # through to the target as-is.
71
+ passthrough: []
72
+
73
+ # The lease section defines how LiteFS creates a cluster and
74
+ # implements leader election. For dynamic clusters, use the
75
+ # "consul". This allows the primary to change automatically when
76
+ # the current primary goes down. For a simpler setup, use
77
+ # "static" which assigns a single node to be the primary and does
78
+ # not failover.
79
+ lease:
80
+ # Required. Must be either "consul" or "static".
81
+ type: "consul"
82
+
83
+ # Required. The URL for this node's LiteFS API.
84
+ # Should match HTTP port.
85
+ advertise-url: "http://${HOSTNAME}.vm.${FLY_APP_NAME}.internal:20202"
86
+
87
+ # Specifies whether the node can become the primary. If using
88
+ # "static" leasing, this should be set to true on the primary
89
+ # and false on the replicas.
90
+ candidate: ${FLY_REGION == PRIMARY_REGION}
91
+
92
+ # A Consul server provides leader election and ensures that the
93
+ # responsibility of the primary node can be moved in the event
94
+ # of a deployment or a failure.
95
+ consul:
96
+ # Required. The base URL of the Consul server.
97
+ url: "${FLY_CONSUL_URL}"
98
+
99
+ # Required. The key used for obtaining a lease by the primary.
100
+ # This must be unique for each cluster of LiteFS servers
101
+ key: "litefs/${FLY_APP_NAME}"
102
+
103
+ # Length of time before a lease expires. The primary will
104
+ # automatically renew the lease while it is alive, however,
105
+ # if it fails to renew in time then a new primary may be
106
+ # elected after the TTL. This only occurs for unexpected loss
107
+ # of the leader as normal operation will allow the leader to
108
+ # handoff the lease to another replica without downtime.
109
+ #
110
+ # Consul does not allow a TTL of less than 10 seconds.
111
+ ttl: "10s"
112
+
113
+ # Length of time after the lease expires before a candidate
114
+ # can become leader. This buffer is intended to prevent
115
+ # overlap in leadership due to clock skew or in-flight calls.
116
+ lock-delay: "1s"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dockerfile-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-18 00:00:00.000000000 Z
11
+ date: 2023-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -49,12 +49,14 @@ files:
49
49
  - lib/generators/templates/docker-entrypoint.erb
50
50
  - lib/generators/templates/dockerfile.yml.erb
51
51
  - lib/generators/templates/dockerignore.erb
52
+ - lib/generators/templates/fly.toml.erb
53
+ - lib/generators/templates/litefs.yml.erb
52
54
  - lib/generators/templates/node-version.erb
53
- homepage: https://github.com/rubys/dockerfile-rails
55
+ homepage: https://github.com/fly-apps/dockerfile-rails
54
56
  licenses:
55
57
  - MIT
56
58
  metadata:
57
- homepage_uri: https://github.com/rubys/dockerfile-rails
59
+ homepage_uri: https://github.com/fly-apps/dockerfile-rails
58
60
  post_install_message:
59
61
  rdoc_options: []
60
62
  require_paths: