diff --git a/www/tutorial/scripts/run_simulator_recording.rb b/www/tutorial/scripts/run_simulator_recording.rb index af39b1954..7d0e3ff68 100644 --- a/www/tutorial/scripts/run_simulator_recording.rb +++ b/www/tutorial/scripts/run_simulator_recording.rb @@ -10,46 +10,6 @@ input_dir = File.expand_path(File.join(File.dirname(__FILE__), '../content')) output_dir = File.expand_path(File.join(File.dirname(__FILE__), '../app/responses')) simulator = 'inspec-simulator' -puts '---> Load Content' - -# Load tutorial instructions -demos = YAML.load(File.read(File.join(input_dir, 'tutorial.yml')))['demos'] -tutorial_instructions = demos.map { |x| [x['title'], x['desc']] } - -# extract commands from instructions -commands = demos.map { |x| x['desc'] }.map { |x| x.scan(/```(.*?)```/m) }.flatten.map(&:strip).map { |x| x.split("\n") } - -puts '---> Prepare Simulation Commands' - -# find out if we have a single command or a multiline shell command -cmds = commands.map { |x| - cmd = x.join("\n") - if cmd.include?('describe') - cmd - else - x - end -} -tutorial_commands = cmds.flatten - -# Pull shell commands out so they can be handled -# This is currently done by checking if the command includes the word inspec :/ -no_shell_tutorial_commands = tutorial_commands.select { |a| a.include?('inspec') && a != 'inspec shell' } -shell_tutorial_commands = tutorial_commands.reject { |a| a.include?('inspec') } - -# escape the shell commands for echo -shell_tutorial_commands.map! { |x| - Shellwords.escape(x) -} -# Add 'echo' and ' | inspec shell' to shell commands to enable inspec shell command functionality -shell_commands = shell_tutorial_commands.map { |x| 'echo ' + x + ' | inspec shell' } - -# extract commands -extra_commands = YAML.load(File.read(File.join(input_dir, 'commands.yml')))['commands'] - -# Merge the output -commands = no_shell_tutorial_commands + extra_commands + shell_commands - # Create key given command def create_key(command) formatted_command = command.gsub(/\W/, '_') @@ -57,56 +17,103 @@ def create_key(command) not_too_long + '.txt' end -puts '---> Create json files for web tutorial' - def indent(text) ' ' + text end -# Create commands.json file -commands_file = File.new(File.join(output_dir, 'commands.json'), 'w') -commands_json = {} -commands.each { |x| - commands_json[x] = { - 'key' => create_key(x), - # indicates if the command is part of the tutorial.yml - 'extra' => extra_commands.include?(x), +# loads all commands from tutorial.yml +def load_tutorial_commands(demos) + # extract commands from instructions + raw_commands = demos.map { |x| x['desc'] }.map { |x| x.scan(/```(.*?)```/m) }.flatten.map(&:strip).map { |x| x.split("\n") } + + # find out if we have a single command or a multiline shell command + tutorial_commands = raw_commands.map { |x| + cmd = x.join("\n") + if cmd.include?('describe') + cmd + else + x + end + }.flatten + + # Pull shell commands out so they can be handled + # This is currently done by checking if the command includes the word inspec :/ + no_shell_tutorial_commands = tutorial_commands.select { |a| a.include?('inspec') && a != 'inspec shell' } + commands = no_shell_tutorial_commands.map {|cmd| + { + 'key' => create_key(cmd), + 'command' => cmd, + 'simulator_cmd' => cmd, + 'extra' => false, + 'shell' => 'sh', + } } -} -puts JSON.generate(commands_json) -commands_file.write(JSON.generate(commands_json)) -puts indent("Wrote #{commands_file.path}") -# Create instructions.json file -instructions_file = File.new(File.join(output_dir, 'instructions.json'), 'w') -tutorial_instructions.map! { |set| [set[0], GitHub::Markup.render('instructions.markdown', set[1])] } -puts JSON.generate(tutorial_instructions) -instructions_file.write(JSON.generate(tutorial_instructions)) -puts indent("Wrote #{instructions_file.path}") + # special handling for InSpec shell commands + shell_tutorial_commands = tutorial_commands.reject { |a| a.include?('inspec') } + commands += shell_tutorial_commands.map {|cmd| + # Add 'echo' and ' | inspec shell' to shell commands to enable inspec shell command functionality + simulator_cmd = 'echo ' + Shellwords.escape(cmd) + ' | inspec shell' + { + 'key' => create_key(simulator_cmd), + 'command' => cmd, + 'simulator_cmd' => simulator_cmd, + 'extra' => false, + 'shell' => 'inspec', + } + } + commands +end -# Generate command results files -require 'docker' -fail "#{simulator} docker image is not available" unless Docker::Image.exist?(simulator) +# load all commands from commands.yml +def load_extra_commands(extra_commands) + extra_commands.map {|cmd| + { + 'key' => create_key(cmd), + 'command' => cmd, + 'simulator_cmd' => cmd, + 'extra' => true, + 'shell' => 'sh', + } + } +end -# start container and get id -Docker.options[:read_timeout] = 3 * 60 -container = Docker::Container.create('Cmd' => ['/bin/sh'], 'Image' => simulator, 'Tty' => true) -container.start -puts "---> Run simulation on Container #{container.id}" -# Create Train connection -backend = Train.create('docker', { host: container.id }) -conn = backend.connection +def generate_webapp_instructions(demos, output_dir) + # Create instructions.json file + instructions_file = File.new(File.join(output_dir, 'instructions.json'), 'w') + tutorial_instructions = demos.map { |x| [x['title'], x['desc']] } + tutorial_instructions.map! { |set| [set[0], GitHub::Markup.render('instructions.markdown', set[1])] } + instructions_file.write(JSON.generate(tutorial_instructions)) + puts indent("Wrote #{instructions_file.path}") +end -# Loop over commands -commands.each do |command| - puts indent("Run `#{command}`") - cmd = conn.run_command(command) +def generate_webapp_commands(commands, output_dir) + # Create commands.json file + commands_file = File.new(File.join(output_dir, 'commands.json'), 'w') + commands_file.write(JSON.generate(commands)) + puts indent("Wrote #{commands_file.path}") +end + +def run_command(command, conn, output_dir) + puts indent("Run `#{command['command']}` on `#{command['shell']}`") + cmd = conn.run_command(command['simulator_cmd']) cmd.stdout # save the result and put it in inspec/www/app/responses with command as filename result = cmd.stdout - key = create_key(command) + # filter inspec shell welcome message + welcome = "To find out how to use it, type: help\n\n" + # To find out how to use it, type: help + idx = result.index(welcome) + if command['shell'] == 'inspec' && !idx.nil? + # find welcome message + index = idx + welcome.length + # also remove the command before the welcome message + result = result.slice(index, result.length - index) + end + + key = command['key'] filename = File.join(output_dir, key.to_s) out_file = File.new(filename, 'w') result.lines.each do |line| @@ -117,6 +124,39 @@ commands.each do |command| puts indent("Wrote #{filename}") end -# close train connection and stop container -conn.close -container.kill +def generate_simulation_files(simulator, commands, output_dir) + require 'docker' + fail "#{simulator} docker image is not available" unless Docker::Image.exist?(simulator) + + # start container and get id + Docker.options[:read_timeout] = 3 * 60 + container = Docker::Container.create('Cmd' => ['/bin/sh'], 'Image' => simulator, 'Tty' => true) + container.start + puts indent("Run simulation on Container #{container.id}") + # Create Train connection + backend = Train.create('docker', { host: container.id }) + conn = backend.connection + + # Loop over sh commands + commands.each do |command| + run_command(command, conn, output_dir) + end + + # close train connection and stop container + conn.close + container.kill +end + +# start workflow +puts '---> Load Content' +# Load tutorial instructions +demos = YAML.load(File.read(File.join(input_dir, 'tutorial.yml')))['demos'] +commands = load_tutorial_commands(demos) + +extra_commands = YAML.load(File.read(File.join(input_dir, 'commands.yml')))['commands'] +commands += load_extra_commands(extra_commands) + +puts '---> Create files for web app' +generate_webapp_instructions(demos, output_dir) +generate_webapp_commands(commands, output_dir) +generate_simulation_files(simulator, commands, output_dir)