mkbrut 0.15.0 → 0.16.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 +4 -4
- data/lib/mkbrut/add_segment.rb +38 -0
- data/lib/mkbrut/add_segment_options.rb +22 -0
- data/lib/mkbrut/app.rb +7 -2
- data/lib/mkbrut/base.rb +1 -0
- data/lib/mkbrut/cli.rb +90 -8
- data/lib/mkbrut/ops/insert_code_in_method.rb +19 -7
- data/lib/mkbrut/ops/insert_into_file.rb +36 -0
- data/lib/mkbrut/ops/prism_parsing_op.rb +14 -2
- data/lib/mkbrut/ops.rb +1 -0
- data/lib/mkbrut/segments/bare_bones.rb +8 -0
- data/lib/mkbrut/segments/demo.rb +8 -0
- data/lib/mkbrut/segments/heroku.rb +23 -3
- data/lib/mkbrut/segments/sidekiq.rb +136 -1
- data/lib/mkbrut/segments.rb +1 -1
- data/lib/mkbrut/version.rb +1 -1
- data/lib/mkbrut.rb +2 -0
- data/templates/Base/.gitignore +3 -0
- data/templates/Base/Dockerfile.dx +18 -21
- data/templates/Base/Gemfile.erb +1 -1
- data/templates/Base/bin/run +107 -67
- data/templates/Base/bin/run.run +4 -0
- data/templates/Base/bin/setup +32 -1
- data/templates/Base/dx/bash_customizations +0 -4
- data/templates/Base/package.json.erb +1 -1
- data/templates/segments/Heroku/deploy/Dockerfile +23 -21
- data/templates/segments/Heroku/deploy/heroku_config.rb +7 -6
- data/templates/segments/Sidekiq/app/boot_sidekiq.rb +2 -0
- data/templates/segments/Sidekiq/app/config/sidekiq.yml +4 -0
- data/templates/segments/Sidekiq/app/src/back_end/jobs/app_job.rb +3 -0
- data/templates/segments/Sidekiq/app/src/back_end/jobs/example_job.rb +12 -0
- data/templates/segments/Sidekiq/app/src/back_end/segments/sidekiq_segment.rb +56 -0
- data/templates/segments/Sidekiq/bin/run.sidekiq +4 -0
- data/templates/segments/Sidekiq/specs/back_end/jobs/example_job.spec.rb +5 -0
- data/templates/segments/Sidekiq/specs/integration/sidekiq_works.spec.rb +38 -0
- metadata +14 -3
- data/templates/Base/.env.development.local +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01efe075fd366adf916bf216ee4bca597ac46b4e70cad921e716e4cf9354066c
|
4
|
+
data.tar.gz: 7790e6f5c3d8f35f79a89ec64d3e4879fa8d80fc085cfb608135ec2dc354239e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bf39f9ded150a7d98aed61649ad65182110fa52ccbd0ad491d7b40e07e00f63369b42ab0beec97b5cfb349faa8980ff0e8d3f9b8c446aa5bfd0fdf50ed07efb
|
7
|
+
data.tar.gz: d7ecb8ad272923a9f4471963d5535ebad77f8cdf69d786ea35e7a466b239e4062725f94053916ca0321de2010d2b77e9a43315e3464149c376faf8059235af24
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "erb"
|
3
|
+
|
4
|
+
module MKBrut
|
5
|
+
class AddSegment
|
6
|
+
def initialize(current_dir:, add_segment_options:, out:, err:)
|
7
|
+
@out = out
|
8
|
+
@add_segment_options = add_segment_options
|
9
|
+
|
10
|
+
@out.puts "Adding #{@add_segment_options.segment_name} to this app"
|
11
|
+
|
12
|
+
if @add_segment_options.dry_run?
|
13
|
+
@out.puts "Dry Run"
|
14
|
+
MKBrut::Ops::BaseOp.dry_run = true
|
15
|
+
end
|
16
|
+
|
17
|
+
templates_dir = Pathname(
|
18
|
+
Gem::Specification.find_by_name("mkbrut").gem_dir
|
19
|
+
) / "templates"
|
20
|
+
|
21
|
+
@segment = if @add_segment_options.segment_name == "sidekiq"
|
22
|
+
MKBrut::Segments::Sidekiq.new(
|
23
|
+
project_root: add_segment_options.project_root,
|
24
|
+
templates_dir:
|
25
|
+
)
|
26
|
+
elsif @add_segment_options.segment_name == "heroku"
|
27
|
+
MKBrut::Segments::Heroku.new(
|
28
|
+
project_root: add_segment_options.project_root,
|
29
|
+
templates_dir:
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def add!
|
35
|
+
@segment.add!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class MKBrut::AddSegmentOptions
|
2
|
+
attr_reader :segment_name, :project_root
|
3
|
+
|
4
|
+
def initialize(
|
5
|
+
segment_name:,
|
6
|
+
project_root:,
|
7
|
+
dry_run: nil,
|
8
|
+
versions: nil,
|
9
|
+
**rest
|
10
|
+
)
|
11
|
+
if segment_name.nil?
|
12
|
+
raise ArgumentError, "segment_name is required"
|
13
|
+
end
|
14
|
+
|
15
|
+
@segment_name = segment_name
|
16
|
+
@project_root = project_root
|
17
|
+
@dry_run = !!dry_run
|
18
|
+
@versions = versions
|
19
|
+
end
|
20
|
+
|
21
|
+
def dry_run? = @dry_run
|
22
|
+
end
|
data/lib/mkbrut/app.rb
CHANGED
@@ -47,14 +47,19 @@ module MKBrut
|
|
47
47
|
case segment
|
48
48
|
when "heroku"
|
49
49
|
@segments << MKBrut::Segments::Heroku.new(
|
50
|
-
|
51
|
-
|
50
|
+
project_root: @base.project_root,
|
51
|
+
templates_dir:
|
52
|
+
)
|
53
|
+
when "sidekiq"
|
54
|
+
@segments << MKBrut::Segments::Sidekiq.new(
|
55
|
+
project_root: @base.project_root,
|
52
56
|
templates_dir:
|
53
57
|
)
|
54
58
|
else
|
55
59
|
raise "Segment #{segment} is not supported"
|
56
60
|
end
|
57
61
|
end
|
62
|
+
@segments.sort!
|
58
63
|
end
|
59
64
|
|
60
65
|
def create!
|
data/lib/mkbrut/base.rb
CHANGED
data/lib/mkbrut/cli.rb
CHANGED
@@ -11,14 +11,56 @@ module MKBrut
|
|
11
11
|
|
12
12
|
def run
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
command = if @args.first == "add-segment"
|
15
|
+
@args.shift
|
16
|
+
:add_segment
|
17
|
+
elsif @args.first == "new-app" || @args.first == "new"
|
18
|
+
@args.shift
|
19
|
+
:new_app
|
20
|
+
else
|
21
|
+
:new_app
|
22
|
+
end
|
23
|
+
|
24
|
+
if command == :new_app
|
25
|
+
app_options = parse_options(@args, MKBrut::Versions.new)
|
26
|
+
new_app = MKBrut::App.new(
|
27
|
+
current_dir: Pathname.pwd.expand_path,
|
28
|
+
app_options:,
|
29
|
+
out: PrefixedIO.new(@out, "mkbrut"),
|
30
|
+
err: @err
|
31
|
+
)
|
32
|
+
new_app.create!
|
33
|
+
elsif command == :add_segment
|
34
|
+
add_segment_options = parse_add_segment_options(@args, MKBrut::Versions.new)
|
35
|
+
add_segment = MKBrut::AddSegment.new(
|
36
|
+
current_dir: add_segment_options.project_root,
|
37
|
+
add_segment_options:,
|
38
|
+
out: PrefixedIO.new(@out, "mkbrut"),
|
39
|
+
err: @err
|
40
|
+
)
|
41
|
+
add_segment.add!
|
42
|
+
@out.puts ""
|
43
|
+
@out.puts "Sidekiq has now been set up for your app. The configuration used is"
|
44
|
+
@out.puts "a basic one, suitable for getting started, however you know own this"
|
45
|
+
@out.puts "configuration. Most of it is in these files:"
|
46
|
+
@out.puts ""
|
47
|
+
@out.puts " app/config/sidekiq.yml"
|
48
|
+
@out.puts " app/src/back_end/segments/sidekiq_segment.rb"
|
49
|
+
@out.puts ""
|
50
|
+
@out.puts "You are encouraged to verify everything is set up as follows:"
|
51
|
+
@out.puts ""
|
52
|
+
@out.puts "1. Quit dx/start, and start it back up - this will downloaded and set up ValKey/Redis"
|
53
|
+
@out.puts "2. Re-run bin/setup. This will install needed gems and create binstubs"
|
54
|
+
@out.puts "3. Run the example integration test:"
|
55
|
+
@out.puts ""
|
56
|
+
@out.puts " bin/test e2e specs/integration/sidekiq_works.spec.rb"
|
57
|
+
@out.puts ""
|
58
|
+
@out.puts " This will use the actual Sidekiq server, so if it passes, you should"
|
59
|
+
@out.puts " all set and can start creating jobs"
|
60
|
+
@out.puts ""
|
61
|
+
else
|
62
|
+
raise "Unknown command #{command}"
|
63
|
+
end
|
22
64
|
0
|
23
65
|
rescue => e
|
24
66
|
@err.puts "Error: #{e.message}"
|
@@ -103,5 +145,45 @@ module MKBrut
|
|
103
145
|
MKBrut::AppOptions.new(**options, versions:)
|
104
146
|
end
|
105
147
|
|
148
|
+
def parse_add_segment_options(args, versions)
|
149
|
+
options = {}
|
150
|
+
@option_parser = OptionParser.new do |opts|
|
151
|
+
opts.banner = [
|
152
|
+
"Usage: mkbrut add-segment [options] segment-name",
|
153
|
+
"",
|
154
|
+
" Adds a segment to an existing Brut-powered app",
|
155
|
+
"",
|
156
|
+
"VERSION",
|
157
|
+
"",
|
158
|
+
" #{MKBrut::VERSION}",
|
159
|
+
"",
|
160
|
+
"OPTIONS",
|
161
|
+
"",
|
162
|
+
].join("\n")
|
163
|
+
|
164
|
+
opts.on("-r PROJECT_ROOT_DIR", "--project-root=PROJECT_ROOT_DIR", "Path to the root of the existing Brut app (defaults to current directory)")
|
165
|
+
opts.on("--dry-run", "Only show what would happen, don't actually do anything")
|
166
|
+
opts.on("--[no-]demo", "Include, or not, additional files that demonstrate Brut's features (default is true for now")
|
167
|
+
opts.on("-h", "--help", "Show this help message") do
|
168
|
+
@out.puts @option_parser
|
169
|
+
@out.puts
|
170
|
+
@out.puts "ARGUMENTS"
|
171
|
+
@out.puts
|
172
|
+
@out.puts " segment-name - name of the segment to add. Known values:"
|
173
|
+
@out.puts " sidekiq - add support for Sidekiq"
|
174
|
+
@out.puts " heroku - add support for Heroku container-based deployment"
|
175
|
+
@out.puts
|
176
|
+
@out.puts "ENVIRONMENT VARIABLES"
|
177
|
+
@out.puts
|
178
|
+
@out.puts " BRUT_CLI_RAISE_ON_ERROR - if set to 'true', any error will raise an exception instead of printing to stderr"
|
179
|
+
@out.puts
|
180
|
+
exit
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
@option_parser.parse!(args, into: options)
|
185
|
+
options[:project_root] = Pathname.new(options[:'project-root'] || ".").expand_path
|
186
|
+
MKBrut::AddSegmentOptions.new(segment_name: @args[0], **options, versions:)
|
187
|
+
end
|
106
188
|
end
|
107
189
|
end
|
@@ -1,14 +1,25 @@
|
|
1
1
|
class MKBrut::Ops::InsertCodeInMethod < MKBrut::Ops::PrismParsingOp
|
2
|
-
def initialize(file:,
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
def initialize(file:,
|
3
|
+
class_name:,
|
4
|
+
method_name:,
|
5
|
+
class_method: false,
|
6
|
+
code:,
|
7
|
+
ignore_if_file_not_found: false,
|
8
|
+
where: :end)
|
9
|
+
@file = file
|
10
|
+
@class_name = class_name
|
11
|
+
@method_name = method_name.to_sym
|
12
|
+
@class_method = class_method
|
13
|
+
@code = code
|
14
|
+
@ignore_if_file_not_found = ignore_if_file_not_found
|
15
|
+
@where = where
|
8
16
|
end
|
9
17
|
|
10
18
|
def call
|
11
|
-
|
19
|
+
if !@file.exist? && @ignore_if_file_not_found
|
20
|
+
return
|
21
|
+
end
|
22
|
+
method_node = find_method(class_name: @class_name, method_name: @method_name, class_method: @class_method)
|
12
23
|
|
13
24
|
insertion_point = if @where == :start
|
14
25
|
insertion_point_for_code_at_start_of_method(method_node: method_node)
|
@@ -42,6 +53,7 @@ private
|
|
42
53
|
}.join("\n") + post_indent
|
43
54
|
end
|
44
55
|
|
56
|
+
# XXX: This does not work with non-ASCII strings
|
45
57
|
def insertion_point_for_code_at_end_of_method(method_node:)
|
46
58
|
line_number_of_method_end = method_node.location.end_line - 1
|
47
59
|
length_of_method_end = @source.lines[line_number_of_method_end].length
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class MKBrut::Ops::InsertIntoFile < MKBrut::Ops::BaseOp
|
2
|
+
def initialize(file:, content:, before_line:)
|
3
|
+
@file = file
|
4
|
+
@content = content
|
5
|
+
@before_line = before_line
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
|
10
|
+
contents = File.read(@file)
|
11
|
+
locations = []
|
12
|
+
new_contents = []
|
13
|
+
contents.split("\n").each_with_index do |line, index|
|
14
|
+
if line == @before_line
|
15
|
+
new_contents << @content
|
16
|
+
locations << (index + 1)
|
17
|
+
end
|
18
|
+
new_contents << line
|
19
|
+
end
|
20
|
+
if locations.empty?
|
21
|
+
raise "Did not find line '#{@before_line}' exactly in #{@file}"
|
22
|
+
end
|
23
|
+
if locations.size > 1
|
24
|
+
raise "Found exact line '#{@before_line}' #{locations.size} times in #{@file}, should be exactly once so we know where to insert"
|
25
|
+
end
|
26
|
+
|
27
|
+
if dry_run?
|
28
|
+
puts "Would add to #{@file}, resulting in this content:\n#{new_contents.join("\n")}\n"
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
File.open(@file, "w") do |file|
|
33
|
+
file.puts new_contents.join("\n")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -56,10 +56,22 @@ private
|
|
56
56
|
class_node
|
57
57
|
end
|
58
58
|
|
59
|
-
def find_method(class_name:, method_name:)
|
59
|
+
def find_method(class_name:, method_name:, class_method: false)
|
60
60
|
class_node = find_class(class_name:)
|
61
61
|
method_node = class_node.body.body.detect { |node|
|
62
|
-
|
62
|
+
|
63
|
+
is_def = node.is_a?(Prism::DefNode)
|
64
|
+
method_name_match = node.name == @method_name
|
65
|
+
receiver_match = if class_method
|
66
|
+
node.receiver.is_a?(Prism::SelfNode)
|
67
|
+
else
|
68
|
+
node.receiver.nil?
|
69
|
+
end
|
70
|
+
|
71
|
+
if is_def && method_name_match && !receiver_match
|
72
|
+
puts "Found method '#{method_name}' in class '#{class_name}' but it was a #{node.receiver.class} receiver"
|
73
|
+
end
|
74
|
+
is_def && method_name_match && receiver_match
|
63
75
|
}
|
64
76
|
|
65
77
|
if !method_node
|
data/lib/mkbrut/ops.rb
CHANGED
@@ -8,6 +8,7 @@ module MKBrut
|
|
8
8
|
autoload :InsertRoute, "mkbrut/ops/insert_route"
|
9
9
|
autoload :InsertCodeInMethod, "mkbrut/ops/insert_code_in_method"
|
10
10
|
autoload :AppendToFile, "mkbrut/ops/append_to_file"
|
11
|
+
autoload :InsertIntoFile, "mkbrut/ops/insert_into_file"
|
11
12
|
autoload :PrismParsingOp, "mkbrut/ops/prism_parsing_op"
|
12
13
|
autoload :AddI18nMessage, "mkbrut/ops/add_i18n_message"
|
13
14
|
autoload :AddCSSImport, "mkbrut/ops/add_css_import"
|
@@ -9,6 +9,14 @@ class MKBrut::Segments::BareBones < MKBrut::Base
|
|
9
9
|
@erb_binding = ErbBindingDelegate.new(app_options)
|
10
10
|
end
|
11
11
|
|
12
|
+
def <=>(other)
|
13
|
+
if self.class == other.class
|
14
|
+
0
|
15
|
+
else
|
16
|
+
-1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
12
20
|
def add!
|
13
21
|
|
14
22
|
operations = copy_files(@templates_dir, @project_root) +
|
data/lib/mkbrut/segments/demo.rb
CHANGED
@@ -10,6 +10,14 @@ class MKBrut::Segments::Demo < MKBrut::Base
|
|
10
10
|
@erb_binding = ErbBindingDelegate.new(app_options)
|
11
11
|
end
|
12
12
|
|
13
|
+
def <=>(other)
|
14
|
+
if self.class == other.class
|
15
|
+
0
|
16
|
+
else
|
17
|
+
1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
13
21
|
def add!
|
14
22
|
operations = copy_files(@templates_dir, @project_root) +
|
15
23
|
other_operations(@project_root)
|
@@ -1,10 +1,9 @@
|
|
1
1
|
class MKBrut::Segments::Heroku < MKBrut::Base
|
2
2
|
def self.friendly_name = "Heroku-based Deployment"
|
3
3
|
|
4
|
-
def initialize(
|
5
|
-
@project_root =
|
4
|
+
def initialize(project_root:, templates_dir:)
|
5
|
+
@project_root = project_root
|
6
6
|
@templates_dir = templates_dir / "segments" / "Heroku"
|
7
|
-
@erb_binding = MKBrut::ErbBindingDelegate.new(app_options)
|
8
7
|
end
|
9
8
|
|
10
9
|
def add!
|
@@ -16,8 +15,29 @@ class MKBrut::Segments::Heroku < MKBrut::Base
|
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
18
|
+
def <=>(other)
|
19
|
+
if self.class == other.class
|
20
|
+
0
|
21
|
+
elsif other.class == MKBrut::Segments::Sidekiq
|
22
|
+
# If both herkou and sidekiq segments are activated, we want to do heroku first,
|
23
|
+
# since Sidekiq will need to modify it.
|
24
|
+
-1
|
25
|
+
else
|
26
|
+
1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
19
30
|
def other_operations
|
20
31
|
[
|
32
|
+
MKBrut::Ops::InsertIntoFile.new(
|
33
|
+
file: @project_root / "Dockerfile.dx",
|
34
|
+
contents: %{
|
35
|
+
# Installs the Heroku CLI, per Heroku docs at
|
36
|
+
# https://devcenter.heroku.com/articles/heroku-cli#install-with-ubuntu-debian-apt-get
|
37
|
+
RUN curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
|
38
|
+
},
|
39
|
+
before_line: "# Up to now, RUN directives ran as the root user. When using this container"
|
40
|
+
),
|
21
41
|
MKBrut::Ops::AppendToFile.new(
|
22
42
|
file: @project_root / ".gitignore",
|
23
43
|
content: %{
|
@@ -1,3 +1,138 @@
|
|
1
1
|
# Adds Sidekiq to a Brut app
|
2
|
-
class MKBrut::Segments::Sidekiq
|
2
|
+
class MKBrut::Segments::Sidekiq < MKBrut::Base
|
3
|
+
def self.friendly_name = "Use Sidekiq to Run Background Jobs"
|
4
|
+
def initialize(project_root:, templates_dir:)
|
5
|
+
@project_root = project_root
|
6
|
+
@templates_dir = templates_dir / "segments" / "Sidekiq"
|
7
|
+
end
|
8
|
+
|
9
|
+
def add!
|
10
|
+
operations = copy_files(@templates_dir, @project_root) +
|
11
|
+
other_operations
|
12
|
+
|
13
|
+
operations.each do |operation|
|
14
|
+
operation.call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def <=>(other)
|
19
|
+
if self.class == other.class
|
20
|
+
0
|
21
|
+
elsif other.class == MKBrut::Segments::Heroku
|
22
|
+
# If both herkou and sidekiq segments are activated, we want to do heroku first,
|
23
|
+
# since Sidekiq will need to modify it.
|
24
|
+
1
|
25
|
+
else
|
26
|
+
-1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def other_operations
|
31
|
+
[
|
32
|
+
MKBrut::Ops::AppendToFile.new(
|
33
|
+
file: @project_root / "docker-compose.dx.yml",
|
34
|
+
content: %{
|
35
|
+
redis:
|
36
|
+
# Change the value to what you are using in production.
|
37
|
+
# If you are using actual Redis, change that here.
|
38
|
+
image: valkey/valkey:8.1
|
39
|
+
},
|
40
|
+
),
|
41
|
+
MKBrut::Ops::AppendToFile.new(
|
42
|
+
file: @project_root / "Procfile.development",
|
43
|
+
content: "sidekiq: bin/run sidekiq\n"
|
44
|
+
),
|
45
|
+
MKBrut::Ops::AppendToFile.new(
|
46
|
+
file: @project_root / "Procfile.test",
|
47
|
+
content: "sidekiq: bin/run sidekiq\n"
|
48
|
+
),
|
49
|
+
MKBrut::Ops::AppendToFile.new(
|
50
|
+
file: @project_root / "Gemfile",
|
51
|
+
content: "# Sidekiq is used for background jobs\ngem \"sidekiq\"\n"
|
52
|
+
),
|
53
|
+
MKBrut::Ops::AppendToFile.new(
|
54
|
+
file: @project_root / ".env.development",
|
55
|
+
content: %{
|
56
|
+
# URL of the Redis/ValKey to use for Sidekiq
|
57
|
+
SIDEKIQ_REDIS_URL=redis://redis:6379/1
|
58
|
+
# Tells Sidekiq which ENV var to use for the Redis/ValKey URL
|
59
|
+
REDIS_PROVIDER=SIDEKIQ_REDIS_URL
|
60
|
+
# Username for basic auth into the Sidekiq Admin UI
|
61
|
+
SIDEKIQ_BASIC_AUTH_USER=sidekiq
|
62
|
+
# Passsword for basic auth into the Sidekiq Admin UI
|
63
|
+
SIDEKIQ_BASIC_AUTH_PASSWORD=password
|
64
|
+
}
|
65
|
+
),
|
66
|
+
MKBrut::Ops::AppendToFile.new(
|
67
|
+
file: @project_root / ".env.test",
|
68
|
+
content: %{
|
69
|
+
SIDEKIQ_REDIS_URL=redis://redis:6379/2
|
70
|
+
REDIS_PROVIDER=SIDEKIQ_REDIS_URL
|
71
|
+
SIDEKIQ_BASIC_AUTH_USER=sidekiq-test
|
72
|
+
SIDEKIQ_BASIC_AUTH_PASSWORD=password
|
73
|
+
}
|
74
|
+
),
|
75
|
+
MKBrut::Ops::InsertIntoFile.new(
|
76
|
+
file: @project_root / "bin" / "test-server",
|
77
|
+
before_line: "wait",
|
78
|
+
content: "bin/run sidekiq &\n",
|
79
|
+
),
|
80
|
+
MKBrut::Ops::InsertIntoFile.new(
|
81
|
+
file: @project_root / "bin" / "setup",
|
82
|
+
before_line: " log \"Installing rspec binstubs\"",
|
83
|
+
content: %{log "Installing sidekiq binstubs"
|
84
|
+
system! "bundle binstub sidekiq"
|
85
|
+
}
|
86
|
+
),
|
87
|
+
MKBrut::Ops::InsertIntoFile.new(
|
88
|
+
file: @project_root / "specs" / "spec_helper.rb",
|
89
|
+
before_line: "require \"brut/spec_support\"",
|
90
|
+
content: "require \"sidekiq/testing\""
|
91
|
+
),
|
92
|
+
MKBrut::Ops::InsertIntoFile.new(
|
93
|
+
file: @project_root / "config.ru",
|
94
|
+
before_line: "bootstrap = Bootstrap.new.bootstrap!",
|
95
|
+
content: "require \"sidekiq/web\"\n"
|
96
|
+
),
|
97
|
+
MKBrut::Ops::InsertIntoFile.new(
|
98
|
+
file: @project_root / "config.ru",
|
99
|
+
before_line: " run bootstrap.rack_app",
|
100
|
+
content: %{
|
101
|
+
map "/sidekiq" do
|
102
|
+
use Rack::Auth::Basic, "Sidekiq" do |username, password|
|
103
|
+
[username, password] == [ENV.fetch("SIDEKIQ_BASIC_AUTH_USER"), ENV.fetch("SIDEKIQ_BASIC_AUTH_PASSWORD")]
|
104
|
+
end
|
105
|
+
run Sidekiq::Web.new
|
106
|
+
end
|
107
|
+
}
|
108
|
+
),
|
109
|
+
MKBrut::Ops::InsertCodeInMethod.new(
|
110
|
+
file: @project_root / "app" / "src" / "app.rb",
|
111
|
+
class_name: "App",
|
112
|
+
method_name: "initialize",
|
113
|
+
code: "@sidekiq_segment = SidekiqSegment.new",
|
114
|
+
),
|
115
|
+
MKBrut::Ops::InsertCodeInMethod.new(
|
116
|
+
file: @project_root / "app" / "src" / "app.rb",
|
117
|
+
class_name: "App",
|
118
|
+
method_name: "boot!",
|
119
|
+
code: "@sidekiq_segment.boot!"
|
120
|
+
),
|
121
|
+
MKBrut::Ops::InsertCodeInMethod.new(
|
122
|
+
file: project_root / "deploy" / "heroku_config.rb",
|
123
|
+
class_name: "HerokuConfig",
|
124
|
+
method_name: "additional_images",
|
125
|
+
class_method: true,
|
126
|
+
ignore_if_file_not_found: true,
|
127
|
+
code: %{
|
128
|
+
{
|
129
|
+
"sidekiq" => {
|
130
|
+
cmd: "bin/run sidekiq",
|
131
|
+
}
|
132
|
+
}
|
133
|
+
},
|
134
|
+
where: :end
|
135
|
+
),
|
136
|
+
]
|
137
|
+
end
|
3
138
|
end
|
data/lib/mkbrut/segments.rb
CHANGED
@@ -2,7 +2,7 @@ module MKBrut
|
|
2
2
|
module Segments
|
3
3
|
autoload :BareBones, "mkbrut/segments/bare_bones"
|
4
4
|
autoload :Demo, "mkbrut/segments/demo"
|
5
|
-
autoload :Sidekiq, "mkbrut/segments/
|
5
|
+
autoload :Sidekiq, "mkbrut/segments/sidekiq"
|
6
6
|
autoload :Heroku, "mkbrut/segments/heroku"
|
7
7
|
end
|
8
8
|
end
|
data/lib/mkbrut/version.rb
CHANGED
data/lib/mkbrut.rb
CHANGED
@@ -3,6 +3,8 @@ module MKBrut
|
|
3
3
|
autoload :AppId, "mkbrut/app_id"
|
4
4
|
autoload :AppName, "mkbrut/app_name"
|
5
5
|
autoload :AppOptions, "mkbrut/app_options"
|
6
|
+
autoload :AddSegment, "mkbrut/add_segment"
|
7
|
+
autoload :AddSegmentOptions, "mkbrut/add_segment_options"
|
6
8
|
autoload :Base, "mkbrut/base"
|
7
9
|
autoload :CLI, "mkbrut/cli"
|
8
10
|
autoload :ErbBindingDelegate, "mkbrut/erb_binding_delegate"
|
data/templates/Base/.gitignore
CHANGED
@@ -17,6 +17,9 @@
|
|
17
17
|
|
18
18
|
# This could contain actual secrets and should not be checked in
|
19
19
|
/.env.development.local
|
20
|
+
# This could also contain actual secrets and should not be checked in - it's configuration
|
21
|
+
# per developer, not per app
|
22
|
+
/dx/bash_customizations.local
|
20
23
|
|
21
24
|
# These are generated artifacts
|
22
25
|
/deploy/Dockerfile.*
|
@@ -25,13 +25,18 @@ ENV DEBIAN_FRONTEND=noninteractive
|
|
25
25
|
# These packages are needed to set up other repos to install other
|
26
26
|
# packages and/or are useful in installing other software
|
27
27
|
#
|
28
|
+
# --mount=type=cache,target=/var/cache/apt means that we'll save
|
29
|
+
# the downloaded file used to install between builds, so that if they
|
30
|
+
# have not changed, builds will be faster.
|
31
|
+
#
|
28
32
|
# - ca-certificates - needed by Postgres client
|
29
33
|
# - curl - needed by various installation instructions
|
30
34
|
# - gnupg - needed to install Docker
|
31
35
|
# - lsb-release - needed by Postgres install instructions
|
32
36
|
# - rsync - needed by Brut's build_assets command
|
33
37
|
# - vim - needed when we want to edit files inside container
|
34
|
-
RUN apt
|
38
|
+
RUN --mount=type=cache,target=/var/cache/apt \
|
39
|
+
apt-get -y clean && \
|
35
40
|
apt-get -y update && \
|
36
41
|
apt-get install --quiet --yes \
|
37
42
|
ca-certificates \
|
@@ -47,7 +52,8 @@ RUN apt-get -y clean && \
|
|
47
52
|
#
|
48
53
|
# npx playwright install-deps --dry-run chromium
|
49
54
|
#
|
50
|
-
RUN apt
|
55
|
+
RUN --mount=type=cache,target=/var/cache/apt \
|
56
|
+
apt-get install -y --no-install-recommends libasound2 \
|
51
57
|
libatk-bridge2.0-0 \
|
52
58
|
libatk1.0-0 \
|
53
59
|
libatspi2.0-0 \
|
@@ -117,10 +123,6 @@ RUN echo "gem: --no-document" >> ~/.gemrc && \
|
|
117
123
|
gem update --system && \
|
118
124
|
gem install bundler
|
119
125
|
|
120
|
-
# Installs the Heroku CLI, per Heroku docs at
|
121
|
-
# https://devcenter.heroku.com/articles/heroku-cli#install-with-ubuntu-debian-apt-get
|
122
|
-
RUN curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
|
123
|
-
|
124
126
|
# Up to now, RUN directives ran as the root user. When using this container
|
125
127
|
# for development, we do not want to run as the root user. On macOS, this is not
|
126
128
|
# a big deal, but in Linux, the root user inside this container can create files
|
@@ -178,28 +180,23 @@ COPY --chown=appuser:${user_gid} dx/bash_customizations.local /home/appuser/.bas
|
|
178
180
|
# as belonging to.
|
179
181
|
USER appuser
|
180
182
|
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
|
185
|
-
# recommendation.
|
186
|
-
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \
|
187
|
-
\. "$HOME/.nvm/nvm.sh" && \
|
188
|
-
nvm install 22 && \
|
189
|
-
node -v && nvm current && npm -v
|
190
|
-
|
191
|
-
|
183
|
+
# Installs NodeJS from the official image. This is simpler
|
184
|
+
# than the canonical instructions which use NVM. Inside the container, there
|
185
|
+
# is not value to use NVM.
|
186
|
+
COPY --from=node:22-slim /usr/local /usr/local
|
192
187
|
|
193
188
|
# This arg should have the version of playwright present in the app's Gemfile.lock.
|
194
189
|
# This is because the playright NodeJS library and the Ruby gem must have the same
|
195
190
|
# version or things won't work right. The value is blank because it really has to be
|
196
191
|
# detected during `dx/build`.
|
197
192
|
ARG PLAYWRIGHT_VERSION
|
193
|
+
|
198
194
|
# Now install the version of Playwright we detected as well as Chromium. NOTE, we
|
199
195
|
# must install Chromium, not chrome, because there is no version of Chrome for an
|
200
196
|
# ARM-based Debian linux, which is what this Dockerfile will build on an Apple
|
201
|
-
# Silicon Mac.
|
202
|
-
|
203
|
-
|
204
|
-
|
197
|
+
# Silicon Mac. Note that we set NPM_CONFIG_PREFIX so that the global
|
198
|
+
# install will work for this user.
|
199
|
+
ENV NPM_CONFIG_PREFIX=/home/appuser/.npm-global
|
200
|
+
ENV PATH=/home/appuser/.npm-global/bin:$PATH
|
201
|
+
RUN npm install -g playwright@$PLAYWRIGHT_VERSION && \
|
205
202
|
npx playwright install chromium
|
data/templates/Base/Gemfile.erb
CHANGED
@@ -40,7 +40,7 @@ gem "pg"
|
|
40
40
|
# We lock the version because it must be the same as the verison
|
41
41
|
# of playwright in package.json. This ensures that we can reliably
|
42
42
|
# install the right browsers and that everything will work right.
|
43
|
-
gem "playwright-ruby-client", "1.
|
43
|
+
gem "playwright-ruby-client", "1.55.0", groups: [ :test ]
|
44
44
|
|
45
45
|
# Sinatra needs a webserver and puma is the best
|
46
46
|
gem "puma"
|
data/templates/Base/bin/run
CHANGED
@@ -1,86 +1,126 @@
|
|
1
|
-
#!/bin/
|
1
|
+
#!/usr/bin/env bash
|
2
2
|
|
3
3
|
set -e
|
4
|
-
SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
|
5
|
-
|
6
|
-
usage() {
|
7
|
-
echo "Usage: $0"
|
8
|
-
echo
|
9
|
-
echo " Run the app in the given RACK_ENV."
|
10
|
-
echo " You likely want to use bin/dev instead of this command."
|
11
|
-
echo
|
12
|
-
echo "ENVIRONMENT VARIABLES"
|
13
|
-
echo
|
14
|
-
echo " PORT - The port to run the app on. Default is 6502"
|
15
|
-
echo " RACK_ENV - The Rack environment to use. Default is development"
|
16
|
-
echo
|
17
|
-
}
|
18
|
-
|
19
|
-
for arg in "$@"; do
|
20
|
-
if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ] || [ "${arg}" = "help" ]; then
|
21
|
-
usage
|
22
|
-
exit 0
|
23
|
-
fi
|
24
|
-
done
|
25
4
|
|
5
|
+
SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
|
26
6
|
PORT="${PORT:-6502}"
|
27
7
|
RACK_ENV="${RACK_ENV:-development}"
|
28
8
|
|
29
9
|
export PORT
|
30
10
|
export RACK_ENV
|
31
11
|
|
32
|
-
|
12
|
+
declare -A CMD_MAP
|
13
|
+
declare -A PIDFILE_MAP
|
14
|
+
declare -A CREATES_PIDFILE_MAP
|
33
15
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
pid=$(cat tmp/pidfile)
|
41
|
-
|
42
|
-
# First, try to gracefully stop the server with kill
|
43
|
-
if ps -p "${pid}" > /dev/null; then
|
44
|
-
echo "[ $0 ] Attempting to kill PID '${pid}'"
|
45
|
-
kill "${pid}"
|
46
|
-
else
|
47
|
-
echo "[ $0 ] PID '${pid}' no longer running"
|
16
|
+
kill_previous() {
|
17
|
+
local pidfile=$1
|
18
|
+
|
19
|
+
if [[ -z "$pidfile" ]]; then
|
20
|
+
echo "[ $0 ] Error: no pidfile path given"
|
21
|
+
return 0
|
48
22
|
fi
|
49
23
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
24
|
+
if [ -f "${pidfile}" ]; then
|
25
|
+
echo "[ $0 ] pidfile found"
|
26
|
+
pid=$(cat "${pidfile}")
|
27
|
+
|
28
|
+
if [[ -z "$pid" || ! "$pid" =~ ^[0-9]+$ ]]; then
|
29
|
+
echo "[ $0 ] Invalid or empty PID in $pidfile: '$pid'; removing"
|
30
|
+
rm -f "$pidfile"
|
31
|
+
return 0
|
32
|
+
fi
|
33
|
+
|
34
|
+
if ps -p "${pid}" > /dev/null; then
|
35
|
+
echo "[ $0 ] Attempting to kill PID '${pid}'"
|
36
|
+
kill "${pid}"
|
37
|
+
else
|
38
|
+
echo "[ $0 ] PID '${pid}' no longer running"
|
39
|
+
fi
|
40
|
+
if timeout 5 tail --pid="${pid}" -f /dev/null; then
|
41
|
+
echo "[ $0 ] PID '${pid}' stopped. Restarting server"
|
60
42
|
else
|
61
|
-
echo "[ $0 ] PID '${pid}'
|
62
|
-
|
43
|
+
echo "[ $0 ] PID '${pid}' has not stopped. Trying kill -9"
|
44
|
+
kill -9 "${pid}"
|
45
|
+
if timeout 1 tail --pid="${pid}" -f /dev/null; then
|
46
|
+
echo "[ $0 ] PID '${pid}' killed. Restarting server"
|
47
|
+
else
|
48
|
+
echo "[ $0 ] PID '${pid}' still running. Something seriously wrong"
|
49
|
+
fi
|
63
50
|
exit 1
|
64
51
|
fi
|
52
|
+
|
53
|
+
else
|
54
|
+
echo "[ $0 ] No pidfile-Starting up"
|
65
55
|
fi
|
56
|
+
}
|
66
57
|
|
67
|
-
|
68
|
-
|
58
|
+
dotenv_run() {
|
59
|
+
local env_files="${SCRIPT_DIR}/../.env.${RACK_ENV}.local,${SCRIPT_DIR}/../.env.${RACK_ENV}"
|
60
|
+
dotenv -f "$env_files" --ignore -- "$@"
|
61
|
+
}
|
62
|
+
|
63
|
+
|
64
|
+
for f in "$SCRIPT_DIR"/run.*; do
|
65
|
+
if [[ -f "$f" ]]; then
|
66
|
+
echo "[ $0 ] Sourcing $f"
|
67
|
+
. "$f"
|
68
|
+
fi
|
69
|
+
done
|
70
|
+
|
71
|
+
case $# in
|
72
|
+
0) name="run" ;;
|
73
|
+
1) name="$1" ;;
|
74
|
+
*) echo "Error: too many arguments ($#)." >&2; usage; exit 2 ;;
|
75
|
+
esac
|
76
|
+
|
77
|
+
if [[ ! -v CMD_MAP[$name] ]]; then
|
78
|
+
echo "[ $0 ] Error: unknown service '$name'." >&2
|
79
|
+
echo -n "[ $0 ] Known services: " >&2
|
80
|
+
printf "%s " "${!CMD_MAP[@]}" >&2
|
81
|
+
echo >&2
|
82
|
+
exit 1
|
69
83
|
fi
|
70
84
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
85
|
+
cmd="${CMD_MAP[$name]}"
|
86
|
+
creates_pidfile="${CREATES_PIDFILE_MAP[$name]:-false}"
|
87
|
+
pidfile="${PIDFILE_MAP[$name]:-}"
|
88
|
+
|
89
|
+
echo "[ $0 ]: Command for $name:"
|
90
|
+
echo
|
91
|
+
echo " $cmd"
|
92
|
+
echo
|
93
|
+
|
94
|
+
kill_previous "$pidfile"
|
95
|
+
|
96
|
+
if [[ "$creates_pidfile" == true ]]; then
|
97
|
+
echo "[ $0 ] Running '$cmd', which creates its own pidfile"
|
98
|
+
eval "dotenv_run $cmd"
|
99
|
+
else
|
100
|
+
# Command wont' create a pidfile, so we will
|
101
|
+
if [[ -z "${pidfile:-}" ]]; then
|
102
|
+
echo "Error: no pidfile path configured for '$name' but creates_pidfile=false." >&2
|
103
|
+
exit 1
|
104
|
+
fi
|
105
|
+
echo "[ $0 ] Running '$cmd', which will not create a pidfile"
|
106
|
+
echo "[ $0 ] We will do that and save it to ${pidfile}"
|
107
|
+
|
108
|
+
echo "[ $0 ] Ensuring $(dirname "$pidfile") exists"
|
109
|
+
mkdir -p "$(dirname "$pidfile")"
|
110
|
+
|
111
|
+
eval "dotenv_run $cmd &"
|
112
|
+
child_pid=$!
|
113
|
+
|
114
|
+
set +e
|
115
|
+
echo "$child_pid" > "$pidfile"
|
116
|
+
|
117
|
+
trap 'kill -TERM "$child_pid" 2>/dev/null' TERM INT
|
118
|
+
|
119
|
+
wait "$child_pid"
|
120
|
+
|
121
|
+
echo "[ $0 ] TERM or INT received, removing ${pidfile} and exiting"
|
122
|
+
|
123
|
+
status=$?
|
124
|
+
rm -f "$pidfile"
|
125
|
+
exit "$status"
|
126
|
+
fi
|
data/templates/Base/bin/setup
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
3
|
require "fileutils"
|
5
4
|
require "open3"
|
6
5
|
require "optparse"
|
7
6
|
require "pathname"
|
7
|
+
require "bundler"
|
8
|
+
require "json"
|
8
9
|
|
9
10
|
# This is intended to run inside the Workspace (i.e. Docker container) to
|
10
11
|
# set up the Foundation, thus enabling development. This script should:
|
@@ -21,6 +22,7 @@ require "pathname"
|
|
21
22
|
# Take care to only add code and not change what is here.
|
22
23
|
|
23
24
|
def setup(update_gems:,update_node:)
|
25
|
+
ensure_versions_are_consistent!
|
24
26
|
if update_gems
|
25
27
|
log "Updating gems"
|
26
28
|
system! "bundle update"
|
@@ -82,6 +84,35 @@ def setup(update_gems:,update_node:)
|
|
82
84
|
help
|
83
85
|
end
|
84
86
|
|
87
|
+
def ensure_versions_are_consistent!
|
88
|
+
gemfile = Bundler::Definition.build(ROOT_DIR / "Gemfile", ROOT_DIR / "Gemfile.lock", nil)
|
89
|
+
package_json = JSON.parse(File.read(ROOT_DIR / "package.json"))
|
90
|
+
|
91
|
+
playwright_version = nil
|
92
|
+
gemfile.dependencies.each do |dep|
|
93
|
+
if dep.name == "playwright-ruby-client"
|
94
|
+
if !dep.specific?
|
95
|
+
puts "You must specify an exact/specific version of 'playwright-ruby-client' in your Gemfile"
|
96
|
+
exit 1
|
97
|
+
end
|
98
|
+
playwright_version = dep.requirement.requirements.first[1]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if playwright_version
|
103
|
+
playwright_version_in_package_json = package_json.dig("devDependencies","playwright")
|
104
|
+
if !playwright_version_in_package_json
|
105
|
+
puts "You must have 'playwright' in the devDependencies section of your package.json"
|
106
|
+
exit 1
|
107
|
+
end
|
108
|
+
if playwright_version != playwright_version_in_package_json
|
109
|
+
puts "The version of 'playwright' in package.json (#{playwright_version_in_package_json}) does not match the version of 'playwright-ruby-client' in the Gemfile (#{playwright_version})"
|
110
|
+
puts "They must be exactly the same, or your end-to-end tests may behave strangely"
|
111
|
+
exit 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
85
116
|
def setup_dot_env_local
|
86
117
|
|
87
118
|
dot_env = ROOT_DIR / ".env.development"
|
@@ -6,7 +6,3 @@ if [ ! -n "$PROJECT_ROOT" ]; then
|
|
6
6
|
fi
|
7
7
|
export GEM_HOME=${PROJECT_ROOT}/local-gems/gem-home
|
8
8
|
export PATH=${PATH}:${GEM_HOME}/bin
|
9
|
-
# This sets up the Node version so we don't have to do it before every. single.
|
10
|
-
# shell. invocation.
|
11
|
-
. ~/.nvm/nvm.sh
|
12
|
-
nvm use default
|
@@ -37,7 +37,8 @@ WORKDIR /brut-app
|
|
37
37
|
# - lsb-release is used to generically access information for this OS's version
|
38
38
|
# - wget allows us to copy/paste commands from vendors about how to install
|
39
39
|
# software even though it does the same thing as curl
|
40
|
-
RUN apt
|
40
|
+
RUN --mount=type=cache,target=/var/cache/apt \
|
41
|
+
apt-get update --quiet --yes && \
|
41
42
|
apt-get install --no-install-recommends --quiet --yes \
|
42
43
|
ca-certificates \
|
43
44
|
curl \
|
@@ -54,16 +55,17 @@ RUN apt-get update --quiet --yes && \
|
|
54
55
|
#
|
55
56
|
# Incancation is based on: https://www.postgresql.org/download/linux/debian/
|
56
57
|
RUN /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && \
|
57
|
-
apt-get update && \
|
58
|
-
apt-get --yes --quiet install postgresql-client-16 && \
|
58
|
+
apt-get update --quiet --yes && \
|
59
|
+
apt-get --yes --quiet --no-install-recommends install postgresql-client-16 && \
|
59
60
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
60
61
|
|
61
62
|
# Set basic env vars for production
|
62
63
|
ENV RACK_ENV="production" \
|
63
64
|
BUNDLE_DEPLOYMENT="1" \
|
64
65
|
BUNDLE_PATH="/usr/local/bundle" \
|
65
|
-
BUNDLE_WITHOUT="development"
|
66
|
-
|
66
|
+
BUNDLE_WITHOUT="development"
|
67
|
+
|
68
|
+
RUN rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
67
69
|
|
68
70
|
# This makes a new image that we'll throw away after building
|
69
71
|
# needed artifacts.
|
@@ -74,35 +76,34 @@ FROM base AS build
|
|
74
76
|
# - libpq-dev is needed by postgres
|
75
77
|
# - pkg-config is, I guess, not considered "essential" (as in build-essential),
|
76
78
|
# but still needed to install downstream stuff
|
77
|
-
RUN apt
|
79
|
+
RUN --mount=type=cache,target=/var/cache/apt \
|
80
|
+
apt-get update --quiet --yes && \
|
78
81
|
apt-get install --no-install-recommends --quiet --yes \
|
79
82
|
build-essential \
|
80
83
|
git \
|
81
84
|
libpq-dev \
|
82
85
|
pkg-config
|
83
86
|
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
nvm install 22 && \
|
88
|
-
node -v && nvm current && npm -v
|
87
|
+
# Copy NodeJS from the official image. This should be faster than
|
88
|
+
# installing it via e.g. nvm
|
89
|
+
COPY --from=node:22-slim /usr/local /usr/local
|
89
90
|
|
90
|
-
# Copy
|
91
|
-
COPY . .
|
91
|
+
# Copy Gemfile only as it may not change between deploys
|
92
|
+
COPY Gemfile Gemfile.lock .
|
92
93
|
|
93
94
|
# Install RubyGems from app's Gemfile
|
94
|
-
RUN
|
95
|
+
RUN --mount=type=cache,target=${BUNDLE_PATH}/cache \
|
96
|
+
bundle install --verbose && \
|
95
97
|
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git
|
96
98
|
|
99
|
+
COPY package.json package-lock.json ./
|
97
100
|
# Install Node Modules from package.json
|
98
|
-
RUN
|
99
|
-
|
100
|
-
|
101
|
+
RUN npm clean-install --no-audit --no-fund --verbose
|
102
|
+
|
103
|
+
COPY . .
|
101
104
|
|
102
105
|
# Build all assets
|
103
|
-
RUN
|
104
|
-
nvm use default && \
|
105
|
-
bin/build-assets && \
|
106
|
+
RUN bin/build-assets && \
|
106
107
|
rm -rf node_modules
|
107
108
|
|
108
109
|
# We are now switching back to building the image that will be deployed.
|
@@ -118,7 +119,8 @@ RUN groupadd --system --gid 1000 brut && \
|
|
118
119
|
chown -R brut:brut logs tmp
|
119
120
|
USER 1000:1000
|
120
121
|
|
122
|
+
ENV APP_GIT_SHA1="${app_git_sha1}"
|
123
|
+
|
121
124
|
# This is used to execute other commands. When the app is run in production,
|
122
125
|
# this script is used to run it.
|
123
126
|
ENTRYPOINT ["/brut-app/deploy/docker-entrypoint"]
|
124
|
-
|
@@ -8,19 +8,20 @@ class HerokuConfig
|
|
8
8
|
# The format of this Hash is:
|
9
9
|
#
|
10
10
|
# {
|
11
|
-
#
|
12
|
-
# cmd:
|
11
|
+
# *image name* => {
|
12
|
+
# cmd: *command line for Dockerfile RUN directive*,
|
13
13
|
# }
|
14
14
|
# }
|
15
15
|
#
|
16
|
-
# For example, if you have the Sidekiq segment installed, `bin/run
|
16
|
+
# For example, if you have the Sidekiq segment installed, `bin/run sidekiq`
|
17
17
|
# runs Sidekiq, so you would return this hash:
|
18
18
|
#
|
19
19
|
# {
|
20
20
|
# "sidekiq" => {
|
21
|
-
# cmd: "bin/run
|
21
|
+
# cmd: "bin/run sidekiq",
|
22
22
|
# }
|
23
23
|
# }
|
24
|
-
|
24
|
+
#
|
25
|
+
def self.additional_images
|
26
|
+
end
|
25
27
|
end
|
26
|
-
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This is entirely to allow the integration test to run
|
2
|
+
# that verifies you have Sidekqi working and the segment was
|
3
|
+
# installed correctly. Once you have created a real job for
|
4
|
+
# your app and done whatever testing you need, please delete
|
5
|
+
# this job and the integration test.
|
6
|
+
class ExampleJob < AppJob
|
7
|
+
def perform(path_to_file, contents)
|
8
|
+
File.open(path_to_file, "w") do |f|
|
9
|
+
f.puts(contents)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "sidekiq"
|
2
|
+
# Encapsulates all Sidekiq configuraiton beyond what can be done in
|
3
|
+
# app/config/sidekiq.yml. You can edit this as needed.
|
4
|
+
class SidekiqSegment
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
Brut.container.store(
|
8
|
+
"flush_spans_in_sidekiq?",
|
9
|
+
"Boolean",
|
10
|
+
"True if sidekiq jobs should flush all OTel spans after the job completes"
|
11
|
+
) do |project_env|
|
12
|
+
if ENV["FLUSH_SPANS_IN_SIDEKIQ"] == "true"
|
13
|
+
true
|
14
|
+
elsif ENV["FLUSH_SPANS_IN_SIDEKIQ"] == "false"
|
15
|
+
false
|
16
|
+
else
|
17
|
+
project_env.development?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def boot!
|
23
|
+
Sidekiq.configure_server do |config|
|
24
|
+
config.redis = {
|
25
|
+
# Per https://devcenter.heroku.com/articles/connecting-heroku-redis#connecting-in-ruby
|
26
|
+
ssl_params: {
|
27
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE,
|
28
|
+
},
|
29
|
+
}
|
30
|
+
config.logger = SemanticLogger["Sidekiq:server"]
|
31
|
+
if Brut.container.flush_spans_in_sidekiq?
|
32
|
+
SemanticLogger[self.class].info("Sidekiq jobs will flush spans")
|
33
|
+
config.server_middleware do |chain|
|
34
|
+
if defined? OpenTelemetry::Instrumentation::Sidekiq::Middlewares::Server::TracerMiddleware
|
35
|
+
chain.insert_before OpenTelemetry::Instrumentation::Sidekiq::Middlewares::Server::TracerMiddleware,
|
36
|
+
Brut::BackEnd::Sidekiq::Middlewares::Server::FlushSpans
|
37
|
+
else
|
38
|
+
SemanticLogger["Sidekiq:server"].warn("OpenTelemetry::Instrumentation::Sidekiq::Middlewares::Server::TracerMiddleware not defined")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
else
|
42
|
+
SemanticLogger[self.class].info("Sidekiq jobs will not flush spans")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Sidekiq.configure_client do |config|
|
47
|
+
config.redis = {
|
48
|
+
# Per https://devcenter.heroku.com/articles/connecting-heroku-redis#connecting-in-ruby
|
49
|
+
ssl_params: {
|
50
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE,
|
51
|
+
},
|
52
|
+
}
|
53
|
+
config.logger = SemanticLogger["Sidekiq:client"]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "timeout"
|
3
|
+
|
4
|
+
# This test will ensure that Sidekiq segment was installed
|
5
|
+
# correctly and your app can use Sidekiq. Once you have
|
6
|
+
# created a real job for your app, delete ExampleJob and this test.
|
7
|
+
RSpec.describe "Sidekiq is working", e2e: true do
|
8
|
+
it "processes jobs using the real Sidekiq server" do
|
9
|
+
# ExampleJob will write contents to a file. This
|
10
|
+
# test will delete that fail, queue the job, then
|
11
|
+
# wait up to 5 seconds for the job to run and create
|
12
|
+
# the file with the contents. This should be more than
|
13
|
+
# enough time.
|
14
|
+
file = Brut.container.tmp_dir / "sidekiq_test.txt"
|
15
|
+
if file.exist?
|
16
|
+
file.delete
|
17
|
+
end
|
18
|
+
|
19
|
+
# If the file still exists at this point, the test
|
20
|
+
# will pass even if Sidekiq is not working.
|
21
|
+
confidence_check { expect(file.exist?).to eq(false) }
|
22
|
+
|
23
|
+
ExampleJob.perform_async(file.to_s, "test content for file")
|
24
|
+
queue_name = ExampleJob.sidekiq_options["queue"]
|
25
|
+
expect {
|
26
|
+
Timeout.timeout(5) do
|
27
|
+
loop do
|
28
|
+
if file.exist?
|
29
|
+
break
|
30
|
+
end
|
31
|
+
sleep 0.1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
}.not_to raise_error
|
35
|
+
content = File.read(file)
|
36
|
+
expect(content).to eq("test content for file\n")
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mkbrut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dave Copeland
|
@@ -34,6 +34,8 @@ extra_rdoc_files: []
|
|
34
34
|
files:
|
35
35
|
- exe/mkbrut
|
36
36
|
- lib/mkbrut.rb
|
37
|
+
- lib/mkbrut/add_segment.rb
|
38
|
+
- lib/mkbrut/add_segment_options.rb
|
37
39
|
- lib/mkbrut/app.rb
|
38
40
|
- lib/mkbrut/app_id.rb
|
39
41
|
- lib/mkbrut/app_name.rb
|
@@ -51,6 +53,7 @@ files:
|
|
51
53
|
- lib/mkbrut/ops/base_op.rb
|
52
54
|
- lib/mkbrut/ops/copy_file.rb
|
53
55
|
- lib/mkbrut/ops/insert_code_in_method.rb
|
56
|
+
- lib/mkbrut/ops/insert_into_file.rb
|
54
57
|
- lib/mkbrut/ops/insert_route.rb
|
55
58
|
- lib/mkbrut/ops/mkdir.rb
|
56
59
|
- lib/mkbrut/ops/prism_parsing_op.rb
|
@@ -68,7 +71,6 @@ files:
|
|
68
71
|
- lib/mkbrut/versions.rb
|
69
72
|
- templates/Base/.dockerignore
|
70
73
|
- templates/Base/.env.development.erb
|
71
|
-
- templates/Base/.env.development.local
|
72
74
|
- templates/Base/.env.test.erb
|
73
75
|
- templates/Base/.gitignore
|
74
76
|
- templates/Base/.projections.json
|
@@ -119,6 +121,7 @@ files:
|
|
119
121
|
- templates/Base/bin/dev
|
120
122
|
- templates/Base/bin/release
|
121
123
|
- templates/Base/bin/run
|
124
|
+
- templates/Base/bin/run.run
|
122
125
|
- templates/Base/bin/scaffold
|
123
126
|
- templates/Base/bin/setup
|
124
127
|
- templates/Base/bin/startup-message
|
@@ -173,6 +176,14 @@ files:
|
|
173
176
|
- templates/segments/Heroku/deploy/Dockerfile
|
174
177
|
- templates/segments/Heroku/deploy/docker-entrypoint
|
175
178
|
- templates/segments/Heroku/deploy/heroku_config.rb
|
179
|
+
- templates/segments/Sidekiq/app/boot_sidekiq.rb
|
180
|
+
- templates/segments/Sidekiq/app/config/sidekiq.yml
|
181
|
+
- templates/segments/Sidekiq/app/src/back_end/jobs/app_job.rb
|
182
|
+
- templates/segments/Sidekiq/app/src/back_end/jobs/example_job.rb
|
183
|
+
- templates/segments/Sidekiq/app/src/back_end/segments/sidekiq_segment.rb
|
184
|
+
- templates/segments/Sidekiq/bin/run.sidekiq
|
185
|
+
- templates/segments/Sidekiq/specs/back_end/jobs/example_job.spec.rb
|
186
|
+
- templates/segments/Sidekiq/specs/integration/sidekiq_works.spec.rb
|
176
187
|
homepage: https://brutrb.com
|
177
188
|
licenses: []
|
178
189
|
metadata: {}
|
@@ -190,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
190
201
|
- !ruby/object:Gem::Version
|
191
202
|
version: '0'
|
192
203
|
requirements: []
|
193
|
-
rubygems_version: 3.7.
|
204
|
+
rubygems_version: 3.7.2
|
194
205
|
specification_version: 4
|
195
206
|
summary: Create a new Brut App
|
196
207
|
test_files: []
|