Refactoring to make more testable and composable
This commit is contained in:
@@ -21,7 +21,12 @@ end
|
||||
|
||||
require "pipeline/analyzer_repo"
|
||||
require "pipeline/analyzer_build"
|
||||
require "pipeline/validation/check_invokable"
|
||||
require "pipeline/validation/check_environment_invariants"
|
||||
require "pipeline/validation/check_fixtures"
|
||||
require "pipeline/validate_build"
|
||||
require "pipeline/util/container_driver"
|
||||
require "pipeline/util/runc_configurator"
|
||||
require "pipeline/util/img_wrapper"
|
||||
require "pipeline/util/runc_wrapper"
|
||||
require "pipeline/build_image"
|
||||
|
||||
49
lib/pipeline/util/container_driver.rb
Normal file
49
lib/pipeline/util/container_driver.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
module Pipeline::Util
|
||||
class ContainerDriver
|
||||
attr_accessor :runc, :img, :configurator, :workdir
|
||||
|
||||
def initialize(runc, img, configurator, workdir)
|
||||
@runc = runc
|
||||
@img = img
|
||||
@configurator = configurator
|
||||
@workdir = workdir
|
||||
end
|
||||
|
||||
def prepare_workdir
|
||||
FileUtils.mkdir_p workdir
|
||||
FileUtils.mkdir_p "#{workdir}/iteration"
|
||||
FileUtils.mkdir_p "#{workdir}/tmp"
|
||||
end
|
||||
|
||||
def unpack_image(build_tag)
|
||||
Dir.chdir(workdir) do
|
||||
img.unpack(build_tag)
|
||||
end
|
||||
configurator.setup_for_terminal_access
|
||||
File.write("#{workdir}/terminal_config.json", configurator.build.to_json)
|
||||
end
|
||||
|
||||
def run_script(script_contents)
|
||||
File.write("#{workdir}/iteration/test_script.sh", script_contents)
|
||||
|
||||
configurator.setup_script("/mnt/exercism-iteration/test_script.sh")
|
||||
File.write("#{workdir}/test_config.json", configurator.build.to_json)
|
||||
|
||||
FileUtils.symlink("#{workdir}/test_config.json", "#{workdir}/config.json", force: true)
|
||||
run_analyzer
|
||||
end
|
||||
|
||||
def run_analyzer
|
||||
runc.run(workdir)
|
||||
end
|
||||
|
||||
def run_analyzer_for(exercise_slug)
|
||||
configurator.invoke_analyzer_for(exercise_slug)
|
||||
File.write("#{workdir}/analyzer_config.json", configurator.build.to_json)
|
||||
FileUtils.symlink("#{workdir}/analyzer_config.json", "#{workdir}/config.json", force: true)
|
||||
run_analyzer
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,10 +1,11 @@
|
||||
module Pipeline::Util
|
||||
class ImgWrapper
|
||||
attr_accessor :binary_path, :state_location
|
||||
attr_accessor :binary_path, :state_location, :suppress_output
|
||||
|
||||
def initialize
|
||||
@binary_path = File.expand_path "./opt/img"
|
||||
@state_location = "/tmp/state-img"
|
||||
@suppress_output = false
|
||||
end
|
||||
|
||||
def build(local_tag)
|
||||
@@ -12,21 +13,29 @@ module Pipeline::Util
|
||||
exec_cmd cmd
|
||||
end
|
||||
|
||||
def unpack(local_tag)
|
||||
exec_cmd "#{binary_path} unpack -state #{state_location} #{local_tag}"
|
||||
end
|
||||
|
||||
def push_cmd
|
||||
"#{binary_path} push -state /tmp/state-img"
|
||||
"#{binary_path} push -state #{state_location}"
|
||||
end
|
||||
|
||||
def push_cmd
|
||||
"#{binary_path} push -state #{state_location}"
|
||||
end
|
||||
|
||||
def build_cmd
|
||||
"#{binary_path} build -state /tmp/state-img"
|
||||
"#{binary_path} build -state #{state_location}"
|
||||
end
|
||||
|
||||
def tag_cmd
|
||||
"#{binary_path} tag -state /tmp/state-img"
|
||||
"#{binary_path} tag -state #{state_location}"
|
||||
end
|
||||
|
||||
def exec_cmd(cmd)
|
||||
puts "> #{cmd}"
|
||||
puts "------------------------------------------------------------"
|
||||
puts "> #{cmd}" unless suppress_output
|
||||
puts "------------------------------------------------------------" unless suppress_output
|
||||
success = system({}, cmd)
|
||||
raise "Failed #{cmd}" unless success
|
||||
end
|
||||
|
||||
29
lib/pipeline/util/runc_wrapper.rb
Normal file
29
lib/pipeline/util/runc_wrapper.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
module Pipeline::Util
|
||||
class RuncWrapper
|
||||
attr_accessor :binary_path, :suppress_output
|
||||
|
||||
def initialize
|
||||
@binary_path = File.expand_path "./opt/runc"
|
||||
@suppress_output = false
|
||||
end
|
||||
|
||||
def run(container_folder)
|
||||
Dir.chdir(container_folder) do
|
||||
exec_cmd run_cmd
|
||||
end
|
||||
end
|
||||
|
||||
def run_cmd
|
||||
"#{binary_path} --root root-state run analyzer-#{Time.now.to_i}"
|
||||
end
|
||||
|
||||
def exec_cmd(cmd)
|
||||
puts "> #{cmd}" unless suppress_output
|
||||
puts "------------------------------------------------------------" unless suppress_output
|
||||
success = system({}, cmd)
|
||||
raise "Failed #{cmd}" unless success
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,113 +1,30 @@
|
||||
class Pipeline::ValidateBuild
|
||||
include Mandate
|
||||
|
||||
attr_accessor :img, :runc, :configurator
|
||||
|
||||
initialize_with :track_slug, :build_tag
|
||||
|
||||
def call
|
||||
@img = File.expand_path "./opt/img"
|
||||
@runc = File.expand_path "./opt/runc"
|
||||
FileUtils.mkdir_p workdir
|
||||
unpack
|
||||
write_runc_config
|
||||
FileUtils.mkdir_p "#{workdir}/iteration"
|
||||
FileUtils.mkdir_p "#{workdir}/tmp"
|
||||
check_environment_is_invokable
|
||||
check_environment_invariants
|
||||
check_sample_solutions
|
||||
end
|
||||
|
||||
def unpack
|
||||
Dir.chdir(workdir) do
|
||||
exec_cmd "#{img} unpack -state /tmp/state-img #{build_tag}"
|
||||
end
|
||||
container_driver.prepare_workdir
|
||||
container_driver.unpack_image(build_tag)
|
||||
end
|
||||
|
||||
def check_environment_is_invokable
|
||||
run_script <<-EOS
|
||||
echo "OK" >/mnt/exercism-iteration/result.txt
|
||||
EOS
|
||||
|
||||
text = File.read("#{workdir}/iteration/result.txt")
|
||||
|
||||
raise "Santity check failed" if (text.strip != "OK")
|
||||
Pipeline::Validation::CheckInvokable.(container_driver)
|
||||
end
|
||||
|
||||
def check_environment_invariants
|
||||
run_script <<-EOS
|
||||
if ping -q -c 1 -W 1 8.8.8.8 >/mnt/exercism-iteration/ping_check.txt; then
|
||||
echo "IPv4 is up" >/mnt/exercism-iteration/ip_connectivity.txt
|
||||
else
|
||||
echo "IPv4 is down" >/mnt/exercism-iteration/ip_connectivity.txt
|
||||
fi
|
||||
EOS
|
||||
|
||||
ip_connectivity = File.read("#{workdir}/iteration/ip_connectivity.txt")
|
||||
raise "IP down check failed" if (ip_connectivity.strip != "IPv4 is down")
|
||||
|
||||
run_script <<-EOS
|
||||
nc -w 3 -v 8.8.8.8 53 2>/mnt/exercism-iteration/net_check.txt
|
||||
EOS
|
||||
|
||||
net_check = File.read("#{workdir}/iteration/net_check.txt")
|
||||
raise "NET check failed" if (net_check.strip != "8.8.8.8 (8.8.8.8:53) open")
|
||||
|
||||
run_script <<-EOS
|
||||
touch /mnt/exercism-iteration/fs_check.txt
|
||||
touch /mnt/foobar && echo "/mnt writable!" >>/mnt/exercism-iteration/fs_check.txt
|
||||
touch /opt/foobar && echo "/opt writable!" >>/mnt/exercism-iteration/fs_check.txt
|
||||
touch /opt/analyzer/foobar && echo "/opt/analyzer/ writable!" >>/mnt/exercism-iteration/fs_check.txt
|
||||
exit 0
|
||||
EOS
|
||||
|
||||
fs_check = File.read("#{workdir}/iteration/fs_check.txt")
|
||||
|
||||
raise "Not readonly: #{fs_check}" unless fs_check.strip.empty?
|
||||
|
||||
run_script <<-EOS
|
||||
echo "/tmp/ writable!" >/tmp/foobar
|
||||
EOS
|
||||
run_script <<-EOS
|
||||
cat /tmp/foobar >/mnt/exercism-iteration/tmp_check.txt
|
||||
EOS
|
||||
|
||||
tmp_check = File.read("#{workdir}/iteration/tmp_check.txt").strip
|
||||
tmp_file = File.read("#{workdir}/tmp/foobar").strip
|
||||
|
||||
raise "/tmp not persisting across invocations" if ("/tmp/ writable!" != tmp_check || "/tmp/ writable!" != tmp_file)
|
||||
Pipeline::Validation::CheckEnvironmentInvariants.(container_driver)
|
||||
end
|
||||
|
||||
def check_sample_solutions
|
||||
exercise_folders = Dir.glob("fixtures/#{track_slug}/*")
|
||||
exercise_folders.each do |exercise_folder|
|
||||
exercise_slug = exercise_folder.split("/").last
|
||||
Dir.glob("#{exercise_folder}/*").each do |fixture_folder|
|
||||
validate_status(exercise_slug, fixture_folder)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_status(exercise, fixture_folder)
|
||||
FileUtils.rm_rf("#{workdir}/iteration/")
|
||||
FileUtils.cp_r "#{fixture_folder}/iteration", "#{workdir}/iteration"
|
||||
|
||||
configurator.invoke_analyzer_for(exercise)
|
||||
File.write("#{workdir}/analyzer_config.json", configurator.build.to_json)
|
||||
FileUtils.symlink("#{workdir}/analyzer_config.json", "#{workdir}/config.json", force: true)
|
||||
|
||||
Dir.chdir(workdir) do
|
||||
exec_cmd "#{runc} --root root-state run 123"
|
||||
end
|
||||
|
||||
analysis = JSON.parse(File.read("#{workdir}/iteration/analysis.json"))
|
||||
expected = JSON.parse(File.read("#{fixture_folder}/expected_analysis.json"))
|
||||
|
||||
raise "Incorrect expected_status" if expected["status"].nil?
|
||||
raise "Incorrect status when validating #{fixture_folder}" if expected["status"] != analysis["status"]
|
||||
expected["comments"] ||= []
|
||||
analysis["comments"] ||= []
|
||||
raise "Incorrect comments when validating #{fixture_folder}" if expected["comments"].sort != analysis["comments"].sort
|
||||
Pipeline::Validation::CheckFixtures.(container_driver, track_slug)
|
||||
end
|
||||
|
||||
memoize
|
||||
@@ -115,35 +32,13 @@ class Pipeline::ValidateBuild
|
||||
"/tmp/analyzer-scratch/#{SecureRandom.uuid}"
|
||||
end
|
||||
|
||||
def rootfs
|
||||
"#{workdir}/rootfs"
|
||||
end
|
||||
|
||||
def run_script(script_contents)
|
||||
File.write("#{workdir}/iteration/test_script.sh", script_contents)
|
||||
|
||||
FileUtils.symlink("#{workdir}/test_config.json", "#{workdir}/config.json", force: true)
|
||||
Dir.chdir(workdir) do
|
||||
exec_cmd "#{runc} --root root-state run 123"
|
||||
end
|
||||
end
|
||||
|
||||
def exec_cmd(cmd)
|
||||
puts "> #{cmd}"
|
||||
puts "------------------------------------------------------------"
|
||||
success = system({}, cmd)
|
||||
raise "Failed #{cmd}" unless success
|
||||
end
|
||||
|
||||
def write_runc_config
|
||||
@configurator = Pipeline::Util::RuncConfigurator.new
|
||||
memoize
|
||||
def container_driver
|
||||
img = Pipeline::Util::ImgWrapper.new
|
||||
runc = Pipeline::Util::RuncWrapper.new
|
||||
configurator = Pipeline::Util::RuncConfigurator.new
|
||||
configurator.seed_from_env
|
||||
|
||||
configurator.setup_for_terminal_access
|
||||
File.write("#{workdir}/terminal_config.json", configurator.build.to_json)
|
||||
|
||||
configurator.setup_script("/mnt/exercism-iteration/test_script.sh")
|
||||
File.write("#{workdir}/test_config.json", configurator.build.to_json)
|
||||
Pipeline::Util::ContainerDriver.new(runc, img, configurator, workdir)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
59
lib/pipeline/validation/check_environment_invariants.rb
Normal file
59
lib/pipeline/validation/check_environment_invariants.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
module Pipeline::Validation
|
||||
class CheckEnvironmentInvariants
|
||||
include Mandate
|
||||
|
||||
initialize_with :container_driver
|
||||
|
||||
def call
|
||||
run_script <<-EOS
|
||||
if ping -q -c 1 -W 1 8.8.8.8 >/mnt/exercism-iteration/ping_check.txt; then
|
||||
echo "IPv4 is up" >/mnt/exercism-iteration/ip_connectivity.txt
|
||||
else
|
||||
echo "IPv4 is down" >/mnt/exercism-iteration/ip_connectivity.txt
|
||||
fi
|
||||
EOS
|
||||
|
||||
ip_connectivity = File.read("#{workdir}/iteration/ip_connectivity.txt")
|
||||
raise "IP down check failed" if (ip_connectivity.strip != "IPv4 is down")
|
||||
|
||||
run_script <<-EOS
|
||||
nc -w 3 -v 8.8.8.8 53 2>/mnt/exercism-iteration/net_check.txt
|
||||
EOS
|
||||
|
||||
net_check = File.read("#{workdir}/iteration/net_check.txt")
|
||||
raise "NET check failed" if (net_check.strip != "8.8.8.8 (8.8.8.8:53) open")
|
||||
|
||||
run_script <<-EOS
|
||||
touch /mnt/exercism-iteration/fs_check.txt
|
||||
touch /mnt/foobar && echo "/mnt writable!" >>/mnt/exercism-iteration/fs_check.txt
|
||||
touch /opt/foobar && echo "/opt writable!" >>/mnt/exercism-iteration/fs_check.txt
|
||||
touch /opt/analyzer/foobar && echo "/opt/analyzer/ writable!" >>/mnt/exercism-iteration/fs_check.txt
|
||||
exit 0
|
||||
EOS
|
||||
|
||||
fs_check = File.read("#{workdir}/iteration/fs_check.txt")
|
||||
|
||||
raise "Not readonly: #{fs_check}" unless fs_check.strip.empty?
|
||||
|
||||
run_script <<-EOS
|
||||
echo "/tmp/ writable!" >/tmp/foobar
|
||||
EOS
|
||||
run_script <<-EOS
|
||||
cat /tmp/foobar >/mnt/exercism-iteration/tmp_check.txt
|
||||
EOS
|
||||
|
||||
tmp_check = File.read("#{workdir}/iteration/tmp_check.txt").strip
|
||||
tmp_file = File.read("#{workdir}/tmp/foobar").strip
|
||||
|
||||
raise "/tmp not persisting across invocations" if ("/tmp/ writable!" != tmp_check || "/tmp/ writable!" != tmp_file)
|
||||
end
|
||||
|
||||
def run_script(script_contents)
|
||||
container_driver.run_script(script_contents)
|
||||
end
|
||||
|
||||
def workdir
|
||||
container_driver.workdir
|
||||
end
|
||||
end
|
||||
end
|
||||
41
lib/pipeline/validation/check_fixtures.rb
Normal file
41
lib/pipeline/validation/check_fixtures.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
module Pipeline::Validation
|
||||
class CheckFixtures
|
||||
include Mandate
|
||||
|
||||
initialize_with :container_driver, :track_slug
|
||||
|
||||
def call
|
||||
clean_and_setup
|
||||
exercise_folders = Dir.glob("fixtures/#{track_slug}/*")
|
||||
exercise_folders.each do |exercise_folder|
|
||||
exercise_slug = exercise_folder.split("/").last
|
||||
Dir.glob("#{exercise_folder}/*").each do |fixture_folder|
|
||||
validate_status(exercise_slug, fixture_folder)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def clean_and_setup
|
||||
FileUtils.rm_rf("#{workdir}/iteration/")
|
||||
end
|
||||
|
||||
def validate_status(exercise, fixture_folder)
|
||||
FileUtils.cp_r "#{fixture_folder}/iteration", "#{workdir}/iteration"
|
||||
|
||||
container_driver.run_analyzer_for(exercise)
|
||||
|
||||
analysis = JSON.parse(File.read("#{workdir}/iteration/analysis.json"))
|
||||
expected = JSON.parse(File.read("#{fixture_folder}/expected_analysis.json"))
|
||||
|
||||
raise "Incorrect expected_status" if expected["status"].nil?
|
||||
raise "Incorrect status when validating #{fixture_folder}" if expected["status"] != analysis["status"]
|
||||
expected["comments"] ||= []
|
||||
analysis["comments"] ||= []
|
||||
raise "Incorrect comments when validating #{fixture_folder}" if expected["comments"].sort != analysis["comments"].sort
|
||||
end
|
||||
|
||||
def workdir
|
||||
container_driver.workdir
|
||||
end
|
||||
end
|
||||
end
|
||||
21
lib/pipeline/validation/check_invokable.rb
Normal file
21
lib/pipeline/validation/check_invokable.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
module Pipeline::Validation
|
||||
class CheckInvokable
|
||||
include Mandate
|
||||
|
||||
initialize_with :container_driver
|
||||
|
||||
def call
|
||||
container_driver.run_script <<-EOS
|
||||
echo "OK" >/mnt/exercism-iteration/result.txt
|
||||
EOS
|
||||
|
||||
text = File.read("#{workdir}/iteration/result.txt")
|
||||
|
||||
raise "Santity check failed" if (text.strip != "OK")
|
||||
end
|
||||
|
||||
def workdir
|
||||
container_driver.workdir
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user