rails_template_18f 0.3.0 → 0.5.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Gemfile +0 -2
  4. data/Gemfile.lock +4 -4
  5. data/exe/rails_template_18f +31 -0
  6. data/lib/generators/rails_template18f/active_storage/active_storage_generator.rb +141 -0
  7. data/lib/generators/rails_template18f/active_storage/templates/app/jobs/file_scan_job.rb +33 -0
  8. data/lib/generators/rails_template18f/active_storage/templates/app/models/file_upload.rb +25 -0
  9. data/lib/generators/rails_template18f/active_storage/templates/doc/adr/clamav.md.tt +30 -0
  10. data/lib/generators/rails_template18f/active_storage/templates/spec/jobs/file_scan_job_spec.rb +35 -0
  11. data/lib/generators/rails_template18f/active_storage/templates/spec/models/file_upload_spec.rb +38 -0
  12. data/lib/generators/rails_template18f/circleci/circleci_generator.rb +4 -1
  13. data/lib/generators/rails_template18f/cloud_gov_config/cloud_gov_config_generator.rb +29 -0
  14. data/lib/generators/rails_template18f/cloud_gov_config/templates/app/models/cloud_gov_config.rb +15 -0
  15. data/lib/generators/rails_template18f/cloud_gov_config/templates/spec/models/cloud_gov_config_spec.rb +44 -0
  16. data/lib/generators/rails_template18f/i18n/i18n_generator.rb +8 -9
  17. data/lib/generators/rails_template18f/i18n_js/i18n_js_generator.rb +59 -0
  18. data/lib/generators/rails_template18f/i18n_js/templates/lib/tasks/i18n.rake +9 -0
  19. data/lib/generators/rails_template18f/newrelic/newrelic_generator.rb +2 -0
  20. data/lib/generators/rails_template18f/sidekiq/sidekiq_generator.rb +81 -0
  21. data/lib/generators/rails_template18f/sidekiq/templates/config/initializers/redis.rb +14 -0
  22. data/lib/generators/rails_template18f/terraform/templates/terraform/README.md.tt +1 -1
  23. data/lib/generators/rails_template18f/terraform/templates/terraform/production/main.tf.tt +37 -5
  24. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/main.tf.tt +50 -0
  25. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/providers.tf +16 -0
  26. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/variables.tf +47 -0
  27. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/main.tf.tt +23 -0
  28. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/providers.tf +16 -0
  29. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/variables.tf +42 -0
  30. data/lib/generators/rails_template18f/terraform/templates/terraform/staging/main.tf.tt +37 -5
  31. data/lib/generators/rails_template18f/terraform/terraform_generator.rb +10 -12
  32. data/lib/rails_template18f/app_updater.rb +19 -0
  33. data/lib/rails_template18f/generators/base.rb +37 -5
  34. data/lib/rails_template18f/generators/cloud_gov_options.rb +0 -4
  35. data/lib/rails_template18f/version.rb +1 -1
  36. data/rails-template-18f.gemspec +1 -0
  37. data/template.rb +29 -1
  38. data/templates/Brewfile +14 -0
  39. data/templates/README.md.tt +9 -10
  40. data/templates/app/views/application/_header.html.erb +0 -1
  41. data/templates/app/views/application/_usa_banner.html.erb +2 -0
  42. data/templates/bin/with-server +1 -2
  43. data/templates/config/deployment/staging.yml +1 -1
  44. data/templates/config/environments/ci.rb +0 -1
  45. data/templates/doc/compliance/apps/application.boundary.md.tt +0 -7
  46. data/templates/env +1 -1
  47. metadata +37 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c732c75ebc9f84d2f730c43042eb922392954f18e8a2500722e4f2e070f329a
4
- data.tar.gz: 0fca28e40b443de8032d7a76f38f889449238c62ffeb15444b78a73d9482eaf0
3
+ metadata.gz: c9452d60916a767ee1b00bb0a76e6582d4d1ed760b3bf0c4d68a07c8225ddfd4
4
+ data.tar.gz: 481c2ee7693849f2c21d382d78edd08a59370b8c2b440bef8502d22a039a2243
5
5
  SHA512:
6
- metadata.gz: c3900e08a7ae9227bad8e4f341b867556ddbaae1614a526ab64be758cd0c78ad0b09e4be1587da1038623151a045f05aa7a7fe3ea82676479f29d54d4cc4d94f
7
- data.tar.gz: 7da9792ec9280f443a5bfa2bfc6ffca23a60cf984cb64c0773019dd27ea130b02fcd331647493b81ffcac434fbc8bb5cdf064c1d3e5a5714617a4bcac5335c90
6
+ metadata.gz: 8a5b1be47db25412b0ba04dd19a395401f86b6d5ee14b907c2de54e95bfae159a6407851bfcd83b07ac20ca9423de568580c5cfdec0c5d49bfd5e2f7f62e59df
7
+ data.tar.gz: 75ff52ec4661f95c4da617217d7eb7dc993192610d9209ab3f9a438504111281a8c779db3074c1d6506073b126d437c6aaebe328b11add1363797938f70d006e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2022-03-04
4
+
5
+ - use Brewfile for installing homebrew-based dependencies
6
+ - move test site banner to the _usa_banner.html.erb partial
7
+ - use dockerize within bin/with-server to wait for rails to start
8
+
9
+ ## [0.4.1] - 2022-02-25
10
+
11
+ - update gem dependencies
12
+ - fix issues when included gem hadn't been previously installed
13
+
14
+ ## [0.4.0] - 2022-02-24
15
+
16
+ - helper script to run rails app:update
17
+ - cloud.gov configuration helper generator
18
+ - activestorage/clamav generator
19
+ - activejob/sidekiq generator
20
+ - i18n-js generator
21
+
3
22
  ## [0.3.0] - 2022-02-17
4
23
 
5
24
  - i18n generator
data/Gemfile CHANGED
@@ -7,6 +7,4 @@ gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
9
 
10
- gem "colorize", "~> 0.8"
11
-
12
10
  gem "byebug"
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_template_18f (0.3.0)
4
+ rails_template_18f (0.5.0)
5
5
  activesupport (~> 7.0.0)
6
+ colorize (~> 0.8)
6
7
  railties (~> 7.0.0)
7
8
  thor (~> 1.0)
8
9
 
@@ -46,9 +47,9 @@ GEM
46
47
  nokogiri (>= 1.5.9)
47
48
  method_source (1.0.0)
48
49
  minitest (5.15.0)
49
- nokogiri (1.13.1-x86_64-darwin)
50
+ nokogiri (1.13.3-x86_64-darwin)
50
51
  racc (~> 1.4)
51
- nokogiri (1.13.1-x86_64-linux)
52
+ nokogiri (1.13.3-x86_64-linux)
52
53
  racc (~> 1.4)
53
54
  parallel (1.21.0)
54
55
  parser (3.1.0.0)
@@ -125,7 +126,6 @@ PLATFORMS
125
126
  DEPENDENCIES
126
127
  ammeter (~> 1.1)
127
128
  byebug
128
- colorize (~> 0.8)
129
129
  rails_template_18f!
130
130
  rake (~> 13.0)
131
131
  rspec (~> 3.11)
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "thor"
5
+ require_relative "../lib/rails_template18f/version"
5
6
 
6
7
  class CLI < Thor
7
8
  include Thor::Actions
@@ -24,6 +25,36 @@ class CLI < Thor
24
25
  railsrc = options[:hotwire] ? "railsrc-hotwire" : "railsrc"
25
26
  run "rails new #{app_directory} --rc=#{File.join(gem_path, railsrc)} --template=#{File.join(gem_path, "template.rb")} #{rails_arguments.join(" ")}"
26
27
  end
28
+
29
+ desc "update", "Run rails app:update with some enhancements"
30
+ long_desc <<-LONGDESC
31
+ Run `rails app:update` with frameworks fully defined by what is commented out at the top
32
+ of config/application.rb
33
+
34
+ Example: to enable ActiveStorage
35
+
36
+ 1) Uncomment `require "active_storage/engine"` in `config/application.rb`
37
+
38
+ 2) Run `bin/rails active_storage:install`
39
+
40
+ 3) Run bundle exec rails_template_18f update
41
+
42
+ 4) Optional: run other rails_template18f generators that may be applicable
43
+ LONGDESC
44
+ def update
45
+ require_relative "../lib/rails_template18f/app_updater"
46
+ require "rails/command"
47
+ Rails::Command.invoke "app:update"
48
+ end
49
+
50
+ desc "version", "Output gem version"
51
+ def version
52
+ puts RailsTemplate18f::VERSION
53
+ end
54
+
55
+ def self.exit_on_failure?
56
+ true
57
+ end
27
58
  end
28
59
 
29
60
  CLI.start(ARGV)
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsTemplate18f
6
+ module Generators
7
+ class ActiveStorageGenerator < ::Rails::Generators::Base
8
+ include Base
9
+
10
+ desc <<~DESC
11
+ Description:
12
+ Document use of Clamav as ActiveStorage scanner
13
+ DESC
14
+
15
+ def configure_active_storage
16
+ generate "rails_template18f:cloud_gov_config", inline: true
17
+ rails_command "active_storage:install", inline: true
18
+ comment_lines "config/environments/production.rb", /active_storage\.service/
19
+ insert_into_file "config/environments/production.rb", "\n config.active_storage.service = :amazon", after: /active_storage\.service.*$/
20
+ environment "config.active_storage.service = :local", env: "ci"
21
+ append_to_file "config/storage.yml", <<~EOYAML
22
+
23
+ amazon:
24
+ service: S3
25
+ access_key_id: <%= CloudGovConfig.dig(:s3, :credentials, :access_key_id) %>
26
+ secret_access_key: <%= CloudGovConfig.dig(:s3, :credentials, :secret_access_key) %>
27
+ region: us-gov-west-1
28
+ bucket: <%= CloudGovConfig.dig(:s3, :credentials, :bucket) %>
29
+ EOYAML
30
+ end
31
+
32
+ def install_gems
33
+ faraday_installed = gem_installed?("faraday")
34
+ middleware_installed = gem_installed?("faraday-multipart")
35
+ sdk_installed = gem_installed?("aws-sdk-s3")
36
+ return if faraday_installed && middleware_installed && sdk_installed
37
+ gem "faraday", "~> 2.2" unless faraday_installed
38
+ gem "faraday-multipart", "~> 1.0" unless middleware_installed
39
+ unless sdk_installed
40
+ gem_group :production do
41
+ gem "aws-sdk-s3", "~> 1.112"
42
+ end
43
+ end
44
+ bundle_install
45
+ end
46
+
47
+ def create_scanned_upload_model_and_job
48
+ generate :migration, "CreateFileUploads", "file:attachment", "record:references{polymorphic}", "scan_status:string", inline: true
49
+ migration_file = Dir.glob(File.expand_path(File.join("db", "migrate", "[0-9]*_*.rb"), destination_root)).grep(/\d+_create_file_uploads.rb$/).first
50
+ unless migration_file.nil?
51
+ gsub_file migration_file, ":scan_status", ":scan_status, null: false, default: \"uploaded\""
52
+ end
53
+ directory "app"
54
+ directory "spec"
55
+ end
56
+
57
+ def configure_local_clamav_runner
58
+ append_to_file "Procfile.dev", "clamav: docker run --rm -p 9443:9443 ajilaag/clamav-rest:20211229\n"
59
+ end
60
+
61
+ def configure_clamav_env_var
62
+ append_to_file ".env", <<~EOM
63
+
64
+ # CLAMAV_API_URL tells FileScanJob where to send files for virus scans
65
+ CLAMAV_API_URL=https://localhost:9443
66
+ EOM
67
+ insert_into_file "manifest.yml", " CLAMAV_API_URL: \"https://#{app_name}-clamapi-((env)).apps.internal:9443\"\n", before: /^\s+processes:/
68
+ insert_into_file "manifest.yml", "\n - #{app_name}-s3-((env))", after: "services:"
69
+ end
70
+
71
+ def update_boundary_diagram
72
+ boundary_filename = "doc/compliance/apps/application.boundary.md"
73
+
74
+ insert_into_file boundary_filename, indent(<<~EOB, 16), after: /ContainerDb\(app_db.*$\n/
75
+ Container(clamav, "File Scanning API", "ClamAV", "Internal application for scanning user uploads")
76
+ ContainerDb(app_s3, "File Storage", "AWS S3", "User-uploaded file storage")
77
+ EOB
78
+ insert_into_file boundary_filename, <<~EOB, before: "@enduml"
79
+ Rel(app, app_s3, "reads/writes file data", "https (443)")
80
+ EOB
81
+ if has_active_job?
82
+ insert_into_file boundary_filename, <<~EOB, before: "@enduml"
83
+ Rel(worker, app_s3, "reads/writes file data", "https (443)")
84
+ Rel(worker, clamav, "scans files", "https (9443)")
85
+ EOB
86
+ end
87
+ end
88
+
89
+ def update_data_model_uml
90
+ insert_into_file "doc/compliance/apps/data.logical.md", data_model_uml, before: "@enduml"
91
+ end
92
+
93
+ def generate_adr
94
+ adr_dir = File.expand_path(File.join("doc", "adr"), destination_root)
95
+ if Dir.exist? adr_dir
96
+ @next_adr_id = `ls #{adr_dir} | tail -n 1 | awk -F '-' '{print $1}'`.strip.to_i + 1
97
+ template "doc/adr/clamav.md", "doc/adr/#{"%04d" % @next_adr_id}-clamav-file-scanning.md"
98
+ end
99
+ end
100
+
101
+ no_tasks do
102
+ def data_model_uml
103
+ <<~UML
104
+ class file_uploads {
105
+ * id : bigint <<generated>>
106
+ * scan_status : string
107
+ * record_id : bigint
108
+ * record_type : string
109
+ }
110
+ class active_storage_attachments {
111
+ * id : bigint <<generated>>
112
+ * name : string
113
+ * record_type : string
114
+ * record_id : bigint
115
+ * blob_id : bigint
116
+ * created_at : timestamp without time zone
117
+ }
118
+ class active_storage_blobs {
119
+ * id : bigint <<generated>>
120
+ * key : string
121
+ * filename : string
122
+ content_type : string
123
+ metadata : text
124
+ * service_name : string
125
+ * byte_size : bigint
126
+ checksum : string
127
+ * created_at : timestamp without time zone
128
+ }
129
+ class active_storage_variant_records {
130
+ * id : bigint <<generated>>
131
+ * variation_digest : string
132
+ }
133
+ file_uploads ||--|| active_storage_attachments
134
+ active_storage_attachments ||--|{ active_storage_blobs
135
+ active_storage_variant_records ||--|{ active_storage_blobs
136
+ UML
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,33 @@
1
+ class FileScanJob < ApplicationJob
2
+ queue_as :default
3
+
4
+ def perform(file_upload)
5
+ return if file_upload.nil? || file_upload.clean?
6
+ file_upload.open do |file|
7
+ payload = {file: Faraday::Multipart::FilePart.new(
8
+ file,
9
+ file_upload.content_type,
10
+ file_upload.filename
11
+ )}
12
+ response = connection.post("/scan", payload)
13
+ if response.success?
14
+ file_upload.update_columns scan_status: "scanned", updated_at: Time.now
15
+ else
16
+ logger.error "File Scan for #{file_upload.id} failed: #{response.body}"
17
+ file_upload.update_columns scan_status: "quarantined", updated_at: Time.now
18
+ end
19
+ end
20
+ rescue => ex
21
+ file_upload&.update_columns scan_status: "scan_failed", updated_at: Time.now
22
+ raise ex
23
+ end
24
+
25
+ def connection
26
+ @connection ||= Faraday.new(
27
+ url: ENV["CLAMAV_API_URL"],
28
+ ssl: {verify: false}
29
+ ) do |f|
30
+ f.request :multipart
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ class FileUpload < ApplicationRecord
2
+ belongs_to :record, polymorphic: true
3
+ has_one_attached :file
4
+
5
+ delegate :open, :content_type, to: :file
6
+
7
+ validates_presence_of :file
8
+ validates_inclusion_of :scan_status, in: %w[uploaded scan_failed scanned quarantined]
9
+
10
+ after_commit :scan
11
+
12
+ def clean?
13
+ scan_status == "scanned"
14
+ end
15
+
16
+ def filename
17
+ file.filename.to_s
18
+ end
19
+
20
+ private
21
+
22
+ def scan
23
+ FileScanJob.perform_later self
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ # <%= @next_adr_id %>. ClamAV File Scanning
2
+
3
+ Date: <%= Date.today.iso8601 %>
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ In order to satisfy the [RA-5](https://nvd.nist.gov/800-53/Rev4/control/RA-5)
12
+ control around vulnerability scanning, we wish to scan all user-uploaded files
13
+ with a malware detection service. These files are user-supplied, and thus
14
+ cannot fit into our other security controls built into the CI/CD pipeline.
15
+
16
+ ## Decision
17
+
18
+ We will run a ClamAV daemon, fronted by a REST API that we will pass all user-uploaded
19
+ files through as part of the upload process.
20
+ <% if terraform_dir_exists? %>
21
+ The ClamAV app is deployed along with other infrastructure by terraform.
22
+ <% else %>
23
+ The ClamAV app is based on a [separate government-controlled repository](https://github.com/18f/clamav-api-cg-app)
24
+ <% end %>
25
+
26
+ ## Consequences
27
+
28
+ While our user-supplied files will now have vulnerability protection, this architecture
29
+ does not provide scanning of the application itself. Therefore we must find other solutions
30
+ to addressing SI-3 or RA-5 in the context of the application.
@@ -0,0 +1,35 @@
1
+ require "rails_helper"
2
+
3
+ RSpec.describe FileScanJob, type: :job do
4
+ subject { described_class.new }
5
+ let(:scanned_file) { double(clean?: true) }
6
+ let(:unscanned_file) { double(id: 1, clean?: false, content_type: "text/plain", filename: "test.txt") }
7
+ let(:success_response) { double(success?: true) }
8
+ let(:error_response) { double(success?: false, body: "Error response body") }
9
+
10
+ it "deals with a nil argument" do
11
+ expect { subject.perform nil }.to_not raise_error
12
+ end
13
+
14
+ it "returns quickly if the file is already scanned" do
15
+ expect { subject.perform scanned_file }.to_not raise_error
16
+ end
17
+
18
+ it "updates the scan_status after scanning the file" do
19
+ now = Time.now
20
+ allow(Time).to receive(:now).and_return now
21
+ allow(unscanned_file).to receive(:open).and_yield __FILE__
22
+ expect(unscanned_file).to receive(:update_columns).with scan_status: "scanned", updated_at: Time.now
23
+ allow(subject).to receive(:connection).and_return double(post: success_response)
24
+ subject.perform unscanned_file
25
+ end
26
+
27
+ it "marks the file as quarantined when dirty" do
28
+ now = Time.now
29
+ allow(Time).to receive(:now).and_return now
30
+ allow(unscanned_file).to receive(:open).and_yield __FILE__
31
+ expect(unscanned_file).to receive(:update_columns).with scan_status: "quarantined", updated_at: Time.now
32
+ allow(subject).to receive(:connection).and_return double(post: error_response)
33
+ subject.perform unscanned_file
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ require "rails_helper"
2
+
3
+ RSpec.describe FileUpload, type: :model do
4
+ subject { described_class.new }
5
+
6
+ describe "validations" do
7
+ before do
8
+ subject.file.attach(io: File.open(__FILE__), filename: "file_upload_spec.rb")
9
+ end
10
+
11
+ %w[uploaded scan_failed scanned quarantined].each do |valid_status|
12
+ it "allows scan_status=#{valid_status}" do
13
+ pending "#{described_class.name} cannot be valid without a record to belong_to"
14
+ subject.scan_status = valid_status
15
+ expect(subject).to be_valid
16
+ end
17
+ end
18
+
19
+ it "is invalid with a bad scan_status" do
20
+ subject.scan_status = "invalid"
21
+ expect(subject).to_not be_valid
22
+ end
23
+ end
24
+
25
+ describe "#clean?" do
26
+ it "returns true when scan_status is scanned" do
27
+ subject.scan_status = "scanned"
28
+ expect(subject).to be_clean
29
+ end
30
+
31
+ it "returns false when scan_status is not scanned" do
32
+ subject.scan_status = "uploaded"
33
+ expect(subject).to_not be_clean
34
+ subject.scan_status = "quarantined"
35
+ expect(subject).to_not be_clean
36
+ end
37
+ end
38
+ end
@@ -14,7 +14,10 @@ module RailsTemplate18f
14
14
  DESC
15
15
 
16
16
  def install_needed_gems
17
- gem "rspec_junit_formatter", "~> 0.5", group: :test
17
+ gem_name = "rspec_junit_formatter"
18
+ return if gem_installed? gem_name
19
+ gem gem_name, "~> 0.5", group: :test
20
+ bundle_install
18
21
  end
19
22
 
20
23
  def install_pipeline
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsTemplate18f
6
+ module Generators
7
+ class CloudGovConfigGenerator < ::Rails::Generators::Base
8
+ include Base
9
+
10
+ desc <<~DESC
11
+ Description:
12
+ Install a helper class to retrieve configuration from ENV["VCAP_SERVICES"]
13
+ DESC
14
+
15
+ def install_climate_control
16
+ return if gem_installed?("climate_control")
17
+ gem_group :test do
18
+ gem "climate_control", "~> 1.0"
19
+ end
20
+ bundle_install
21
+ end
22
+
23
+ def install_model_and_test
24
+ copy_file "app/models/cloud_gov_config.rb"
25
+ copy_file "spec/models/cloud_gov_config_spec.rb"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CloudGovConfig
4
+ ENV_VARIABLE = "VCAP_SERVICES"
5
+
6
+ def self.dig(*path)
7
+ return nil if ENV[ENV_VARIABLE].blank?
8
+ first, *rest = path
9
+ vcap_services[first]&.first&.dig(*rest)
10
+ end
11
+
12
+ def self.vcap_services
13
+ @vcap_services ||= JSON.parse(ENV[ENV_VARIABLE]).with_indifferent_access
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+
5
+ RSpec.describe CloudGovConfig, type: :model do
6
+ subject { described_class }
7
+
8
+ describe ".dig" do
9
+ context "VCAP_SERVICES is blank" do
10
+ it "returns nil" do
11
+ expect(subject.dig(:s3, :credentials, :bucket)).to be_nil
12
+ end
13
+ end
14
+
15
+ context "VCAP_SERVICES is set" do
16
+ let(:bucket_name) { "bucket-name" }
17
+ let(:vcap) {
18
+ {
19
+ s3: [
20
+ {
21
+ credentials: {
22
+ bucket: bucket_name
23
+ }
24
+ }
25
+ ]
26
+ }
27
+ }
28
+
29
+ around do |example|
30
+ ClimateControl.modify VCAP_SERVICES: vcap.to_json do
31
+ example.run
32
+ end
33
+ end
34
+
35
+ it "can find a path" do
36
+ expect(subject.dig(:s3, :credentials, :bucket)).to eq bucket_name
37
+ end
38
+
39
+ it "returns nil for a missing path" do
40
+ expect(subject.dig(:s3, :credentials, :other)).to be_nil
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
- require "bundler"
5
4
 
6
5
  module RailsTemplate18f
7
6
  module Generators
@@ -16,17 +15,17 @@ module RailsTemplate18f
16
15
  Always installs configuration for English
17
16
  DESC
18
17
 
19
- def install_helper_gem_and_tasks
20
- return if file_content("Gemfile").match?(/gem "i18n-tasks"/)
18
+ def install_gem
19
+ return if gem_installed?("i18n-tasks")
21
20
  gem_group :development, :test do
22
21
  gem "i18n-tasks", "~> 0.9"
23
22
  end
24
- Bundler.with_original_env do
25
- in_root do
26
- run "bundle install"
27
- run "cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/"
28
- run "cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/"
29
- end
23
+ end
24
+
25
+ def install_helper_tasks
26
+ bundle_install do
27
+ run "cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/"
28
+ run "cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/"
30
29
  end
31
30
  insert_into_file "config/i18n-tasks.yml", "\n#{indent("- app/assets/builds", 4)}", after: "exclude:"
32
31
  uncomment_lines "config/i18n-tasks.yml", "ignore_missing:"
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsTemplate18f
6
+ module Generators
7
+ class I18nJsGenerator < ::Rails::Generators::Base
8
+ include Base
9
+
10
+ desc <<~DESC
11
+ Description:
12
+ Install and configure i18n-js gem to provide translations to JS code.
13
+
14
+ By default, will only export translations with keys that match `*.js.*`
15
+ DESC
16
+
17
+ def install_gem_and_tasks
18
+ return if gem_installed?("i18n-js")
19
+ gem "i18n-js", "~> 3.9"
20
+ bundle_install do
21
+ run "yarn add i18n-js"
22
+ generate "i18n:js:config"
23
+ end
24
+ end
25
+
26
+ def configure_translation_yaml
27
+ append_to_file "config/i18n-js.yml", <<~EOYAML
28
+ # remove `only` to include all translations
29
+ translations:
30
+ - file: "app/assets/builds/translations.js"
31
+ only: "*.js.*"
32
+ EOYAML
33
+ end
34
+
35
+ def configure_asset_pipeline
36
+ copy_file "lib/tasks/i18n.rake"
37
+ environment "config.middleware.use I18n::JS::Middleware", env: :development
38
+ insert_into_file "app/views/layouts/application.html.erb", indent(<<~EOHTML, 4), after: /<%= stylesheet_link_tag "application".*$\n/
39
+ <%= javascript_include_tag "i18n", "data-turbo-track": "reload" %>
40
+ <%= javascript_include_tag "translations", "data-turbo-track": "reload" %>
41
+ EOHTML
42
+ append_to_file "app/assets/config/manifest.js", <<~EOJS
43
+ //= link i18n.js
44
+ //= link translations.js
45
+ EOJS
46
+ end
47
+
48
+ def ignore_generated_file
49
+ unless skip_git?
50
+ append_to_file ".gitignore", <<~EOM
51
+
52
+ # Generated by i18n-js
53
+ /public/javascripts/i18n.js
54
+ EOM
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,9 @@
1
+ # export translations as part of asset precompile
2
+
3
+ Rake::Task["assets:precompile"].enhance(["i18n:js:export"])
4
+
5
+ if Rake::Task.task_defined?("test:prepare")
6
+ Rake::Task["test:prepare"].enhance(["i18n:js:export"])
7
+ elsif Rake::Task.task_defined?("db:test:prepare")
8
+ Rake::Task["db:test:prepare"].enhance(["i18n:js:export"])
9
+ end
@@ -23,7 +23,9 @@ module RailsTemplate18f
23
23
  end
24
24
 
25
25
  def install_gem
26
+ return if gem_installed?("newrelic_rpm")
26
27
  gem "newrelic_rpm", "~> 8.4"
28
+ bundle_install
27
29
  end
28
30
 
29
31
  def install_config