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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/exe/rails_template_18f +31 -0
- data/lib/generators/rails_template18f/active_storage/active_storage_generator.rb +135 -0
- data/lib/generators/rails_template18f/active_storage/templates/app/jobs/file_scan_job.rb +33 -0
- data/lib/generators/rails_template18f/active_storage/templates/app/models/file_upload.rb +25 -0
- data/lib/generators/rails_template18f/active_storage/templates/doc/adr/clamav.md.tt +30 -0
- data/lib/generators/rails_template18f/active_storage/templates/spec/jobs/file_scan_job_spec.rb +35 -0
- data/lib/generators/rails_template18f/active_storage/templates/spec/models/file_upload_spec.rb +38 -0
- data/lib/generators/rails_template18f/cloud_gov_config/cloud_gov_config_generator.rb +28 -0
- data/lib/generators/rails_template18f/cloud_gov_config/templates/app/models/cloud_gov_config.rb +15 -0
- data/lib/generators/rails_template18f/cloud_gov_config/templates/spec/models/cloud_gov_config_spec.rb +44 -0
- data/lib/generators/rails_template18f/i18n_js/i18n_js_generator.rb +60 -0
- data/lib/generators/rails_template18f/i18n_js/templates/lib/tasks/i18n.rake +9 -0
- data/lib/generators/rails_template18f/sidekiq/sidekiq_generator.rb +70 -0
- data/lib/generators/rails_template18f/sidekiq/templates/config/initializers/redis.rb +14 -0
- data/lib/generators/rails_template18f/terraform/templates/terraform/production/main.tf.tt +37 -5
- data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/main.tf.tt +50 -0
- data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/providers.tf +16 -0
- data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/variables.tf +47 -0
- data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/main.tf.tt +23 -0
- data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/providers.tf +16 -0
- data/lib/generators/rails_template18f/terraform/templates/terraform/shared/redis/variables.tf +42 -0
- data/lib/generators/rails_template18f/terraform/templates/terraform/staging/main.tf.tt +37 -5
- data/lib/generators/rails_template18f/terraform/terraform_generator.rb +0 -11
- data/lib/rails_template18f/app_updater.rb +19 -0
- data/lib/rails_template18f/generators/base.rb +22 -5
- data/lib/rails_template18f/generators/cloud_gov_options.rb +0 -4
- data/lib/rails_template18f/version.rb +1 -1
- data/template.rb +16 -0
- data/templates/config/deployment/staging.yml +1 -1
- data/templates/config/environments/ci.rb +0 -1
- data/templates/doc/compliance/apps/application.boundary.md.tt +0 -7
- metadata +22 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c87267d368d3e90a500c0fc1bf23312a3f6edf437ba991a9a4c7b994fff4282d
|
|
4
|
+
data.tar.gz: ec24cf97ec587cf730bb2f63ff4cf0fd5b13ae64364b175041b0b0afd42a4321
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/exe/rails_template_18f
CHANGED
|
@@ -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.
|
data/lib/generators/rails_template18f/active_storage/templates/spec/jobs/file_scan_job_spec.rb
ADDED
|
@@ -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
|
data/lib/generators/rails_template18f/active_storage/templates/spec/models/file_upload_spec.rb
ADDED
|
@@ -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
|
data/lib/generators/rails_template18f/cloud_gov_config/templates/app/models/cloud_gov_config.rb
ADDED
|
@@ -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
|
|
25
|
-
cf_password
|
|
26
|
-
cf_org_name
|
|
27
|
-
cf_space_name
|
|
28
|
-
|
|
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
|
+
}
|
data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/providers.tf
ADDED
|
@@ -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
|
+
}
|
data/lib/generators/rails_template18f/terraform/templates/terraform/shared/clamav/variables.tf
ADDED
|
@@ -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
|
|
25
|
-
cf_password
|
|
26
|
-
cf_org_name
|
|
27
|
-
cf_space_name
|
|
28
|
-
|
|
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.
|
|
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
|
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 = [
|
|
@@ -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.
|
|
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-
|
|
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
|