rails_template_18f 0.3.0 → 0.4.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/Gemfile.lock +1 -1
  4. data/exe/rails_template_18f +31 -0
  5. data/lib/generators/rails_template18f/active_storage/active_storage_generator.rb +135 -0
  6. data/lib/generators/rails_template18f/active_storage/templates/app/jobs/file_scan_job.rb +33 -0
  7. data/lib/generators/rails_template18f/active_storage/templates/app/models/file_upload.rb +25 -0
  8. data/lib/generators/rails_template18f/active_storage/templates/doc/adr/clamav.md.tt +30 -0
  9. data/lib/generators/rails_template18f/active_storage/templates/spec/jobs/file_scan_job_spec.rb +35 -0
  10. data/lib/generators/rails_template18f/active_storage/templates/spec/models/file_upload_spec.rb +38 -0
  11. data/lib/generators/rails_template18f/cloud_gov_config/cloud_gov_config_generator.rb +28 -0
  12. data/lib/generators/rails_template18f/cloud_gov_config/templates/app/models/cloud_gov_config.rb +15 -0
  13. data/lib/generators/rails_template18f/cloud_gov_config/templates/spec/models/cloud_gov_config_spec.rb +44 -0
  14. data/lib/generators/rails_template18f/i18n_js/i18n_js_generator.rb +60 -0
  15. data/lib/generators/rails_template18f/i18n_js/templates/lib/tasks/i18n.rake +9 -0
  16. data/lib/generators/rails_template18f/sidekiq/sidekiq_generator.rb +70 -0
  17. data/lib/generators/rails_template18f/sidekiq/templates/config/initializers/redis.rb +14 -0
  18. data/lib/generators/rails_template18f/terraform/templates/terraform/production/main.tf.tt +37 -5
  19. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/main.tf.tt +50 -0
  20. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/providers.tf +16 -0
  21. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/variables.tf +47 -0
  22. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/main.tf.tt +23 -0
  23. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/providers.tf +16 -0
  24. data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/variables.tf +42 -0
  25. data/lib/generators/rails_template18f/terraform/templates/terraform/staging/main.tf.tt +37 -5
  26. data/lib/generators/rails_template18f/terraform/terraform_generator.rb +0 -11
  27. data/lib/rails_template18f/app_updater.rb +19 -0
  28. data/lib/rails_template18f/generators/base.rb +22 -5
  29. data/lib/rails_template18f/generators/cloud_gov_options.rb +0 -4
  30. data/lib/rails_template18f/version.rb +1 -1
  31. data/template.rb +16 -0
  32. data/templates/config/deployment/staging.yml +1 -1
  33. data/templates/config/environments/ci.rb +0 -1
  34. data/templates/doc/compliance/apps/application.boundary.md.tt +0 -7
  35. metadata +22 -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: c87267d368d3e90a500c0fc1bf23312a3f6edf437ba991a9a4c7b994fff4282d
4
+ data.tar.gz: ec24cf97ec587cf730bb2f63ff4cf0fd5b13ae64364b175041b0b0afd42a4321
5
5
  SHA512:
6
- metadata.gz: c3900e08a7ae9227bad8e4f341b867556ddbaae1614a526ab64be758cd0c78ad0b09e4be1587da1038623151a045f05aa7a7fe3ea82676479f29d54d4cc4d94f
7
- data.tar.gz: 7da9792ec9280f443a5bfa2bfc6ffca23a60cf984cb64c0773019dd27ea130b02fcd331647493b81ffcac434fbc8bb5cdf064c1d3e5a5714617a4bcac5335c90
6
+ metadata.gz: c2c05519936ff836d7c99dd16a5363755a6e86731c5d41454adbc0f6a7ada083fd1b636d0f5da3db446225866f095b42c2278100c85d3c10a3a2182100fcf550
7
+ data.tar.gz: b2e6da4f298a5c0054997c2c44244e4956044af83af81792937adf7d52716a68b3f8ebf8332f598943b9bad9a37bebf3c6dd4da0eb196ac119cc32455db00e57
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2022-02-24
4
+
5
+ - helper script to run rails app:update
6
+ - cloud.gov configuration helper generator
7
+ - activestorage/clamav generator
8
+ - activejob/sidekiq generator
9
+ - i18n-js generator
10
+
3
11
  ## [0.3.0] - 2022-02-17
4
12
 
5
13
  - i18n generator
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_template_18f (0.3.0)
4
+ rails_template_18f (0.4.0)
5
5
  activesupport (~> 7.0.0)
6
6
  railties (~> 7.0.0)
7
7
  thor (~> 1.0)
@@ -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,135 @@
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"
17
+ rails_command "active_storage:install"
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
+ gem "faraday", "~> 2.2"
34
+ gem "faraday-multipart", "~> 1.0"
35
+ gem_group :production do
36
+ gem "aws-sdk-s3", "~> 1.112"
37
+ end
38
+ end
39
+
40
+ def create_scanned_upload_model_and_job
41
+ generate :migration, "CreateFileUploads", "file:attachment", "record:references{polymorphic}", "scan_status:string"
42
+ migration_file = Dir.glob(File.expand_path(File.join("db", "migrate", "[0-9]*_*.rb"), destination_root)).grep(/\d+_create_file_uploads.rb$/).first
43
+ unless migration_file.nil?
44
+ gsub_file migration_file, ":scan_status", ":scan_status, null: false, default: \"uploaded\""
45
+ end
46
+ directory "app"
47
+ directory "spec"
48
+ end
49
+
50
+ def configure_local_clamav_runner
51
+ append_to_file "Procfile.dev", "clamav: docker run --rm -p 9443:9443 ajilaag/clamav-rest:20211229\n"
52
+ end
53
+
54
+ def configure_clamav_env_var
55
+ append_to_file ".env", <<~EOM
56
+
57
+
58
+ # CLAMAV_API_URL tells FileScanJob where to send files for virus scans
59
+ CLAMAV_API_URL=https://localhost:9443
60
+ EOM
61
+ insert_into_file "manifest.yml", " CLAMAV_API_URL: \"https://#{app_name}-clamapi-((env)).apps.internal:9443\"\n", before: /^\s+processes:/
62
+ insert_into_file "manifest.yml", "\n - #{app_name}-s3-((env))", after: "services:"
63
+ end
64
+
65
+ def update_boundary_diagram
66
+ boundary_filename = "doc/compliance/apps/application.boundary.md"
67
+
68
+ insert_into_file boundary_filename, indent(<<~EOB, 16), after: /ContainerDb\(app_db.*$\n/
69
+ Container(clamav, "File Scanning API", "ClamAV", "Internal application for scanning user uploads")
70
+ ContainerDb(app_s3, "File Storage", "AWS S3", "User-uploaded file storage")
71
+ EOB
72
+ insert_into_file boundary_filename, <<~EOB, before: "@enduml"
73
+ Rel(app, app_s3, "reads/writes file data", "https (443)")
74
+ EOB
75
+ if has_active_job?
76
+ insert_into_file boundary_filename, <<~EOB, before: "@enduml"
77
+ Rel(worker, app_s3, "reads/writes file data", "https (443)")
78
+ Rel(worker, clamav, "scans files", "https (9443)")
79
+ EOB
80
+ end
81
+ end
82
+
83
+ def update_data_model_uml
84
+ insert_into_file "doc/compliance/apps/data.logical.md", data_model_uml, before: "@enduml"
85
+ end
86
+
87
+ def generate_adr
88
+ adr_dir = File.expand_path(File.join("doc", "adr"), destination_root)
89
+ if Dir.exist? adr_dir
90
+ @next_adr_id = `ls #{adr_dir} | tail -n 1 | awk -F '-' '{print $1}'`.strip.to_i + 1
91
+ template "doc/adr/clamav.md", "doc/adr/#{"%04d" % @next_adr_id}-clamav-file-scanning.md"
92
+ end
93
+ end
94
+
95
+ no_tasks do
96
+ def data_model_uml
97
+ <<~UML
98
+ class file_uploads {
99
+ * id : bigint <<generated>>
100
+ * scan_status : string
101
+ * record_id : bigint
102
+ * record_type : string
103
+ }
104
+ class active_storage_attachments {
105
+ * id : bigint <<generated>>
106
+ * name : string
107
+ * record_type : string
108
+ * record_id : bigint
109
+ * blob_id : bigint
110
+ * created_at : timestamp without time zone
111
+ }
112
+ class active_storage_blobs {
113
+ * id : bigint <<generated>>
114
+ * key : string
115
+ * filename : string
116
+ content_type : string
117
+ metadata : text
118
+ * service_name : string
119
+ * byte_size : bigint
120
+ checksum : string
121
+ * created_at : timestamp without time zone
122
+ }
123
+ class active_storage_variant_records {
124
+ * id : bigint <<generated>>
125
+ * variation_digest : string
126
+ }
127
+ file_uploads ||--|| active_storage_attachments
128
+ active_storage_attachments ||--|{ active_storage_blobs
129
+ active_storage_variant_records ||--|{ active_storage_blobs
130
+ UML
131
+ end
132
+ end
133
+ end
134
+ end
135
+ 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
@@ -0,0 +1,28 @@
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 file_content("Gemfile").match?(/gem "climate_control"/)
17
+ gem_group :test do
18
+ gem "climate_control", "~> 1.0"
19
+ end
20
+ end
21
+
22
+ def install_model_and_test
23
+ copy_file "app/models/cloud_gov_config.rb"
24
+ copy_file "spec/models/cloud_gov_config_spec.rb"
25
+ end
26
+ end
27
+ end
28
+ 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
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "bundler"
5
+
6
+ module RailsTemplate18f
7
+ module Generators
8
+ class I18nJsGenerator < ::Rails::Generators::Base
9
+ include Base
10
+
11
+ desc <<~DESC
12
+ Description:
13
+ Install and configure i18n-js gem to provide translations to JS code.
14
+
15
+ By default, will only export translations with keys that match `*.js.*`
16
+ DESC
17
+
18
+ def install_gem_and_tasks
19
+ return if file_content("Gemfile").match?(/gem "i18n-js"/)
20
+ gem "i18n-js", "~> 3.9"
21
+ Bundler.with_original_env do
22
+ in_root do
23
+ run "bundle install"
24
+ run "yarn add i18n-js"
25
+ generate "i18n:js:config"
26
+ end
27
+ end
28
+ append_to_file "config/i18n-js.yml", <<~EOYAML
29
+ # remove `only` to include all translations
30
+ translations:
31
+ - file: "app/assets/builds/translations.js"
32
+ only: "*.js.*"
33
+ EOYAML
34
+ end
35
+
36
+ def configure_asset_pipeline
37
+ copy_file "lib/tasks/i18n.rake"
38
+ environment "config.middleware.use I18n::JS::Middleware", env: :development
39
+ insert_into_file "app/views/layouts/application.html.erb", indent(<<~EOHTML, 4), after: /<%= stylesheet_link_tag "application".*$\n/
40
+ <%= javascript_include_tag "i18n", "data-turbo-track": "reload" %>
41
+ <%= javascript_include_tag "translations", "data-turbo-track": "reload" %>
42
+ EOHTML
43
+ append_to_file "app/assets/config/manifest.js", <<~EOJS
44
+ //= link i18n.js
45
+ //= link translations.js
46
+ EOJS
47
+ end
48
+
49
+ def ignore_generated_file
50
+ unless skip_git?
51
+ append_to_file ".gitignore", <<~EOM
52
+
53
+ # Generated by i18n-js
54
+ /public/javascripts/i18n.js
55
+ EOM
56
+ end
57
+ end
58
+ end
59
+ end
60
+ 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
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsTemplate18f
6
+ module Generators
7
+ class SidekiqGenerator < ::Rails::Generators::Base
8
+ include Base
9
+
10
+ desc <<~DESC
11
+ Description:
12
+ Install Sidekiq and configure it as the ActiveJob backend
13
+ DESC
14
+
15
+ def install_gem
16
+ gem "sidekiq", "~> 6.4"
17
+ end
18
+
19
+ def configure_server_runner
20
+ append_to_file "Procfile.dev", "worker: bundle exec sidekiq\n"
21
+ insert_into_file "manifest.yml", indent(<<~EOYAML), after: /processes:$\n/
22
+ - type: worker
23
+ instances: ((worker_instances))
24
+ memory: ((worker_memory))
25
+ command: bundle exec sidekiq
26
+ EOYAML
27
+ insert_into_file "manifest.yml", "\n - #{app_name}-redis-((env))", after: "services:"
28
+ inside "config/deployment" do
29
+ append_to_file "staging.yml", <<~EOYAML
30
+ worker_instances: 1
31
+ worker_memory: 256M
32
+ EOYAML
33
+ append_to_file "production.yml", <<~EOYAML
34
+ worker_instances: 1
35
+ worker_memory: 512M
36
+ EOYAML
37
+ end
38
+ end
39
+
40
+ def configure_active_job
41
+ generate "rails_template18f:cloud_gov_config"
42
+ copy_file "config/initializers/redis.rb"
43
+ application "config.active_job.queue_adapter = :sidekiq"
44
+ end
45
+
46
+ def configure_sidekiq_ui
47
+ prepend_to_file "config/routes.rb", "require \"sidekiq/web\"\n\n"
48
+ route <<~EOR
49
+ if Rails.env.development?
50
+ mount Sidekiq::Web => "/sidekiq"
51
+ end
52
+ EOR
53
+ end
54
+
55
+ def update_boundary_diagram
56
+ boundary_filename = "doc/compliance/apps/application.boundary.md"
57
+
58
+ insert_into_file boundary_filename, indent(<<~EOB, 16), after: /ContainerDb\(app_db.*$\n/
59
+ Container(worker, "<&layers> Sidekiq workers", "Ruby #{ruby_version}, Sidekiq", "Perform background work and data processing")
60
+ ContainerDb(redis, "Redis Database", "AWS ElastiCache (Redis)", "Background job queue")
61
+ EOB
62
+ insert_into_file boundary_filename, <<~EOB, before: "@enduml"
63
+ Rel(app, redis, "enqueue job parameters", "redis")
64
+ Rel(worker, redis, "dequeues job parameters", "redis")
65
+ Rel(worker, app_db, "reads/writes primary data", "psql (5432)")
66
+ EOB
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.config.to_prepare do
4
+ redis_url = CloudGovConfig.dig "aws-elasticache-redis", "credentials", "uri"
5
+ if redis_url.present?
6
+ Sidekiq.configure_server do |config|
7
+ config.redis = {url: redis_url, ssl: true}
8
+ end
9
+
10
+ Sidekiq.configure_client do |config|
11
+ config.redis = {url: redis_url, ssl: true}
12
+ end
13
+ end
14
+ end
@@ -16,17 +16,49 @@ module "database" {
16
16
  recursive_delete = local.recursive_delete
17
17
  rds_plan_name = "TKTK-production-rds-plan"
18
18
  }
19
+ <% if has_active_job? %>
20
+ module "redis" {
21
+ source = "../shared/redis"
19
22
 
23
+ cf_user = var.cf_user
24
+ cf_password = var.cf_password
25
+ cf_org_name = local.cf_org_name
26
+ cf_space_name = local.cf_space_name
27
+ env = local.env
28
+ recursive_delete = local.recursive_delete
29
+ redis_plan_name = "TKTK-production-redis-plan"
30
+ }
31
+ <% end %>
20
32
  <% if has_active_storage? %>
21
33
  module "s3" {
22
34
  source = "../shared/s3"
23
35
 
24
- cf_user = var.cf_user
25
- cf_password = var.cf_password
26
- cf_org_name = local.cf_org_name
27
- cf_space_name = local.cf_space_name
28
- s3_service_name = "<%= app_name %>-s3-${local.env}"
36
+ cf_user = var.cf_user
37
+ cf_password = var.cf_password
38
+ cf_org_name = local.cf_org_name
39
+ cf_space_name = local.cf_space_name
40
+ recursive_delete = local.recursive_delete
41
+ s3_service_name = "<%= app_name %>-s3-${local.env}"<% if cloud_gov_organization == "sandbox-gsa" %>
42
+ s3_plan_name = "basic-sandbox"<% end %>
29
43
  }
44
+
45
+ ###########################################################################
46
+ # The following lines need to be commented out for the initial `terraform apply`
47
+ # It can be re-enabled after:
48
+ # 1) the app has first been deployed
49
+ # 2) Your organization has sufficient memory. Each clamav app requires 3GB
50
+ ###########################################################################
51
+ # module "clamav" {
52
+ # source = "../shared/clamav"
53
+ #
54
+ # cf_user = var.cf_user
55
+ # cf_password = var.cf_password
56
+ # cf_org_name = local.cf_org_name
57
+ # cf_space_name = local.cf_space_name
58
+ # env = local.env
59
+ # clamav_image = "ajilaag/clamav-rest:20211229"
60
+ # max_file_size = "30M"
61
+ # }
30
62
  <% end %>
31
63
 
32
64
  ###########################################################################
@@ -0,0 +1,50 @@
1
+ ###
2
+ # Target space/org
3
+ ###
4
+
5
+ data "cloudfoundry_space" "space" {
6
+ org_name = var.cf_org_name
7
+ name = var.cf_space_name
8
+ }
9
+
10
+ data "cloudfoundry_domain" "internal" {
11
+ name = "apps.internal"
12
+ }
13
+
14
+ data "cloudfoundry_app" "app" {
15
+ name_or_id = "<%= app_name %>-${var.env}"
16
+ space = data.cloudfoundry_space.space.id
17
+ }
18
+
19
+ ###
20
+ # ClamAV API app
21
+ ###
22
+
23
+ resource "cloudfoundry_route" "clamav_route" {
24
+ space = data.cloudfoundry_space.space.id
25
+ domain = data.cloudfoundry_domain.internal.id
26
+ hostname = "<%= app_name %>-clamapi-${var.env}"
27
+ }
28
+
29
+ resource "cloudfoundry_app" "clamav_api" {
30
+ name = "<%= app_name %>-clamav-api-${var.env}"
31
+ space = data.cloudfoundry_space.space.id
32
+ memory = var.clamav_memory
33
+ disk_quota = 2048
34
+ timeout = 600
35
+ docker_image = var.clamav_image
36
+ routes {
37
+ route = cloudfoundry_route.clamav_route.id
38
+ }
39
+ environment = {
40
+ MAX_FILE_SIZE = var.max_file_size
41
+ }
42
+ }
43
+
44
+ resource "cloudfoundry_network_policy" "clamav_routing" {
45
+ policy {
46
+ source_app = data.cloudfoundry_app.app.id
47
+ destination_app = cloudfoundry_app.clamav_api.id
48
+ port = "9443"
49
+ }
50
+ }
@@ -0,0 +1,16 @@
1
+ terraform {
2
+ required_version = "~> 1.0"
3
+ required_providers {
4
+ cloudfoundry = {
5
+ source = "cloudfoundry-community/cloudfoundry"
6
+ version = "0.15.0"
7
+ }
8
+ }
9
+ }
10
+
11
+ provider "cloudfoundry" {
12
+ api_url = var.cf_api_url
13
+ user = var.cf_user
14
+ password = var.cf_password
15
+ app_logs_max = 30
16
+ }
@@ -0,0 +1,47 @@
1
+ variable "cf_api_url" {
2
+ type = string
3
+ description = "cloud.gov api url"
4
+ default = "https://api.fr.cloud.gov"
5
+ }
6
+
7
+ variable "cf_user" {
8
+ type = string
9
+ description = "cloud.gov deployer account user"
10
+ }
11
+
12
+ variable "cf_password" {
13
+ type = string
14
+ description = "secret; cloud.gov deployer account password"
15
+ sensitive = true
16
+ }
17
+
18
+ variable "cf_org_name" {
19
+ type = string
20
+ description = "cloud.gov organization name"
21
+ }
22
+
23
+ variable "cf_space_name" {
24
+ type = string
25
+ description = "cloud.gov space name (staging or prod)"
26
+ }
27
+
28
+ variable "env" {
29
+ type = string
30
+ description = "deployment environment (staging, production)"
31
+ }
32
+
33
+ variable "clamav_image" {
34
+ type = string
35
+ description = "Docker image to deploy the clamav api app"
36
+ }
37
+
38
+ variable "clamav_memory" {
39
+ type = number
40
+ description = "Memory in MB to allocate to clamav app"
41
+ default = 3072
42
+ }
43
+
44
+ variable "max_file_size" {
45
+ type = string
46
+ description = "Maximum file size the API will accept for scanning"
47
+ }
@@ -0,0 +1,23 @@
1
+ ###
2
+ # Target space/org
3
+ ###
4
+
5
+ data "cloudfoundry_space" "space" {
6
+ org_name = var.cf_org_name
7
+ name = var.cf_space_name
8
+ }
9
+
10
+ ###
11
+ # RDS instance
12
+ ###
13
+
14
+ data "cloudfoundry_service" "redis" {
15
+ name = "aws-elasticache-redis"
16
+ }
17
+
18
+ resource "cloudfoundry_service_instance" "redis" {
19
+ name = "<%= app_name %>-redis-${var.env}"
20
+ space = data.cloudfoundry_space.space.id
21
+ service_plan = data.cloudfoundry_service.redis.service_plans[var.redis_plan_name]
22
+ recursive_delete = var.recursive_delete
23
+ }
@@ -0,0 +1,16 @@
1
+ terraform {
2
+ required_version = "~> 1.0"
3
+ required_providers {
4
+ cloudfoundry = {
5
+ source = "cloudfoundry-community/cloudfoundry"
6
+ version = "0.15.0"
7
+ }
8
+ }
9
+ }
10
+
11
+ provider "cloudfoundry" {
12
+ api_url = var.cf_api_url
13
+ user = var.cf_user
14
+ password = var.cf_password
15
+ app_logs_max = 30
16
+ }
@@ -0,0 +1,42 @@
1
+ variable "cf_api_url" {
2
+ type = string
3
+ description = "cloud.gov api url"
4
+ default = "https://api.fr.cloud.gov"
5
+ }
6
+
7
+ variable "cf_user" {
8
+ type = string
9
+ description = "cloud.gov deployer account user"
10
+ }
11
+
12
+ variable "cf_password" {
13
+ type = string
14
+ description = "secret; cloud.gov deployer account password"
15
+ sensitive = true
16
+ }
17
+
18
+ variable "cf_org_name" {
19
+ type = string
20
+ description = "cloud.gov organization name"
21
+ }
22
+
23
+ variable "cf_space_name" {
24
+ type = string
25
+ description = "cloud.gov space name (staging or prod)"
26
+ }
27
+
28
+ variable "env" {
29
+ type = string
30
+ description = "deployment environment (staging, production)"
31
+ }
32
+
33
+ variable "recursive_delete" {
34
+ type = bool
35
+ description = "when true, deletes service bindings attached to the resource (not recommended for production)"
36
+ default = false
37
+ }
38
+
39
+ variable "redis_plan_name" {
40
+ type = string
41
+ description = "name of the service plan name to create"
42
+ }
@@ -16,15 +16,47 @@ module "database" {
16
16
  recursive_delete = local.recursive_delete
17
17
  rds_plan_name = "micro-psql"
18
18
  }
19
+ <% if has_active_job? %>
20
+ module "redis" {
21
+ source = "../shared/redis"
19
22
 
23
+ cf_user = var.cf_user
24
+ cf_password = var.cf_password
25
+ cf_org_name = local.cf_org_name
26
+ cf_space_name = local.cf_space_name
27
+ env = local.env
28
+ recursive_delete = local.recursive_delete
29
+ redis_plan_name = "redis-dev"
30
+ }
31
+ <% end %>
20
32
  <% if has_active_storage? %>
21
33
  module "s3" {
22
34
  source = "../shared/s3"
23
35
 
24
- cf_user = var.cf_user
25
- cf_password = var.cf_password
26
- cf_org_name = local.cf_org_name
27
- cf_space_name = local.cf_space_name
28
- s3_service_name = "<%= app_name %>-s3-${local.env}"
36
+ cf_user = var.cf_user
37
+ cf_password = var.cf_password
38
+ cf_org_name = local.cf_org_name
39
+ cf_space_name = local.cf_space_name
40
+ recursive_delete = local.recursive_delete
41
+ s3_service_name = "<%= app_name %>-s3-${local.env}"<% if cloud_gov_organization == "sandbox-gsa" %>
42
+ s3_plan_name = "basic-sandbox"<% end %>
29
43
  }
44
+
45
+ ###########################################################################
46
+ # The following lines need to be commented out for the initial `terraform apply`
47
+ # It can be re-enabled after:
48
+ # 1) the app has first been deployed
49
+ # 2) Your organization has sufficient memory. Each clamav app requires 3GB
50
+ ###########################################################################
51
+ # module "clamav" {
52
+ # source = "../shared/clamav"
53
+ #
54
+ # cf_user = var.cf_user
55
+ # cf_password = var.cf_password
56
+ # cf_org_name = local.cf_org_name
57
+ # cf_space_name = local.cf_space_name
58
+ # env = local.env
59
+ # clamav_image = "ajilaag/clamav-rest:20211229"
60
+ # max_file_size = "30M"
61
+ # }
30
62
  <% end %>
@@ -79,17 +79,6 @@ module RailsTemplate18f
79
79
  EOM
80
80
  end
81
81
  end
82
-
83
- private
84
-
85
- def terraform_dir_exists?
86
- # prevents cloud_gov_* helpers from trying to read non-existant .tf files
87
- false
88
- end
89
-
90
- def has_active_storage?
91
- defined?(::ActiveStorage)
92
- end
93
82
  end
94
83
  end
95
84
  end
@@ -0,0 +1,19 @@
1
+ require "rails/app_updater"
2
+
3
+ module AppUpdaterOptions
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def generator_options
8
+ options = super
9
+ # These options all end up hardcoded to true in the default `rails app:update`
10
+ options[:skip_active_job] = !defined?(ActiveJob::Railtie)
11
+ options[:skip_action_mailbox] = !defined?(ActionMailbox::Engine)
12
+ options[:skip_action_text] = !defined?(ActionText::Engine)
13
+ options[:skip_test] = !defined?(Rails::TestUnitRailtie)
14
+ options
15
+ end
16
+ end
17
+ end
18
+
19
+ Rails::AppUpdater.prepend(AppUpdaterOptions)
@@ -6,10 +6,6 @@ module RailsTemplate18f
6
6
  extend ActiveSupport::Concern
7
7
  include ::Rails::Generators::AppName
8
8
 
9
- included do
10
- self.source_path = RailsTemplate18f::Generators.const_source_location(name).first
11
- end
12
-
13
9
  class_methods do
14
10
  attr_accessor :source_path
15
11
 
@@ -18,19 +14,40 @@ module RailsTemplate18f
18
14
  end
19
15
  end
20
16
 
17
+ included do
18
+ self.source_path = RailsTemplate18f::Generators.const_source_location(name).first
19
+ end
20
+
21
21
  private
22
22
 
23
23
  def file_content(filename)
24
- File.read(File.expand_path(filename, destination_root))
24
+ file_path = File.expand_path(filename, destination_root)
25
+ if File.exist? file_path
26
+ File.read(file_path)
27
+ else
28
+ ""
29
+ end
25
30
  end
26
31
 
27
32
  def ruby_version
28
33
  RUBY_VERSION
29
34
  end
30
35
 
36
+ def terraform_dir_exists?
37
+ Dir.exist? File.expand_path("terraform", destination_root)
38
+ end
39
+
31
40
  def skip_git?
32
41
  !Dir.exist?(File.expand_path(".git", destination_root))
33
42
  end
43
+
44
+ def has_active_job?
45
+ defined?(::ActiveJob)
46
+ end
47
+
48
+ def has_active_storage?
49
+ defined?(::ActiveStorage)
50
+ end
34
51
  end
35
52
  end
36
53
  end
@@ -48,10 +48,6 @@ module RailsTemplate18f
48
48
  end
49
49
  "prod"
50
50
  end
51
-
52
- def terraform_dir_exists?
53
- Dir.exist? File.expand_path("terraform", destination_root)
54
- end
55
51
  end
56
52
  end
57
53
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsTemplate18f
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/template.rb CHANGED
@@ -10,6 +10,10 @@ def skip_git?
10
10
  !!options[:skip_git]
11
11
  end
12
12
 
13
+ def skip_active_job?
14
+ !!options[:skip_active_job]
15
+ end
16
+
13
17
  def webpack?
14
18
  adjusted_javascript_option == "webpack"
15
19
  end
@@ -339,6 +343,18 @@ if terraform
339
343
  register_announcement("Terraform", "Run the bootstrap script and update the appropriate CI/CD environment variables defined in the Deployment section of the README")
340
344
  end
341
345
 
346
+ if !skip_active_job?
347
+ after_bundle do
348
+ generate "rails_template18f:sidekiq"
349
+ end
350
+ end
351
+
352
+ if !skip_active_storage?
353
+ after_bundle do
354
+ generate "rails_template18f:active_storage"
355
+ end
356
+ end
357
+
342
358
  if @github_actions
343
359
  after_bundle do
344
360
  generator_arguments = [
@@ -1,3 +1,3 @@
1
1
  env: staging
2
2
  web_instances: 1
3
- web_memory: 512M
3
+ web_memory: 256M
@@ -1,7 +1,6 @@
1
1
  require_relative "./production"
2
2
 
3
3
  Rails.application.configure do
4
- config.assets.compile = true
5
4
  config.public_file_server.enabled = true
6
5
 
7
6
  logger = ActiveSupport::Logger.new($stdout)
@@ -30,15 +30,11 @@ Boundary(aws, "AWS GovCloud") {
30
30
  System_Boundary(inventory, "Application") {
31
31
  Container(app, "<&layers> <%= app_name.titleize %>", "Ruby <%= @ruby_version %>, Rails <%= Rails.version %>", "TKTK Application Description")
32
32
  ContainerDb(app_db, "Application DB", "AWS RDS (PostgreSQL)", "Primary data storage")
33
- <% if !skip_active_storage? %>
34
- ContainerDb(app_s3, "File Storage", "AWS S3", "User-uploaded file storage")
35
- <% end %>
36
33
  }
37
34
  }
38
35
  }
39
36
  }
40
37
 
41
-
42
38
  Boundary(gsa_saas, "GSA-authorized SaaS") {
43
39
  }
44
40
 
@@ -49,9 +45,6 @@ Rel(browser, aws_alb, "request info, submit requests", "https GET/POST (443)")
49
45
  Rel(aws_alb, cloudgov_router, "proxies requests", "https GET/POST (443)")
50
46
  Rel(cloudgov_router, app, "proxies requests", "https GET/POST (443)")
51
47
  Rel(app, app_db, "reads/writes primary data", "psql (5432)")
52
- <% if !skip_active_storage? %>
53
- Rel(app, app_s3, "reads/writes file data", "https (443)")
54
- <% end %>
55
48
  @enduml
56
49
  ```
57
50
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_template_18f
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Ahearn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-17 00:00:00.000000000 Z
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -114,11 +114,20 @@ files:
114
114
  - bin/console
115
115
  - bin/setup
116
116
  - exe/rails_template_18f
117
+ - lib/generators/rails_template18f/active_storage/active_storage_generator.rb
118
+ - lib/generators/rails_template18f/active_storage/templates/app/jobs/file_scan_job.rb
119
+ - lib/generators/rails_template18f/active_storage/templates/app/models/file_upload.rb
120
+ - lib/generators/rails_template18f/active_storage/templates/doc/adr/clamav.md.tt
121
+ - lib/generators/rails_template18f/active_storage/templates/spec/jobs/file_scan_job_spec.rb
122
+ - lib/generators/rails_template18f/active_storage/templates/spec/models/file_upload_spec.rb
117
123
  - lib/generators/rails_template18f/circleci/circleci_generator.rb
118
124
  - lib/generators/rails_template18f/circleci/templates/Dockerfile.tt
119
125
  - lib/generators/rails_template18f/circleci/templates/bin/ci-server-start
120
126
  - lib/generators/rails_template18f/circleci/templates/circleci/config.yml.tt
121
127
  - lib/generators/rails_template18f/circleci/templates/docker-compose.ci.yml
128
+ - lib/generators/rails_template18f/cloud_gov_config/cloud_gov_config_generator.rb
129
+ - lib/generators/rails_template18f/cloud_gov_config/templates/app/models/cloud_gov_config.rb
130
+ - lib/generators/rails_template18f/cloud_gov_config/templates/spec/models/cloud_gov_config_spec.rb
122
131
  - lib/generators/rails_template18f/dap/dap_generator.rb
123
132
  - lib/generators/rails_template18f/github_actions/github_actions_generator.rb
124
133
  - lib/generators/rails_template18f/github_actions/templates/github/actions/run-server/action.yml
@@ -139,8 +148,12 @@ files:
139
148
  - lib/generators/rails_template18f/i18n/templates/config/locales/es.yml
140
149
  - lib/generators/rails_template18f/i18n/templates/config/locales/fr.yml
141
150
  - lib/generators/rails_template18f/i18n/templates/config/locales/zh.yml
151
+ - lib/generators/rails_template18f/i18n_js/i18n_js_generator.rb
152
+ - lib/generators/rails_template18f/i18n_js/templates/lib/tasks/i18n.rake
142
153
  - lib/generators/rails_template18f/newrelic/newrelic_generator.rb
143
154
  - lib/generators/rails_template18f/newrelic/templates/config/newrelic.yml.tt
155
+ - lib/generators/rails_template18f/sidekiq/sidekiq_generator.rb
156
+ - lib/generators/rails_template18f/sidekiq/templates/config/initializers/redis.rb
144
157
  - lib/generators/rails_template18f/terraform/templates/terraform/README.md.tt
145
158
  - lib/generators/rails_template18f/terraform/templates/terraform/bootstrap/import.sh
146
159
  - lib/generators/rails_template18f/terraform/templates/terraform/bootstrap/main.tf.tt
@@ -153,12 +166,18 @@ files:
153
166
  - lib/generators/rails_template18f/terraform/templates/terraform/production/main.tf.tt
154
167
  - lib/generators/rails_template18f/terraform/templates/terraform/production/providers.tf.tt
155
168
  - lib/generators/rails_template18f/terraform/templates/terraform/production/variables.tf
169
+ - lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/main.tf.tt
170
+ - lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/providers.tf
171
+ - lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/variables.tf
156
172
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/database/main.tf.tt
157
173
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/database/providers.tf
158
174
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/database/variables.tf
159
175
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/domain/main.tf.tt
160
176
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/domain/providers.tf
161
177
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/domain/variables.tf
178
+ - lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/main.tf.tt
179
+ - lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/providers.tf
180
+ - lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/variables.tf
162
181
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/s3/main.tf
163
182
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/s3/providers.tf
164
183
  - lib/generators/rails_template18f/terraform/templates/terraform/shared/s3/variables.tf
@@ -166,6 +185,7 @@ files:
166
185
  - lib/generators/rails_template18f/terraform/templates/terraform/staging/providers.tf.tt
167
186
  - lib/generators/rails_template18f/terraform/templates/terraform/staging/variables.tf
168
187
  - lib/generators/rails_template18f/terraform/terraform_generator.rb
188
+ - lib/rails_template18f/app_updater.rb
169
189
  - lib/rails_template18f/generators.rb
170
190
  - lib/rails_template18f/generators/base.rb
171
191
  - lib/rails_template18f/generators/cloud_gov_options.rb