Skip to content

Commit 1b92daf

Browse files
committed
feat: Added concurrency to Hyperproof sync.
1 parent 2347a9c commit 1b92daf

File tree

6 files changed

+73
-9
lines changed

6 files changed

+73
-9
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Code for America Security Controls
22

3-
This repository contains the configuration for our AWS security controls. These
4-
configurations ensure that our AWS accounts are secure and meet our compliance
3+
This repository contains the configuration and automation for our security
4+
controls. These ensure that our organization is secure and meets our compliance
55
requirements.

hyperproof/Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ PATH
66
aws-sdk-configservice (~> 1.128)
77
aws-sdk-rds (~> 1.276)
88
aws-sdk-resourceexplorer2 (~> 1.36)
9+
concurrent-ruby (~> 1.3)
910
configsl (~> 1.0)
1011
csv (~> 3.3)
1112
faraday (~> 2.13)

hyperproof/bin/hyperproof

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,37 @@ module CfaSecurityControls
1111
module Hyperproof
1212
# CLI tool for the CFA Security Controls Hyperproof integration.
1313
class CLI < Thor
14+
def self.exit_on_failure?
15+
true
16+
end
17+
1418
desc 'collect', 'Collect and sync all proofs to Hyperproof'
1519
def collect
1620
say 'Collecting proofs...', :green
17-
CfaSecurityControls::Hyperproof.run
18-
say 'All proofs collected and synced to Hyperproof.', :green
21+
results = CfaSecurityControls::Hyperproof.run
22+
if results.values.any?(false)
23+
say_error "#{results.values.tally[false]} proofs failed to collect.", :red
24+
exit 1
25+
end
26+
27+
say "Collected and synced #{bold(results.length)} proofs to Hypersync.", :green
1928
end
2029

2130
desc 'version', 'Show the current version'
2231
def version
2332
say CfaSecurityControls::Hyperproof::VERSION
2433
end
34+
35+
# Helper methods that will not be exposed as commands.
36+
no_commands do
37+
# Bold a string for terminal output.
38+
#
39+
# @param string [String] The string to bold.
40+
# @return [String] The bolded string.
41+
def bold(string)
42+
"\e[1m#{string}\e[22m"
43+
end
44+
end
2545
end
2646
end
2747
end

hyperproof/cfa-security-controls-hyperproof.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
2727
s.add_dependency 'aws-sdk-configservice', '~> 1.128'
2828
s.add_dependency 'aws-sdk-rds', '~> 1.276'
2929
s.add_dependency 'aws-sdk-resourceexplorer2', '~> 1.36'
30+
s.add_dependency 'concurrent-ruby', '~> 1.3'
3031
s.add_dependency 'configsl', '~> 1.0'
3132
s.add_dependency 'csv', '~> 3.3'
3233
s.add_dependency 'faraday', '~> 2.13'

hyperproof/lib/cfa_security_controls/hyperproof.rb

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'concurrent-ruby'
4+
35
require_relative 'hyperproof/config'
46
require_relative 'hyperproof/entities/label'
57
require_relative 'hyperproof/entities/proof'
@@ -30,16 +32,43 @@ def self.config(config = nil)
3032
def self.run
3133
Dir.mktmpdir do |dir|
3234
config.logger.info("Writing proofs to #{dir}")
35+
3336
writer = Writer.new(dir)
34-
Proofs.proofs.map do |klass|
35-
proof = klass.new
36-
filename = collect_proof(proof, writer)
37-
Entities::Proof.new(File.basename(filename), label: proof_label(proof))
38-
.create(filename)
37+
futures = Proofs.proofs.map do |klass|
38+
Concurrent::Promises.future(executor: config.thread_pool) do
39+
create_proof(klass.new, writer)
40+
end.run
3941
end
42+
43+
# Wait for all futures to complete and collect results.
44+
Concurrent::Promises.zip(*futures).value!.to_h
4045
end
4146
end
4247

48+
# Create the proof in Hyperproof.
49+
#
50+
# @param proof [Proofs::Proof] The proof to create.
51+
# @param writer [Writer] The writer to use for formatting the proof.
52+
# @return [Array] A promise response containing the proof name and its ID.
53+
private_class_method def self.create_proof(proof, writer)
54+
filename = collect_proof(proof, writer)
55+
entity = Entities::Proof.new(File.basename(filename), label: proof_label(proof))
56+
entity.create(filename)
57+
[proof.name, entity.id]
58+
rescue StandardError => e
59+
handle_exception(proof, e)
60+
end
61+
62+
# Handle exceptions that occur during proof collection.
63+
#
64+
# @param proof [Proofs::Proof] The proof that was being processed.
65+
# @param error [StandardError] The error that occurred.
66+
# @return [Array] Valid promise repose representing a failure.
67+
private_class_method def self.handle_exception(proof, error)
68+
config.logger.error("An error occurred: #{error.message}")
69+
[proof.name, false]
70+
end
71+
4372
# Collect evidence for a specific proof.
4473
#
4574
# @param proof [Proofs::Proof] The proof to collect evidence for.

hyperproof/lib/cfa_security_controls/hyperproof/config.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22

3+
require 'concurrent-ruby'
34
require 'configsl'
45

56
module CfaSecurityControls
@@ -13,6 +14,9 @@ class Config < ConfigSL::Config
1314
option :aptible_password, type: String
1415
option :hyperproof_client_id, type: String, required: true
1516
option :hyperproof_client_secret, type: String, required: true
17+
option :threads_min, type: Integer, default: 1
18+
option :threads_max, type: Integer, default: Concurrent.processor_count
19+
option :theads_queue_size, type: Integer, default: 10
1620

1721
def initialize(params = {})
1822
super
@@ -22,6 +26,15 @@ def initialize(params = {})
2226
def logger
2327
@logger ||= Logger.new($stdout, level: log_level)
2428
end
29+
30+
def thread_pool
31+
@thread_pool ||= Concurrent::ThreadPoolExecutor.new(
32+
min_threads: threads_min,
33+
max_threads: threads_max,
34+
max_queue: theads_queue_size,
35+
fallback_policy: :caller_runs
36+
)
37+
end
2538
end
2639
end
2740
end

0 commit comments

Comments
 (0)