Merge pull request #1101 from chef/chris-rock/optimize-tutorial

Optimize tutorial
This commit is contained in:
Dominik Richter 2016-09-22 03:18:36 +02:00 committed by GitHub
commit 25424e2d81
52 changed files with 1256 additions and 556 deletions

View file

@ -1,13 +1,9 @@
.main {
overflow: auto;
}
.terminal-nav {
.tutorial-nav {
display: inline-block;
position: fixed;
top: 0;
top: 10;
right: 0;
width: 120px;
width: 150px;
height: 60px;
color: #888;
text-align: center;
@ -25,23 +21,38 @@
img {
padding-top: 5px;
width: 160px;
height: 40px;
width: 100px;
padding-bottom: 5px;
position: fixed;
bottom: 10;
right: 30px;
z-index: 100;
}
.guide {
-webkit-font-smoothing: antialiased;
.tutorial {
position: fixed;
width: 100%;
top: 0;
left: 0;
z-index: 5;
}
.tutorial-wrapper {
max-width: 1000px;
min-width: 400px;
margin: 10px auto;
padding: 10px;
}
.tutorial-guide {
padding: 10px;
color: #DDDDDD;
background-color: #444;
font-family: 'Ubuntu Mono', 'Monaco', sans-serif;
letter-spacing: 1.2px;
font-size: 15px;
max-width: 1000px;
margin: auto;
padding: 1rem;
background-color: #444;
text-align: left;
line-height: 1.4;
border-radius: 5px;
}
.title {
@ -57,15 +68,13 @@ I'm unsure why, but attempting to set the style of code in this setting is just
not working....but this works! */
:host /deep/ code {
font-weight: bold;
font-size: 16px;
font-size: 15px;
}
.cli {
-webkit-font-smoothing: antialiased;
font-family: monospace;
font-size: 1.2rem;
white-space: pre-wrap;
word-wrap: break-word;
max-width: 1200px;
margin: auto;
position: fixed;
top: 250px;
right: 0;
left: 0;
bottom: 0;
}

View file

@ -1,17 +1,18 @@
<div class="terminal-nav">
<span (click)="updateInstructions('prev')"> < </span>
<span (click)="updateInstructions('next')"> > </span>
<span> x </span>
<div class="tutorial">
<div class="tutorial-nav">
<i (click)="updateInstructions('prev')" class="icon-angle-circled-left"></i>
<i (click)="updateInstructions('next')" class="icon-angle-circled-right"></i>
<i class="icon-cancel-circled"></i>
</div>
<div class="tutorial-wrapper">
<div class="tutorial-guide">
<p class="title">{{title}}</p>
<p [innerHTML]="formatInstructions()"></p>
</div>
</div>
</div>
<img src="inspec-logo.png" alt="Inspec Logo">
<div class="main">
<div class="guide">
<p class="title">{{title}}</p>
<p [innerHTML]="formatInstructions()"></p>
</div>
<xterm-terminal class="cli" (stdin)="evalCommand(command=$event)" [stdout]="stdout" [prompt]="prompt"></xterm-terminal>
<div class="cli">
<xterm-terminal (command)="evalCommand(command=$event)" [response]="response" [shell]="shell"></xterm-terminal>
</div>
</div>
<img src="inspec-logo-white.png" alt="InSpec Logo">

View file

@ -1,10 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, EventEmitter } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { HTTP_PROVIDERS } from '@angular/http';
import { XtermTerminalComponent } from './xterm-terminal/xterm-terminal.component';
declare var require: any;
var shellwords = require("shellwords");
const SH_PROMPT = '$ ';
const INSPEC_PROMPT = 'inspec> ';
@Component({
selector: 'my-app',
@ -15,170 +17,110 @@ var shellwords = require("shellwords");
})
export class AppComponent implements OnInit {
// template values
instructions: any;
// all available commands parsed from json files
commands: any = [];
// we support two shell modes: 'sh' and 'inspec'
shellMode: string = 'sh'
// all tutorial instructions
instructionsArray: any = [];
// title of the current tutorial step
title: string;
// instruction of the current step
instruction: any;
// keeps track of step number count
counter: number = 0;
// taken as input by xterm terminal componen
response: string;
shell: string;
stdout: EventEmitter<string> = new EventEmitter<string>();
// blue regular shell prompt
prompt: string = SH_PROMPT;
// colors for responses
red: string = "";
white: string = "";
black: string = "";
matchFound: boolean; // helps to handle no match found response
counter: number = 0; // keeps track of step number count
userCommand: string; // used to display better error msg when no match is found
// arrays of data parsed from json files
commands: any = [];
instructionsArray: any = [];
// inspec shell depth
shell_depth = 0;
shell_command = ''
constructor(private http: Http) { }
ngOnInit() {
// load json files
this.getInstructions();
this.getCommands();
// load content
this.loadInstructions();
this.loadCommands();
}
ngAfterViewChecked() {
window.scrollTo( 0, document.body.scrollHeight );
}
// called when command entered is 'next' or 'prev'
// modifies value of counter and calls displayInstructions
updateInstructions(step) {
let totalSteps = this.instructionsArray.length - 1;
let msg = Math.random();
if (step === 'next') {
if (this.counter <= totalSteps) {
this.counter += 1;
}
this.response = this.black + 'next' + msg;
} else if (step === 'prev') {
if (this.counter > 0) {
this.counter -= 1;
}
this.response = this.black + 'prev' + msg;
} else if (step === 'last') {
this.counter = totalSteps;
this.response = this.black + 'last' + msg;
}
this.displayInstructions();
}
// determines all commands that are not part of the tutorial
extraCmds() {
let cmds = this.commands
let extra = Object.keys(cmds).filter(function(key){
return cmds[key]['extra'] == true
let extra = this.commands.filter(function(item){
return item['extra'] == true
});
return extra
}
// display instructions based on value of counter and
// format text to remove triple backticks. if the user has reached
// then end of the demo, display a message containing extra commands that have been
// enabled in the demo
displayInstructions() {
if (this.counter === this.instructionsArray.length) {
this.title = "the end; that's all folks!";
this.instructions = "here are some other commands you can try out: \r\n\r\n" + this.extraCmds();
} else {
if (this.instructionsArray[this.counter][1]) {
this.title = this.instructionsArray[this.counter][0];
this.instructions = this.instructionsArray[this.counter][1];
} else {
this.instructions = 'Sorry, something seems to have gone wrong. Please try refreshing your browser.';
}
}
return extra.map(function(item){return item.command})
}
formatInstructions() {
return this.instructions || '';
return this.instruction || '';
}
// called when a new value is emitted for command
// checks for a match, calls parseInspecShell if shell is inspec-shell
// and calls checkCommand if none of the first commands match
evalCommand(command) {
this.userCommand = command;
if (command.match(/^next\s*/)) {
this.updateInstructions('next');
// called when tutorial commands need to be displayed
updateInstructions(step = null) {
// if step is given, we calculate the new index
let totalSteps = this.instructionsArray.length - 1;
switch(step) {
case "next":
if (this.counter < totalSteps) {
this.counter += 1;
}
break;
case 'prev':
if (this.counter > 0) {
this.counter -= 1;
}
break;
case 'first':
this.counter = 1;
break
case 'last':
this.counter = totalSteps;
break
}
else if (command.match(/^prev\s*/)) {
this.updateInstructions('prev');
}
else if (command.match(/^last\s*/)) {
this.updateInstructions('last');
}
else if (command.match(/^inspec\s*shell\s*$/)) {
this.shell = 'inspec-shell';
this.response = this.white + 'Welcome to the interactive InSpec Shell\r\nTo find out how to use it, type: help\r\nTo exit, type: exit\r\n';
}
else if (this.shell === 'inspec-shell') {
this.parseInspecShell(command);
}
else {
this.checkCommand(command);
if (this.counter === this.instructionsArray.length - 1) {
this.title = "the end; that's all folks!";
this.instruction = "here are some other commands you can try out: \r\n\r\n" + this.extraCmds();
} else if (this.instructionsArray[this.counter][1]) {
this.title = this.instructionsArray[this.counter][0];
this.instruction = this.instructionsArray[this.counter][1];
} else {
this.instruction = 'Sorry, something seems to have gone wrong. Please try refreshing your browser.';
}
}
// if the shell is inspec-shell, we want to exit the shell env on 'exit'
// format the command for checkCommand by using shellwords.escape and
// adding 'echo' and 'inspec shell' to match command from commands.json
parseInspecShell(command) {
if (command.match(/^exit\s*/)) {
this.shell = '';
this.response = '';
}
else if (command.match(/^pwd\s*/)) {
this.response = this.white + "anonymous-web-user/inspec-shell";
}
else {
let escaped_cmd;
let formatted_cmd;
// TODO: make this better
// I don't really like what we're doing here when we have a
// describe and control block, but I had a lot of trouble getting this
// to work because of the new lines carriage returns in the command from commands.json
// so at the moment we're splitting the command on "do" and assuming a match if that first
// result group matches (everything before do) :/
if (command.match(/^describe.*|^control/)) {
let split_cmd = command.split('do');
escaped_cmd = shellwords.escape(split_cmd[0]);
formatted_cmd = 'echo.*' + escaped_cmd ;
} else {
escaped_cmd = shellwords.escape(command)
formatted_cmd = 'echo.*' + escaped_cmd + '.*inspec.*shell';
}
let regex_compatible = formatted_cmd.replace(/\W+/g, '.*');
this.checkCommand(regex_compatible);
}
}
// takes the command as input, replaces all white space with regex whitespace matcher
// and creates a new regex. check if the regex matches any of the keys in the commands
// if it matches, we set matchFound to true and call displayResult. if it doesn't match,
// takes the command as input. checks if the regex matches any of the keys in the commands
// if it matches, we set matchFound to true and call printOnStdout. if it doesn't match,
// we display a default error message
checkCommand(command) {
execCommand(command, shell) {
let response = ''
let dir = 'app/responses/';
let cmd = command.replace(/ /g,'\\s*')
let regexcmd = new RegExp(('^'+cmd+'$'), 'm')
this.matchFound = false;
var matchFound = false;
// iterate over commands and try to match the command with the input
let cmds = Object.keys(this.commands)
for (var i = 0; i < cmds.length; i++) {
let cmd = cmds[i];
if (cmd.match(regexcmd)) {
this.matchFound = true;
let key = this.commands[cmd]['key'];
this.http.get(dir + key).subscribe(data => {
this.displayResult(command, data);
for (var i = 0; i < this.commands.length; i++) {
let item = this.commands[i]
if (item.regex.exec(command) && item.shell == shell) {
matchFound = true;
this.http.get(dir + item.key).subscribe(data => {
this.printOnStdout(data['_body'])
},
err => console.error(err));
}
@ -186,46 +128,119 @@ export class AppComponent implements OnInit {
// if no match is found, we check if the command entered was inspec exec something
// and if it is respond appropriately ('could not fetch inspec profile in ''), otherwise
// respond with 'invalid command' and the command entered
if (this.matchFound === false) {
let msg = Math.random();
if (matchFound === false) {
console.log('no match found')
if (command.match(/^inspec exec\s*.*/)) {
let target = command.match(/^inspec exec\s*(.*)/)
this.response = this.red + "Could not fetch inspec profile in '" + target[1] + "' " + this.black + msg;
response = this.red + "Could not fetch inspec profile in '" + target[1] + "' " + this.black;
} else {
this.response = this.red + 'invalid command: ' + this.userCommand + this.black + msg;
response = this.red + 'command not found: ' + command;
}
this.printOnStdout(response + "\n\r")
}
}
// if the command if inspec shell, we also need to set the shell variable to
// inspec shell. print response value and random msg (to ensure recognition of value change by xterm
// terminal component)
displayResult(command, data) {
if (command.match(/^inspec\s*shell\s*$/)) {
this.shell = 'inspec-shell';
// handles a stdin command and prints on terminal stdout
evalCommand(command) {
let self = this
// tutorial commands
var m = /^\s*(next|prev|first|last)\s*$/.exec(command)
let multiline = /\s*(describe|control|end)\s*/.exec(command)
if (m) {
// update the instructions widget
// we are not calling this from the ng event loop, therefore we need to
// trigger angular to update the tutorial section
setTimeout(function(){
self.updateInstructions(m[1]);
}, 0);
// send an empty response to get the command prompt back
this.printOnStdout('')
}
let msg = Math.random();
this.response = this.white + data['_body'] + this.black + msg;
// switch to InSpec shell commands
// TODO, support targets for InSpec shell
else if (/^\s*inspec\s*shell\s*$/.exec(command)) {
// switch to InSpec shell
this.shellMode = 'inspec'
// switch prompt
this.prompt = INSPEC_PROMPT;
// output inspec hello text
let init = "Welcome to the interactive InSpec Shell\n\rTo find out how to use it, type: help\n\r"
this.printOnStdout(init);
}
// leave InSpec shell
else if (/^\s*exit\s*$/.exec(command) && this.shellMode == 'inspec') {
this.shellMode = 'sh'
// switch prompt
this.prompt = SH_PROMPT;
this.printOnStdout("\n\r");
}
else if (this.shellMode == 'inspec' && multiline) {
// count control + describe
let mstart = command.match(/describe|control/g)
if (mstart) {
this.shell_depth += mstart.length
}
// end
let mend = command.match(/end/g)
if (mend) {
this.shell_depth -= mend.length
}
this.shell_command += command + "\n"
if (mend && this.shell_depth == 0) {
command = this.shell_command
this.shell_depth = 0
this.shell_command = ''
this.execCommand(command, this.shellMode);
}
}
// default sh commands
else if ((this.shell_depth == 0) || this.shellMode == 'sh') {
this.execCommand(command, this.shellMode);
}
// store command in cache, must be an inspec shell command
else {
this.shell_command += command + "\n"
}
}
// submit stdout data to terminal
printOnStdout(data){
this.stdout.emit(data)
this.stdout.emit(this.prompt)
}
// load json file for instructions and save to instructionsArray
// call displayInstructions to load first set of instructions
getInstructions() {
// call updateInstructions to load first set of instructions
loadInstructions() {
let self = this
this.http.get('app/responses/instructions.json')
.subscribe(data => {
this.instructionsArray = JSON.parse(data['_body']);
this.displayInstructions();
self.instructionsArray = JSON.parse(data['_body']);
self.updateInstructions();
},
err => console.error(err)
);
}
// load json file for commands and push each object to commands
getCommands() {
loadCommands() {
let self = this;
this.http.get('app/responses/commands.json')
.subscribe(data => {
let result = JSON.parse(data['_body']);
this.commands = result
let commands = JSON.parse(data['_body']);
// generate regular expression for each entry
for (var i = 0; i < commands.length; i++) {
// preps a string for use in regular expressions
// @see http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
let cmd = commands[i].command.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
cmd = cmd.replace(/ /g,"\\s*")
cmd = cmd.replace(/\n/g,"\\s*")
let pattern = '^\\s*'+cmd+'\\s*$'
commands[i].regex = new RegExp(pattern, 'im')
}
this.commands = commands
},
err => console.error(err)
);

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1 @@
inspec> command('uname -a').stdout
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec> => "Linux 2e958a7a89d6 4.4.19-moby #1 SMP Mon Aug 22 23:30:19 UTC 2016 x86_64 Linux\n"
inspec> => "Linux 20b81abc543e 4.4.20-moby #1 SMP Thu Sep 8 21:27:34 UTC 2016 x86_64 Linux\n"

View file

@ -1,14 +1,4 @@
inspec> control "id" do
inspec>  title "Check permissions on /root!"
inspec>  impact 0.5
inspec>  describe file('/root') do
inspec>  its('mode') { should cmp '0750'}
inspec>  end
inspec>  end
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec>  ✖ id: File /root mode should cmp "0750" (
inspec>  ✖ id: Check permissions on /root! (
expected: "0750"
got: 0700
@ -16,4 +6,9 @@ To find out how to use it, type: help
)
Summary: 0 successful, 1 failures, 0 skipped
Profile: inspec-shell
Version: unknown
Profile Summary: 0 successful, 1 failures, 0 skipped

View file

@ -1,12 +1,17 @@
inspec> describe file('/root') do
inspec>  it { should exist }
inspec>  its('mode') { should cmp '0750'}
inspec>  end
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec> 
File /root
 ✔ should exist
 ✖ mode should cmp "0750"
expected: "0750"
got: 0700
(compared using `cmp` matcher)

Summary: 1 successful, 1 failures, 0 skipped
Profile: inspec-shell
Version: unknown
Test Summary: 1 successful, 1 failures, 0 skipped

View file

@ -1,5 +1 @@
inspec> file('/proc/cpuinfo').owner
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec> => "root"

View file

@ -1,7 +1,3 @@
inspec> help command
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help
Name: command
Description:

View file

@ -1,7 +1,3 @@
inspec> help file
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help
Name: file
Description:

View file

@ -1,7 +1,3 @@
inspec> help
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help

Available commands:

View file

@ -1,7 +1,3 @@
inspec> help os
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help
Name: os
Description:
@ -10,7 +6,7 @@ Use the os InSpec audit resource to test the platform on which the system is run
Example:
describe os.family do
describe os[:family] do
it { should eq 'redhat' }
end
describe os.redhat? do

View file

@ -1,5 +1 @@
inspec> help resources
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help
apache apache_conf apt ppa audit_policy auditd_conf auditd_rules command bash file bond bridge directory etc_group gem group grub_conf host iis_site inetd_conf interface iptables json kernel_module kernel_parameter linux_kernel_parameter limits_conf login_defs mount mssql_session mysql mysql_conf mysql_session npm ntp_conf oneget os os_env package parse_config parse_config_file passwd pip port postgres postgres_conf postgres_session powershell script processes registry_key windows_registry_key security_policy service systemd_service upstart_service sysv_service bsd_service launchd_service runit_service shadow ssl ssh_config sshd_config sys_info users user vbscript windows_feature xinetd_conf wmi yum yumrepo yaml csv ini

View file

@ -1,6 +1,2 @@
inspec> sshd_config.params
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec> => {"authorizedkeysfile"=>[".ssh/authorized_keys"],
"subsystem"=>["sftp\t/usr/lib/ssh/sftp-server"]}

View file

@ -1,6 +1,6 @@
I, [2016-09-16T13:59:42.964480 #1147] INFO -- : Checking profile in examples/profile
I, [2016-09-16T13:59:42.964582 #1147] INFO -- : Metadata OK.
I, [2016-09-16T13:59:42.976429 #1147] INFO -- : Found 4 controls.
I, [2016-09-16T13:59:42.976466 #1147] INFO -- : Control definitions OK.
I, [2016-09-16T13:59:42.976604 #1147] INFO -- : Generate archive /filesystem/profile.tar.gz.
I, [2016-09-16T13:59:42.978718 #1147] INFO -- : Finished archive generation.
I, [2016-09-21T20:14:01.136322 #1698] INFO -- : Checking profile in examples/profile
I, [2016-09-21T20:14:01.136404 #1698] INFO -- : Metadata OK.
I, [2016-09-21T20:14:01.151169 #1698] INFO -- : Found 4 controls.
I, [2016-09-21T20:14:01.151207 #1698] INFO -- : Control definitions OK.
I, [2016-09-21T20:14:01.151402 #1698] INFO -- : Generate archive /filesystem/profile.tar.gz.
I, [2016-09-21T20:14:01.154062 #1698] INFO -- : Finished archive generation.

View file

@ -1,7 +1,7 @@
Location: examples/profile
Profile: profile
Controls: 4
Timestamp: 2016-09-16T13:59:27+00:00
Timestamp: 2016-09-21T20:13:37+00:00
Valid: true
No errors or warnings

View file

@ -1,8 +1,3 @@
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: local://
 ✔ tmp-1.0: Create /tmp directory
 ✔ File /tmp should be directory
 ○ gordon-1.0: Verify the version number of Gordon (1 skipped)
@ -14,4 +9,11 @@ Target: local://
File /tmp
 ✔ should be directory
Summary: 4 successful, 0 failures, 1 skipped
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: local://
Profile Summary: 2 successful, 0 failures, 1 skipped
Test Summary: 4 successful, 0 failures, 1 skipped

View file

@ -1,8 +1,3 @@
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: ssh://bob@host.node:
 ✔ tmp-1.0: Create /tmp directory
 ✔ File /tmp should be directory
 ○ gordon-1.0: Verify the version number of Gordon (1 skipped)
@ -14,4 +9,11 @@ Target: ssh://bob@host.node:
File /tmp
 ✔ should be directory
Summary: 4 successful, 0 failures, 1 skipped
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: ssh://bob@host.node:
Profile Summary: 2 successful, 0 failures, 1 skipped
Test Summary: 4 successful, 0 failures, 1 skipped

File diff suppressed because one or more lines are too long

View file

@ -1,8 +1,3 @@
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: docker://abcdef123
 ✔ tmp-1.0: Create /tmp directory
 ✔ File /tmp should be directory
 ○ gordon-1.0: Verify the version number of Gordon (1 skipped)
@ -14,4 +9,11 @@ Target: docker://abcdef123
File /tmp
 ✔ should be directory
Summary: 4 successful, 0 failures, 1 skipped
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: docker://abcdef123
Profile Summary: 2 successful, 0 failures, 1 skipped
Test Summary: 4 successful, 0 failures, 1 skipped

View file

@ -1,8 +1,3 @@
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: ssh://bob@host.node:
 ✔ tmp-1.0: Create /tmp directory
 ✔ File /tmp should be directory
 ○ gordon-1.0: Verify the version number of Gordon (1 skipped)
@ -14,4 +9,11 @@ Target: ssh://bob@host.node:
File /tmp
 ✔ should be directory
Summary: 4 successful, 0 failures, 1 skipped
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: ssh://bob@host.node:
Profile Summary: 2 successful, 0 failures, 1 skipped
Test Summary: 4 successful, 0 failures, 1 skipped

View file

@ -1,8 +1,3 @@
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: winrm://alice@windows.node:
 ✔ tmp-1.0: Create /tmp directory
 ✔ File /tmp should be directory
 ○ gordon-1.0: Verify the version number of Gordon (1 skipped)
@ -14,4 +9,11 @@ Target: winrm://alice@windows.node:
File /tmp
 ✔ should be directory
Summary: 4 successful, 0 failures, 1 skipped
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: winrm://alice@windows.node:
Profile Summary: 2 successful, 0 failures, 1 skipped
Test Summary: 4 successful, 0 failures, 1 skipped

View file

@ -1,8 +1,3 @@
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: winrm://alice@windows.node:
 ✔ tmp-1.0: Create /tmp directory
 ✔ File /tmp should be directory
 ○ gordon-1.0: Verify the version number of Gordon (1 skipped)
@ -14,4 +9,11 @@ Target: winrm://alice@windows.node:
File /tmp
 ✔ should be directory
Summary: 4 successful, 0 failures, 1 skipped
Profile: InSpec Example Profile (profile)
Version: 1.0.0
Target: winrm://alice@windows.node:
Profile Summary: 2 successful, 0 failures, 1 skipped
Test Summary: 4 successful, 0 failures, 1 skipped

View file

@ -24,6 +24,7 @@ Options:
[--color], [--no-color] # Use colors in output.
# Default: true
[--attrs=one two three] # Load attributes file (experimental)
[--cache=CACHE] # Use the given path for caching dependencies. (default: ~/.inspec/cache)
l, [--log-level=LOG_LEVEL] # Set the log level: info (default), debug, warn, error
[--log-location=LOG_LOCATION] # Location to send diagnostic log messages to. (default: STDOUT or STDERR)
[--diagnose], [--no-diagnose] # Show diagnostics (versions, configurations)

View file

@ -1 +1 @@
{"name":"profile","title":"InSpec Example Profile","maintainer":"Chef Software, Inc.","copyright":"Chef Software, Inc.","copyright_email":"support@chef.io","license":"Apache 2 license","summary":"Demonstrates the use of InSpec Compliance Profile","version":"1.0.0","supports":[{"os-family":"unix"}],"controls":{"tmp-1.0":{"title":"Create /tmp directory","desc":"An optional description...","impact":0.7,"refs":[{"url":"http://...","ref":"Document A-12"}],"tags":{"data":"temp data","security":null},"code":"control \"tmp-1.0\" do # A unique ID for this control\n impact 0.7 # The criticality, if this control fails.\n title \"Create /tmp directory\" # A human-readable title\n desc \"An optional description...\" # Describe why this is needed\n tag data: \"temp data\" # A tag allows you to associate key information\n tag \"security\" # to the test\n ref \"Document A-12\", url: 'http://...' # Additional references\n\n describe file('/tmp') do # The actual test\n it { should be_directory }\n end\nend\n","source_location":{"ref":"examples/profile/controls/example.rb","line":8}},"(generated from example.rb:22 7195529956129b055ac310a3a55c6d56)":{"title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":" rule = rule_class.new(id, profile_id, {}) do\n res = describe(*args, &block)\n end\n","source_location":{"ref":"/usr/local/bundle/gems/inspec-0.35.0/lib/inspec/control_eval_context.rb","line":87}},"gordon-1.0":{"title":"Verify the version number of Gordon","desc":"An optional description...","impact":0.7,"refs":[{"uri":"http://...","ref":"Gordon Requirements 1.0"}],"tags":{"gordon":null},"code":"control 'gordon-1.0' do\n impact 0.7\n title 'Verify the version number of Gordon'\n desc 'An optional description...'\n tag 'gordon'\n ref 'Gordon Requirements 1.0', uri: 'http://...'\n\n # Test using the custom gordon_config Inspec resource\n # Find the resource content here: ../libraries/\n describe gordon_config do\n it { should exist }\n its('version') { should eq('1.0') }\n its('file_size') { should <= 20 }\n its('comma_count') { should eq 0 }\n end\n\n # Test the version again to showcase variables\n g = gordon_config\n g_path = g.file_path\n g_version = g.version\n describe file(g_path) do\n its('content') { should match g_version }\n end\nend\n","source_location":{"ref":"examples/profile/controls/gordon.rb","line":14}},"ssh-1":{"title":"Allow only SSH Protocol 2","desc":"Only SSH protocol version 2 connections should be permitted. The default setting in /etc/ssh/sshd_config is correct, and can be verified by ensuring that the following line appears: Protocol 2","impact":1.0,"refs":[{"url":"https://www.nsa.gov/ia/_files/os/redhat/rhel5-guide-i731.pdf","ref":"NSA-RH6-STIG - Section 3.5.2.1"},{"url":"http://iasecontent.disa.mil/stigs/zip/Jan2016/U_RedHat_6_V1R10_STIG.zip","ref":"DISA-RHEL6-SG - Section 9.2.1"},{"ref":"http://people.redhat.com/swells/scap-security-guide/RHEL/6/output/ssg-centos6-guide-C2S.html"}],"tags":{"production":null,"development":null,"ssh":null,"sshd":null,"openssh-server":null,"cce":"CCE-27072-8","disa":"RHEL-06-000227","nist":"IA-5(1)","cci":"CCI-001436","remediation":"https://supermarket.chef.io/cookbooks/ssh-hardening"},"code":"control 'ssh-1' do\n impact 1.0\n\n title 'Allow only SSH Protocol 2'\n desc 'Only SSH protocol version 2 connections should be permitted.\n The default setting in /etc/ssh/sshd_config is correct, and can be\n verified by ensuring that the following line appears: Protocol 2'\n\n tag 'production','development'\n tag 'ssh','sshd','openssh-server'\n\n tag cce: 'CCE-27072-8'\n tag disa: 'RHEL-06-000227'\n\n tag nist: 'AC-3(10).i'\n tag nist: 'IA-5(1)'\n\n tag cci: 'CCI-000776'\n tag cci: 'CCI-000774'\n tag cci: 'CCI-001436'\n\n tag remediation: 'stig_rhel6/recipes/sshd-config.rb'\n tag remediation: 'https://supermarket.chef.io/cookbooks/ssh-hardening'\n\n ref 'NSA-RH6-STIG - Section 3.5.2.1', url: 'https://www.nsa.gov/ia/_files/os/redhat/rhel5-guide-i731.pdf'\n ref 'DISA-RHEL6-SG - Section 9.2.1', url: 'http://iasecontent.disa.mil/stigs/zip/Jan2016/U_RedHat_6_V1R10_STIG.zip'\n ref 'http://people.redhat.com/swells/scap-security-guide/RHEL/6/output/ssg-centos6-guide-C2S.html'\n\n describe file('/bin/sh') do\n it { should be_owned_by 'root' }\n end\nend\n","source_location":{"ref":"examples/profile/controls/meta.rb","line":3}}},"groups":{"controls/example.rb":{"title":"/tmp profile","controls":["tmp-1.0","(generated from example.rb:22 7195529956129b055ac310a3a55c6d56)"]},"controls/gordon.rb":{"title":"Gordon Config Checks","controls":["gordon-1.0"]},"controls/meta.rb":{"title":"SSH Server Configuration","controls":["ssh-1"]}},"attributes":[]}
{"name":"profile","title":"InSpec Example Profile","maintainer":"Chef Software, Inc.","copyright":"Chef Software, Inc.","copyright_email":"support@chef.io","license":"Apache 2 license","summary":"Demonstrates the use of InSpec Compliance Profile","version":"1.0.0","supports":[{"os-family":"unix"}],"controls":[{"title":"Create /tmp directory","desc":"An optional description...","impact":0.7,"refs":[{"url":"http://...","ref":"Document A-12"}],"tags":{"data":"temp data","security":null},"code":"control \"tmp-1.0\" do # A unique ID for this control\n impact 0.7 # The criticality, if this control fails.\n title \"Create /tmp directory\" # A human-readable title\n desc \"An optional description...\" # Describe why this is needed\n tag data: \"temp data\" # A tag allows you to associate key information\n tag \"security\" # to the test\n ref \"Document A-12\", url: 'http://...' # Additional references\n\n describe file('/tmp') do # The actual test\n it { should be_directory }\n end\nend\n","source_location":{"ref":"examples/profile/controls/example.rb","line":8},"id":"tmp-1.0"},{"title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":" rule = rule_class.new(id, profile_id, {}) do\n res = describe(*args, &block)\n end\n","source_location":{"ref":"/usr/local/bundle/gems/inspec-1.0.0.pre.beta1/lib/inspec/control_eval_context.rb","line":87},"id":"(generated from example.rb:22 6386e6cced185a1a98884c0d9cfa8113)"},{"title":"Verify the version number of Gordon","desc":"An optional description...","impact":0.7,"refs":[{"uri":"http://...","ref":"Gordon Requirements 1.0"}],"tags":{"gordon":null},"code":"control 'gordon-1.0' do\n impact 0.7\n title 'Verify the version number of Gordon'\n desc 'An optional description...'\n tag 'gordon'\n ref 'Gordon Requirements 1.0', uri: 'http://...'\n\n # Test using the custom gordon_config Inspec resource\n # Find the resource content here: ../libraries/\n describe gordon_config do\n it { should exist }\n its('version') { should eq('1.0') }\n its('file_size') { should <= 20 }\n its('comma_count') { should eq 0 }\n end\n\n # Test the version again to showcase variables\n g = gordon_config\n g_path = g.file_path\n g_version = g.version\n describe file(g_path) do\n its('content') { should match g_version }\n end\nend\n","source_location":{"ref":"examples/profile/controls/gordon.rb","line":14},"id":"gordon-1.0"},{"title":"Allow only SSH Protocol 2","desc":"Only SSH protocol version 2 connections should be permitted. The default setting in /etc/ssh/sshd_config is correct, and can be verified by ensuring that the following line appears: Protocol 2","impact":1.0,"refs":[{"url":"https://www.nsa.gov/ia/_files/os/redhat/rhel5-guide-i731.pdf","ref":"NSA-RH6-STIG - Section 3.5.2.1"},{"url":"http://iasecontent.disa.mil/stigs/zip/Jan2016/U_RedHat_6_V1R10_STIG.zip","ref":"DISA-RHEL6-SG - Section 9.2.1"},{"ref":"http://people.redhat.com/swells/scap-security-guide/RHEL/6/output/ssg-centos6-guide-C2S.html"}],"tags":{"production":null,"development":null,"ssh":null,"sshd":null,"openssh-server":null,"cce":"CCE-27072-8","disa":"RHEL-06-000227","nist":"IA-5(1)","cci":"CCI-001436","remediation":"https://supermarket.chef.io/cookbooks/ssh-hardening"},"code":"control 'ssh-1' do\n impact 1.0\n\n title 'Allow only SSH Protocol 2'\n desc 'Only SSH protocol version 2 connections should be permitted.\n The default setting in /etc/ssh/sshd_config is correct, and can be\n verified by ensuring that the following line appears: Protocol 2'\n\n tag 'production','development'\n tag 'ssh','sshd','openssh-server'\n\n tag cce: 'CCE-27072-8'\n tag disa: 'RHEL-06-000227'\n\n tag nist: 'AC-3(10).i'\n tag nist: 'IA-5(1)'\n\n tag cci: 'CCI-000776'\n tag cci: 'CCI-000774'\n tag cci: 'CCI-001436'\n\n tag remediation: 'stig_rhel6/recipes/sshd-config.rb'\n tag remediation: 'https://supermarket.chef.io/cookbooks/ssh-hardening'\n\n ref 'NSA-RH6-STIG - Section 3.5.2.1', url: 'https://www.nsa.gov/ia/_files/os/redhat/rhel5-guide-i731.pdf'\n ref 'DISA-RHEL6-SG - Section 9.2.1', url: 'http://iasecontent.disa.mil/stigs/zip/Jan2016/U_RedHat_6_V1R10_STIG.zip'\n ref 'http://people.redhat.com/swells/scap-security-guide/RHEL/6/output/ssg-centos6-guide-C2S.html'\n\n describe file('/bin/sh') do\n it { should be_owned_by 'root' }\n end\nend\n","source_location":{"ref":"examples/profile/controls/meta.rb","line":3},"id":"ssh-1"}],"groups":[{"title":"/tmp profile","controls":["tmp-1.0","(generated from example.rb:22 6386e6cced185a1a98884c0d9cfa8113)"],"id":"controls/example.rb"},{"title":"Gordon Config Checks","controls":["gordon-1.0"],"id":"controls/gordon.rb"},{"title":"SSH Server Configuration","controls":["ssh-1"],"id":"controls/meta.rb"}],"attributes":[]}

View file

@ -1 +1 @@
0.35.0
1.0.0-beta1

View file

@ -1,9 +1,11 @@
#terminal-container {
max-width: 1200px;
height: 350px;
margin: 0 auto;
padding: 2px;
font-size: 16px;
max-width: 1000px;
-webkit-font-smoothing: antialiased;
font-family: monospace;
font-size: 1.2rem;
}
#terminal-container .terminal {
@ -26,4 +28,4 @@ so we're deep assigning here to make sure that 0 is marked as black */
on the black background so we're deep assigning to make 6 a lightskyblu*/
:host /deep/ .xterm-color-6 {
color: lightskyblue;
}
}

View file

@ -1 +1 @@
<div (keyup)="onKey($event)" (keydown)="handleDelete($event)" id="terminal-container"></div>
<div id="terminal-container" (window:resize)="onResize($event)"></div>

View file

@ -1,4 +1,12 @@
import { Component, OnInit, Input, Output, EventEmitter, SimpleChange } from '@angular/core';
import {
Component,
OnInit,
OnChanges,
Input,
Output,
EventEmitter,
SimpleChange
} from '@angular/core';
declare var Terminal: any;
@Component({
@ -6,19 +14,22 @@ declare var Terminal: any;
templateUrl: 'app/xterm-terminal/xterm-terminal.component.html',
styleUrls: ['app/xterm-terminal/xterm-terminal.component.css']
})
export class XtermTerminalComponent implements OnInit {
@Input() response: string;
@Input() shell: string;
@Output() command: EventEmitter<string> = new EventEmitter<string>();
export class XtermTerminalComponent implements OnInit, OnChanges{
// handle up/down arrow functionality
previousCommands: any = [];
poppedCommands: any = [];
// This Xterm implementation emits all commands, once the users presses 'Enter'
@Output() stdin: EventEmitter<string> = new EventEmitter<string>();
// waits for events that should be printed on the terminal
@Input() stdout: EventEmitter<string>;
// sets the shell prompt
@Input() prompt: string;
// handle up/down command history functionality
history: any = [];
historyIndex: number;
last: number;
// handle user input
buffer: string = ''; // the buffer is our string of user input
blockCmd: string = ''; // handle describe and control blocks
// caches stdin, until the user presses Enter
buffer: string = '';
// xterm variables
terminalContainer: any;
@ -27,200 +38,184 @@ export class XtermTerminalComponent implements OnInit {
cols: string;
rows: string;
// we cannot use \33[2K\r since strict mode recognize it as ocal number
clearLine = "\u001b[2K\r"
ngOnInit() {
// set up Xterm terminal (https://github.com/sourcelair/xterm.js)
this.terminalContainer = document.getElementById('terminal-container');
this.cols = '70';
this.rows = '70';
this.createTerminal();
}
this.initializeTerminal();
}
// watch for changes on the value of response and call printResponse
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
if (changes['response'] && this.term) {
let currentResponse = changes['response'].currentValue;
this.printResponse(currentResponse);
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
let log: string[] = [];
for (let propName in changes) {
let changedProp = changes[propName];
// reprint prompt
if (propName == 'prompt' && this.term != null) {
this.term.write(this.clearLine+this.prompt)
}
}
}
onResize(event) {
this.updateTerminalSize()
}
updateTerminalSize() {
// recalculate cols and rows
let charwidth = 24;
// read size from parent wrapping element
let cols = 80
let rows = Math.floor(this.terminalContainer.parentElement.clientHeight / charwidth)
this.term.resize(cols, rows);
}
// create Xterm terminal (https://github.com/sourcelair/xterm.js)
createTerminal() {
initializeTerminal() {
while (this.terminalContainer.children.length) {
this.terminalContainer.removeChild(this.terminalContainer.children[0]);
}
this.term = new Terminal({
cursorBlink: true
cursorBlink: true,
});
this.term.open(this.terminalContainer);
this.term.fit();
var initialGeometry = this.term.proposeGeometry(),
cols = initialGeometry.cols,
rows = initialGeometry.rows;
this.cols = cols;
this.rows = rows;
this.runFakeTerminal()
}
// more Xterm terminal stuff. we're faking it. faking it can be a good thing ;)
runFakeTerminal() {
// start registering the event listener, only need to be done the first time
if (this.term._initialized) {
return;
}
this.term._initialized = true;
this.setPrompt();
this.writePrompt();
// determine rows for terminal
this.updateTerminalSize()
let self = this
// raw data
this.term.on('data', function(data){
self.writeBuffer(data)
})
// overwrite keydown handler to be able to customize cursor handling
this.term.attachCustomKeydownHandler(function(ev){
return self.keydown(ev);
})
// handle stream events from the app
this.stdout.subscribe(function(data) {
self.onStdout(data)
}, function(err) {
console.error(err)
})
}
// if the response matches the special message we send when the user
// has entered 'next' or 'prev' (to navigate through the demo)
// then no need to print the response, just set the prompt. otherwise,
// print response and set prompt
printResponse(response) {
if (response.match(/.30mnext|.30mprev|.30mlast/)) {
// call createTerminal to clear terminal screen on next and prev
this.createTerminal();
} else {
this.term.writeln(response);
this.setPrompt();
}
// this writes the raw buffer and includes invisible charaters
writeBuffer(data) {
this.term.write(data);
this.buffer += data;
}
// the value of shell is taken as an input. the default
// shellprompt is displayed unless shell is set to 'inspec-shell'
setPrompt() {
this.buffer = '';
if (this.shell === 'inspec-shell') {
if (this.blockCmd != '') {
this.term.write(' inspec>*'); // green inspec shell prompt with * to denote multi-line command
// reset the command buffer
resetBuffer() {
this.buffer = ''
}
// print out stdout data
onStdout(data) {
let res = data
this.term.write(res);
}
// print out error
onStderr(response) {
this.term.writeln(response);
}
// writes the command prompt, a prompt is not part of the input buffer
writePrompt() {
this.term.write(this.prompt);
}
// stores a command in commmand history
storeInHistory(cmd) {
// remove cariage returns from history commands
cmd = cmd.replace(/\r/,'')
this.history.push(cmd);
this.historyIndex = this.history.length
}
// prints the current selected command at history index
historyCmdtoStdout() {
this.buffer = this.history[this.historyIndex]
this.reprintCommand(this.buffer )
}
// reprint a command in the current line
reprintCommand(cmd){
this.term.write(this.clearLine+this.prompt + cmd)
}
// resets the terminal
clear() {
this.term.reset()
this.writePrompt()
}
// terminal event handler
// be aware that we need to decouple the event loops, therefore we need to
// use setTimeout to reliable print content on the terminal
keydown(ev) {
// console.log(ev);
let self = this
if (ev.keyCode === 13) {
let cmd = self.buffer
// special handling for clear and cls
if (/^\s*clear|cls\s*/.exec(cmd) != null) {
// reset terminal
setTimeout(function(){
self.clear()
}, 0);
} else {
this.term.write(' inspec> '); // green inspec shell prompt
}
} else {
this.term.write('$ '); // blue regular shell prompt
}
}
// delete everything on the line
deleteCharacters() {
// don't delete the prompt
let letters = this.term.x - 2;
for (var i = 0; i < letters; i++) {
this.term.write('\b \b');
}
}
// keydown calls handleDelete to check if the user is holding down the backpace key
// TODO: make this work! the backspace event isn't coming in for some reason :(
handleDelete(ev) {
if (ev.keyCode == 8) {
this.deleteCharacters();
}
}
// handle describe and control blocks. if the command entered matches the
// syntax we would generally expect in a control/describe block, then we
// wait to have the whole string command (as marked by 'end' for a simple describe
// block and end end for a control block) and emit that
handleBlockCommand(buffer) {
this.blockCmd += buffer + '';
if (this.blockCmd.match(/^control/)) {
if (this.blockCmd.match(/end.*end.*/)) {
this.command.emit(this.blockCmd);
this.blockCmd = '';
} else {
this.setPrompt();
}
} else {
if (buffer.match(/^end$/)) {
this.command.emit(this.blockCmd);
this.blockCmd = '';
} else {
this.setPrompt();
}
}
}
// onKey will check if the character is printable (on keyup) and add it to the buffer. if the enter key is hit
// the value of the buffer is emitted as 'command'. onKey also supports proper backspace handling and
// the ability to up-arrow through previous commands/down arrow back through them.
onKey(ev) {
var shell = null
// determine if the character is a printable one
var printable = ['Alt', 'Control', 'Meta', 'Shift', 'CapsLock', 'Tab', 'Escape', 'ArrowLeft', 'ArrowRight'].indexOf(ev.key) == -1
// on enter, save command to array and send current value of buffer
// to parent component (app)
if (ev.keyCode == 13) {
if ((this.buffer === 'clear') || (this.buffer === 'cls')) {
this.createTerminal();
} else {
this.previousCommands.push(this.buffer);
this.term.write('\r\n');
if (this.shell === 'inspec-shell') {
// if the command entered matches any of the typical describe and
// control blocks, then we call handleBlockCommand to not emit the
// value until we have the whole multi-line command
if (this.buffer.match(/^describe.*|^it.*|^end$|^impact.*|^title.*|^control.*/)) {
this.handleBlockCommand(this.buffer);
} else {this.command.emit(this.buffer);}
} else {
this.command.emit(this.buffer);
}
// decouple from event loop, write a new line
setTimeout(function(){
self.term.writeln('')
self.stdin.emit(cmd);
self.storeInHistory(cmd)
}, 0);
}
// reset index to last item +1
self.resetBuffer()
}
// on backspace, pop characters from buffer
else if (ev.keyCode == 8) {
// if inspec shell is being used, this needs to be set to 9 to account for the extra letters
if (this.shell === 'inspec-shell') {
if (this.term.x > 8) {
this.buffer = this.buffer.substr(0, this.buffer.length-1);
this.term.write('\b \b');
}
} else {
// setting the value here to 3 ensures that we don't delete the promp '$' or the space after it
if (this.term.x > 2) {
this.buffer = this.buffer.substr(0, this.buffer.length-1);
this.term.write('\b \b');
}
}
self.buffer = self.buffer.substr(0, self.buffer.length-1);
setTimeout(function(){
self.reprintCommand(self.buffer)
}, 0);
}
// on up arrow, delete anything on line, print previous command
// and push last to poppedCommands
else if (ev.keyCode === 38) {
let last;
this.buffer = '';
if (this.previousCommands.length > 0) {
last = this.previousCommands.pop();
this.poppedCommands.push(last);
} else {
last = '';
}
this.deleteCharacters();
this.buffer = last;
this.term.write(last);
}
// on down arrow, delete anything on line, print last item from poppedCommands
// and push previous to previousCommands
// press the up key, find previous item in cmd history
else if (ev.keyCode === 40) {
let previous;
this.buffer = '';
if (this.poppedCommands.length > 0) {
previous = this.poppedCommands.pop();
this.previousCommands.push(previous);
} else {
previous = '';
}
this.deleteCharacters();
this.buffer = previous;
this.term.write(previous);
setTimeout(function(){
if (self.historyIndex < self.history.length - 1) {
self.historyIndex += 1
self.historyCmdtoStdout()
}
}, 0);
}
// if the character is printable and none of the event key codes from above were matched
// then we write the character on the prompt line and add the character to the buffer
else if (printable) {
this.term.write(ev.key);
this.buffer += ev.key;
// press the down key, find next item in cmd history
else if (ev.keyCode === 38) {
setTimeout(function(){
if (self.historyIndex >= 1) {
self.historyIndex -= 1
self.historyCmdtoStdout()
}
}, 0);
}
// this forces xterm not to handle the key events with its own event handler
return false;
}
}

View file

@ -0,0 +1,10 @@
Font license info
## Font Awesome
Copyright (C) 2016 by Dave Gandy
Author: Dave Gandy
License: SIL ()
Homepage: http://fortawesome.github.com/Font-Awesome/

View file

@ -0,0 +1,75 @@
This webfont is generated by http://fontello.com open source project.
================================================================================
Please, note, that you should obey original font licenses, used to make this
webfont pack. Details available in LICENSE.txt file.
- Usually, it's enough to publish content of LICENSE.txt file somewhere on your
site in "About" section.
- If your project is open-source, usually, it will be ok to make LICENSE.txt
file publicly available in your repository.
- Fonts, used in Fontello, don't require a clickable link on your site.
But any kind of additional authors crediting is welcome.
================================================================================
Comments on archive content
---------------------------
- /font/* - fonts in different formats
- /css/* - different kinds of css, for all situations. Should be ok with
twitter bootstrap. Also, you can skip <i> style and assign icon classes
directly to text elements, if you don't mind about IE7.
- demo.html - demo file, to show your webfont content
- LICENSE.txt - license info about source fonts, used to build your one.
- config.json - keeps your settings. You can import it back into fontello
anytime, to continue your work
Why so many CSS files ?
-----------------------
Because we like to fit all your needs :)
- basic file, <your_font_name>.css - is usually enough, it contains @font-face
and character code definitions
- *-ie7.css - if you need IE7 support, but still don't wish to put char codes
directly into html
- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face
rules, but still wish to benefit from css generation. That can be very
convenient for automated asset build systems. When you need to update font -
no need to manually edit files, just override old version with archive
content. See fontello source code for examples.
- *-embedded.css - basic css file, but with embedded WOFF font, to avoid
CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.
We strongly recommend to resolve this issue by `Access-Control-Allow-Origin`
server headers. But if you ok with dirty hack - this file is for you. Note,
that data url moved to separate @font-face to avoid problems with <IE9, when
string is too long.
- animate.css - use it to get ideas about spinner rotation animation.
Attention for server setup
--------------------------
You MUST setup server to reply with proper `mime-types` for font files -
otherwise some browsers will fail to show fonts.
Usually, `apache` already has necessary settings, but `nginx` and other
webservers should be tuned. Here is list of mime types for our file extensions:
- `application/vnd.ms-fontobject` - eot
- `application/x-font-woff` - woff
- `application/x-font-ttf` - ttf
- `image/svg+xml` - svg

View file

@ -0,0 +1,28 @@
{
"name": "font-awesome",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "0f4cae16f34ae243a6144c18a003f2d8",
"css": "cancel-circled",
"code": 59392,
"src": "fontawesome"
},
{
"uid": "94089b37297572e936b0943bcfa041d3",
"css": "angle-circled-right",
"code": 61752,
"src": "fontawesome"
},
{
"uid": "8933c2579166c2ee56ae40dc6a0b4dc6",
"css": "angle-circled-left",
"code": 61751,
"src": "fontawesome"
}
]
}

View file

@ -0,0 +1,85 @@
/*
Animation example, for spinners
*/
.animate-spin {
-moz-animation: spin 2s infinite linear;
-o-animation: spin 2s infinite linear;
-webkit-animation: spin 2s infinite linear;
animation: spin 2s infinite linear;
display: inline-block;
}
@-moz-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-webkit-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-o-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-ms-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}

View file

@ -0,0 +1,4 @@
.icon-cancel-circled:before { content: '\e800'; } /* '' */
.icon-angle-circled-left:before { content: '\f137'; } /* '' */
.icon-angle-circled-right:before { content: '\f138'; } /* '' */

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,4 @@
.icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-angle-circled-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf137;&nbsp;'); }
.icon-angle-circled-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf138;&nbsp;'); }

View file

@ -0,0 +1,15 @@
[class^="icon-"], [class*=" icon-"] {
font-family: 'font-awesome';
font-style: normal;
font-weight: normal;
/* fix buttons height */
line-height: 1em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
}
.icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-angle-circled-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf137;&nbsp;'); }
.icon-angle-circled-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf138;&nbsp;'); }

View file

@ -0,0 +1,60 @@
@font-face {
font-family: 'font-awesome';
src: url('../font/font-awesome.eot?65695272');
src: url('../font/font-awesome.eot?65695272#iefix') format('embedded-opentype'),
url('../font/font-awesome.woff2?65695272') format('woff2'),
url('../font/font-awesome.woff?65695272') format('woff'),
url('../font/font-awesome.ttf?65695272') format('truetype'),
url('../font/font-awesome.svg?65695272#font-awesome') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'font-awesome';
src: url('../font/font-awesome.svg?65695272#font-awesome') format('svg');
}
}
*/
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "font-awesome";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-cancel-circled:before { content: '\e800'; } /* '' */
.icon-angle-circled-left:before { content: '\f137'; } /* '' */
.icon-angle-circled-right:before { content: '\f138'; } /* '' */

View file

@ -0,0 +1,311 @@
<!DOCTYPE html>
<html>
<head><!--[if lt IE 9]><script language="javascript" type="text/javascript" src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
<meta charset="UTF-8"><style>/*
* Bootstrap v2.2.1
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/
.clearfix {
*zoom: 1;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
line-height: 0;
}
.clearfix:after {
clear: both;
}
html {
font-size: 100%;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
a:focus {
outline: thin dotted #333;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
a:hover,
a:active {
outline: 0;
}
button,
input,
select,
textarea {
margin: 0;
font-size: 100%;
vertical-align: middle;
}
button,
input {
*overflow: visible;
line-height: normal;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
padding: 0;
border: 0;
}
body {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
color: #333;
background-color: #fff;
}
a {
color: #08c;
text-decoration: none;
}
a:hover {
color: #005580;
text-decoration: underline;
}
.row {
margin-left: -20px;
*zoom: 1;
}
.row:before,
.row:after {
display: table;
content: "";
line-height: 0;
}
.row:after {
clear: both;
}
[class*="span"] {
float: left;
min-height: 1px;
margin-left: 20px;
}
.container,
.navbar-static-top .container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
width: 940px;
}
.span12 {
width: 940px;
}
.span11 {
width: 860px;
}
.span10 {
width: 780px;
}
.span9 {
width: 700px;
}
.span8 {
width: 620px;
}
.span7 {
width: 540px;
}
.span6 {
width: 460px;
}
.span5 {
width: 380px;
}
.span4 {
width: 300px;
}
.span3 {
width: 220px;
}
.span2 {
width: 140px;
}
.span1 {
width: 60px;
}
[class*="span"].pull-right,
.row-fluid [class*="span"].pull-right {
float: right;
}
.container {
margin-right: auto;
margin-left: auto;
*zoom: 1;
}
.container:before,
.container:after {
display: table;
content: "";
line-height: 0;
}
.container:after {
clear: both;
}
p {
margin: 0 0 10px;
}
.lead {
margin-bottom: 20px;
font-size: 21px;
font-weight: 200;
line-height: 30px;
}
small {
font-size: 85%;
}
h1 {
margin: 10px 0;
font-family: inherit;
font-weight: bold;
line-height: 20px;
color: inherit;
text-rendering: optimizelegibility;
}
h1 small {
font-weight: normal;
line-height: 1;
color: #999;
}
h1 {
line-height: 40px;
}
h1 {
font-size: 38.5px;
}
h1 small {
font-size: 24.5px;
}
body {
margin-top: 90px;
}
.header {
position: fixed;
top: 0;
left: 50%;
margin-left: -480px;
background-color: #fff;
border-bottom: 1px solid #ddd;
padding-top: 10px;
z-index: 10;
}
.footer {
color: #ddd;
font-size: 12px;
text-align: center;
margin-top: 20px;
}
.footer a {
color: #ccc;
text-decoration: underline;
}
.the-icons {
font-size: 14px;
line-height: 24px;
}
.switch {
position: absolute;
right: 0;
bottom: 10px;
color: #666;
}
.switch input {
margin-right: 0.3em;
}
.codesOn .i-name {
display: none;
}
.codesOn .i-code {
display: inline;
}
.i-code {
display: none;
}
@font-face {
font-family: 'font-awesome';
src: url('./font/font-awesome.eot?55546040');
src: url('./font/font-awesome.eot?55546040#iefix') format('embedded-opentype'),
url('./font/font-awesome.woff?55546040') format('woff'),
url('./font/font-awesome.ttf?55546040') format('truetype'),
url('./font/font-awesome.svg?55546040#font-awesome') format('svg');
font-weight: normal;
font-style: normal;
}
.demo-icon
{
font-family: "font-awesome";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* You can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
</style>
<link rel="stylesheet" href="css/animation.css"><!--[if IE 7]><link rel="stylesheet" href="css/font-awesome-ie7.css"><![endif]-->
<script>
function toggleCodes(on) {
var obj = document.getElementById('icons');
if (on) {
obj.className += ' codesOn';
} else {
obj.className = obj.className.replace(' codesOn', '');
}
}
</script>
</head>
<body>
<div class="container header">
<h1>
font-awesome
<small>font demo</small>
</h1>
<label class="switch">
<input type="checkbox" onclick="toggleCodes(this.checked)">show codes
</label>
</div>
<div id="icons" class="container">
<div class="row">
<div title="Code: 0xe800" class="the-icons span3"><i class="demo-icon icon-cancel-circled">&#xe800;</i> <span class="i-name">icon-cancel-circled</span><span class="i-code">0xe800</span></div>
<div title="Code: 0xf137" class="the-icons span3"><i class="demo-icon icon-angle-circled-left">&#xf137;</i> <span class="i-name">icon-angle-circled-left</span><span class="i-code">0xf137</span></div>
<div title="Code: 0xf138" class="the-icons span3"><i class="demo-icon icon-angle-circled-right">&#xf138;</i> <span class="i-name">icon-angle-circled-right</span><span class="i-code">0xf138</span></div>
</div>
</div>
<div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div>
</body>
</html>

Binary file not shown.

View file

@ -0,0 +1,16 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata>
<defs>
<font id="font-awesome" horiz-adv-x="1000" >
<font-face font-family="font-awesome" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="cancel-circled" unicode="&#xe800;" d="M641 224q0 14-10 25l-101 101 101 101q10 11 10 25 0 15-10 26l-51 50q-10 11-25 11-15 0-25-11l-101-101-101 101q-11 11-25 11-16 0-26-11l-50-50q-11-11-11-26 0-14 11-25l101-101-101-101q-11-11-11-25 0-15 11-26l50-50q10-11 26-11 14 0 25 11l101 101 101-101q10-11 25-11 15 0 25 11l51 50q10 11 10 26z m216 126q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="angle-circled-left" unicode="&#xf137;" d="M507 72l57 57q11 10 11 25t-11 25l-171 171 171 171q11 11 11 25t-11 26l-57 57q-10 10-25 10t-25-10l-253-254q-11-10-11-25t11-25l253-253q11-11 25-11t25 11z m350 278q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="angle-circled-right" unicode="&#xf138;" d="M400 72l254 253q10 11 10 25t-10 25l-254 254q-10 10-25 10t-25-10l-57-57q-11-11-11-26t11-25l171-171-171-171q-11-11-11-25t11-25l57-57q11-11 25-11t25 11z m457 278q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,7 +1,6 @@
commands:
- ls
- cat README.md
- inspec help supermarket
- inspec help compliance
- inspec version
- inspec detect --format json
@ -9,6 +8,3 @@ commands:
- inspec exec examples/profile --format json | jq
- inspec json examples/profile
- inspec archive examples/profile
- inspec env
- inspec exec examples/inheritance
- inspec exec test/unit/mock/profiles/failures

View file

@ -22,7 +22,6 @@ gulp.task('copy', function(){
'app/**/*.css',
'app/**/*.html',
'tutorial_files/**',
'node_modules/shellwords/src/shellwords.coffee',
])
.pipe(gcopy('dist/'));
})

View file

@ -1,20 +1,18 @@
<html>
<head>
<title>InSpec Demo</title>
<title>InSpec Interactive Tutorial</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- build:css css/combined.css -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="node_modules/xterm/src/xterm.css">
<link rel="stylesheet" href="assets/font-awesome/css/font-awesome.css">
<!-- endbuild -->
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">
</head>
<body>
<!-- FIXME I need compilation and integration vv -->
<script src="node_modules/shellwords/src/shellwords.coffee"></script>
<!-- build:js scripts/combined.js -->
<script src="node_modules/xterm/src/xterm.js"></script>
<script src="node_modules/xterm/addons/fit/fit.js"></script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -3,8 +3,8 @@
"version": "1.0.0",
"scripts": {
"build": "node --max-old-space-size=4096 ./node_modules/webpack/bin/webpack --config config/webpack.config.js --progress --profile --colors --display-error-details --display-cached",
"start": "node --max-old-space-size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server --config config/webpack.config.js & open http://localhost:8080/",
"dev": "node --max-old-space-size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server --config config/webpack.config.js --inline --progress --hot --profile --colors --watch --display-error-details --display-cachedwebpack-dev-server & open http://localhost:8080/"
"start": "node --max-old-space-size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server --config config/webpack.config.js",
"dev": "node --max-old-space-size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server --config config/webpack.config.js --inline --progress --hot --profile --colors --watch --display-error-details --display-cachedwebpack-dev-server"
},
"license": "ISC",
"dependencies": {
@ -23,7 +23,6 @@
"core-js": "^2.4.0",
"reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-beta.6",
"shellwords": "^0.1.0",
"systemjs": "0.19.27",
"webpack": "^1.13.2",
"webpack-dev-server": "^1.15.1",

View file

@ -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)