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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +4 -4
- data/exe/rails_template_18f +31 -0
- data/lib/generators/rails_template18f/active_storage/active_storage_generator.rb +141 -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/circleci/circleci_generator.rb +4 -1
- data/lib/generators/rails_template18f/cloud_gov_config/cloud_gov_config_generator.rb +29 -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/i18n_generator.rb +8 -9
- data/lib/generators/rails_template18f/i18n_js/i18n_js_generator.rb +59 -0
- data/lib/generators/rails_template18f/i18n_js/templates/lib/tasks/i18n.rake +9 -0
- data/lib/generators/rails_template18f/newrelic/newrelic_generator.rb +2 -0
- data/lib/generators/rails_template18f/sidekiq/sidekiq_generator.rb +81 -0
- data/lib/generators/rails_template18f/sidekiq/templates/config/initializers/redis.rb +14 -0
- data/lib/generators/rails_template18f/terraform/templates/terraform/README.md.tt +1 -1
- 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 +10 -12
- data/lib/rails_template18f/app_updater.rb +19 -0
- data/lib/rails_template18f/generators/base.rb +37 -5
- data/lib/rails_template18f/generators/cloud_gov_options.rb +0 -4
- data/lib/rails_template18f/version.rb +1 -1
- data/rails-template-18f.gemspec +1 -0
- data/template.rb +29 -1
- data/templates/Brewfile +14 -0
- data/templates/README.md.tt +9 -10
- data/templates/app/views/application/_header.html.erb +0 -1
- data/templates/app/views/application/_usa_banner.html.erb +2 -0
- data/templates/bin/with-server +1 -2
- 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
- data/templates/env +1 -1
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9452d60916a767ee1b00bb0a76e6582d4d1ed760b3bf0c4d68a07c8225ddfd4
|
4
|
+
data.tar.gz: 481c2ee7693849f2c21d382d78edd08a59370b8c2b440bef8502d22a039a2243
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rails_template_18f (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.
|
50
|
+
nokogiri (1.13.3-x86_64-darwin)
|
50
51
|
racc (~> 1.4)
|
51
|
-
nokogiri (1.13.
|
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)
|
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,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.
|
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
|
@@ -14,7 +14,10 @@ module RailsTemplate18f
|
|
14
14
|
DESC
|
15
15
|
|
16
16
|
def install_needed_gems
|
17
|
-
|
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
|
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
|
@@ -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
|
20
|
-
return if
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|