mirror of
https://github.com/inspec/inspec
synced 2025-02-17 06:28:40 +00:00
Merge pull request #1101 from chef/chris-rock/optimize-tutorial
Optimize tutorial
This commit is contained in:
commit
25424e2d81
52 changed files with 1256 additions and 556 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 = '[36m$[0m ';
|
||||
const INSPEC_PROMPT = '[0;32minspec> [0m';
|
||||
|
||||
@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 = "[31m";
|
||||
white: string = "[37m";
|
||||
black: string = "[30m";
|
||||
|
||||
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: [1mhelp[0m\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
|
@ -1,5 +1 @@
|
|||
[0;32minspec> [0mcommand('uname -a').stdout
|
||||
Welcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G[0;32minspec> [0m=> [31m[1;31m"[0m[31mLinux 2e958a7a89d6 4.4.19-moby #1 SMP Mon Aug 22 23:30:19 UTC 2016 x86_64 Linux[1;35m\n[0m[31m[1;31m"[0m[31m[0m
|
||||
[0G[0;32minspec> [0m=> [31m[1;31m"[0m[31mLinux 20b81abc543e 4.4.20-moby #1 SMP Thu Sep 8 21:27:34 UTC 2016 x86_64 Linux[1;35m\n[0m[31m[1;31m"[0m[31m[0m
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
[0;32minspec> [0mcontrol "id" do
|
||||
[0;32minspec> [0m title "Check permissions on /root!"
|
||||
[0;32minspec> [0m impact 0.5
|
||||
[0;32minspec> [0m describe file('/root') do
|
||||
[0;32minspec> [0m its('mode') { should cmp '0750'}
|
||||
[0;32minspec> [0m end
|
||||
[0;32minspec> [0m end
|
||||
Welcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G[0;32minspec> [0m[31m ✖ id: File /root mode should cmp "0750" (
|
||||
[0G[0;32minspec> [0m[31m ✖ id: Check permissions on /root! (
|
||||
expected: "0750"
|
||||
got: 0700
|
||||
|
||||
|
@ -16,4 +6,9 @@ To find out how to use it, type: [1mhelp[0m
|
|||
)[0m
|
||||
|
||||
|
||||
Summary: [32m0 successful[0m, [31m1 failures[0m, [37m0 skipped[0m
|
||||
|
||||
Profile: inspec-shell
|
||||
Version: unknown
|
||||
|
||||
|
||||
Profile Summary: [32m0 successful[0m, [31m1 failures[0m, [37m0 skipped[0m
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
[0;32minspec> [0mdescribe file('/root') do
|
||||
[0;32minspec> [0m it { should exist }
|
||||
[0;32minspec> [0m its('mode') { should cmp '0750'}
|
||||
[0;32minspec> [0m end
|
||||
Welcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G[0;32minspec> [0m
|
||||
File /root
|
||||
[32m ✔ should exist[0m
|
||||
[31m ✖ mode should cmp "0750"
|
||||
|
||||
expected: "0750"
|
||||
got: 0700
|
||||
|
||||
(compared using `cmp` matcher)
|
||||
[0m
|
||||
|
||||
Summary: [32m1 successful[0m, [31m1 failures[0m, [37m0 skipped[0m
|
||||
|
||||
Profile: inspec-shell
|
||||
Version: unknown
|
||||
|
||||
|
||||
Test Summary: [32m1 successful[0m, [31m1 failures[0m, [37m0 skipped[0m
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
[0;32minspec> [0mfile('/proc/cpuinfo').owner
|
||||
Welcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G[0;32minspec> [0m=> [31m[1;31m"[0m[31mroot[1;31m"[0m[31m[0m
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
[0;32minspec> [0mhelp command
|
||||
[0;32minspec> [0mWelcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G[1mName:[0m command
|
||||
|
||||
[1mDescription:[0m
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
[0;32minspec> [0mhelp file
|
||||
[0;32minspec> [0mWelcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G[1mName:[0m file
|
||||
|
||||
[1mDescription:[0m
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
[0;32minspec> [0mhelp
|
||||
[0;32minspec> [0mWelcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G
|
||||
Available commands:
|
||||
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
[0;32minspec> [0mhelp os
|
||||
[0;32minspec> [0mWelcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G[1mName:[0m os
|
||||
|
||||
[1mDescription:[0m
|
||||
|
@ -10,7 +6,7 @@ Use the os InSpec audit resource to test the platform on which the system is run
|
|||
|
||||
[1mExample:[0m
|
||||
|
||||
describe os.family do
|
||||
describe os[:family] do
|
||||
it { should eq 'redhat' }
|
||||
end
|
||||
describe os.redhat? do
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
[0;32minspec> [0mhelp resources
|
||||
[0;32minspec> [0mWelcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0Gapache 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
|
||||
|
|
|
@ -1,6 +1,2 @@
|
|||
[0;32minspec> [0msshd_config.params
|
||||
Welcome to the interactive InSpec Shell
|
||||
To find out how to use it, type: [1mhelp[0m
|
||||
|
||||
[0G[0;32minspec> [0m=> {[31m[1;31m"[0m[31mauthorizedkeysfile[1;31m"[0m[31m[0m=>[[31m[1;31m"[0m[31m.ssh/authorized_keys[1;31m"[0m[31m[0m],
|
||||
[31m[1;31m"[0m[31msubsystem[1;31m"[0m[31m[0m=>[[31m[1;31m"[0m[31msftp[1;35m\t[0m[31m/usr/lib/ssh/sftp-server[1;31m"[0m[31m[0m]}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Location: [0;36mexamples/profile[0m
|
||||
Profile: [0;36mprofile[0m
|
||||
Controls: [0;36m4[0m
|
||||
Timestamp: [0;36m2016-09-16T13:59:27+00:00[0m
|
||||
Timestamp: [0;36m2016-09-21T20:13:37+00:00[0m
|
||||
Valid: [0;36mtrue[0m
|
||||
|
||||
No errors or warnings
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: local://
|
||||
|
||||
[32m ✔ tmp-1.0: Create /tmp directory[0m
|
||||
[32m ✔ File /tmp should be directory[0m
|
||||
[37m ○ gordon-1.0: Verify the version number of Gordon (1 skipped)[0m
|
||||
|
@ -14,4 +9,11 @@ Target: local://
|
|||
File /tmp
|
||||
[32m ✔ should be directory[0m
|
||||
|
||||
Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: local://
|
||||
|
||||
|
||||
Profile Summary: [32m2 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
Test Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: ssh://bob@host.node:
|
||||
|
||||
[32m ✔ tmp-1.0: Create /tmp directory[0m
|
||||
[32m ✔ File /tmp should be directory[0m
|
||||
[37m ○ gordon-1.0: Verify the version number of Gordon (1 skipped)[0m
|
||||
|
@ -14,4 +9,11 @@ Target: ssh://bob@host.node:
|
|||
File /tmp
|
||||
[32m ✔ should be directory[0m
|
||||
|
||||
Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: ssh://bob@host.node:
|
||||
|
||||
|
||||
Profile Summary: [32m2 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
Test Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,3 @@
|
|||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: docker://abcdef123
|
||||
|
||||
[32m ✔ tmp-1.0: Create /tmp directory[0m
|
||||
[32m ✔ File /tmp should be directory[0m
|
||||
[37m ○ gordon-1.0: Verify the version number of Gordon (1 skipped)[0m
|
||||
|
@ -14,4 +9,11 @@ Target: docker://abcdef123
|
|||
File /tmp
|
||||
[32m ✔ should be directory[0m
|
||||
|
||||
Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: docker://abcdef123
|
||||
|
||||
|
||||
Profile Summary: [32m2 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
Test Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: ssh://bob@host.node:
|
||||
|
||||
[32m ✔ tmp-1.0: Create /tmp directory[0m
|
||||
[32m ✔ File /tmp should be directory[0m
|
||||
[37m ○ gordon-1.0: Verify the version number of Gordon (1 skipped)[0m
|
||||
|
@ -14,4 +9,11 @@ Target: ssh://bob@host.node:
|
|||
File /tmp
|
||||
[32m ✔ should be directory[0m
|
||||
|
||||
Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: ssh://bob@host.node:
|
||||
|
||||
|
||||
Profile Summary: [32m2 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
Test Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: winrm://alice@windows.node:
|
||||
|
||||
[32m ✔ tmp-1.0: Create /tmp directory[0m
|
||||
[32m ✔ File /tmp should be directory[0m
|
||||
[37m ○ gordon-1.0: Verify the version number of Gordon (1 skipped)[0m
|
||||
|
@ -14,4 +9,11 @@ Target: winrm://alice@windows.node:
|
|||
File /tmp
|
||||
[32m ✔ should be directory[0m
|
||||
|
||||
Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: winrm://alice@windows.node:
|
||||
|
||||
|
||||
Profile Summary: [32m2 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
Test Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: winrm://alice@windows.node:
|
||||
|
||||
[32m ✔ tmp-1.0: Create /tmp directory[0m
|
||||
[32m ✔ File /tmp should be directory[0m
|
||||
[37m ○ gordon-1.0: Verify the version number of Gordon (1 skipped)[0m
|
||||
|
@ -14,4 +9,11 @@ Target: winrm://alice@windows.node:
|
|||
File /tmp
|
||||
[32m ✔ should be directory[0m
|
||||
|
||||
Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
||||
Profile: InSpec Example Profile (profile)
|
||||
Version: 1.0.0
|
||||
Target: winrm://alice@windows.node:
|
||||
|
||||
|
||||
Profile Summary: [32m2 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
Test Summary: [32m4 successful[0m, [31m0 failures[0m, [37m1 skipped[0m
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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":[]}
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.35.0
|
||||
1.0.0-beta1
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<div (keyup)="onKey($event)" (keydown)="handleDelete($event)" id="terminal-container"></div>
|
||||
<div id="terminal-container" (window:resize)="onResize($event)"></div>
|
||||
|
|
|
@ -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(' [32minspec>*'); // 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(' [32minspec> '); // green inspec shell prompt
|
||||
}
|
||||
} else {
|
||||
this.term.write('[36m$ '); // 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;
|
||||
}
|
||||
}
|
||||
|
|
10
www/tutorial/assets/font-awesome/LICENSE.txt
Executable file
10
www/tutorial/assets/font-awesome/LICENSE.txt
Executable 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/
|
75
www/tutorial/assets/font-awesome/README.txt
Executable file
75
www/tutorial/assets/font-awesome/README.txt
Executable 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
|
28
www/tutorial/assets/font-awesome/config.json
Executable file
28
www/tutorial/assets/font-awesome/config.json
Executable 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"
|
||||
}
|
||||
]
|
||||
}
|
85
www/tutorial/assets/font-awesome/css/animation.css
vendored
Executable file
85
www/tutorial/assets/font-awesome/css/animation.css
vendored
Executable 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);
|
||||
}
|
||||
}
|
4
www/tutorial/assets/font-awesome/css/font-awesome-codes.css
vendored
Executable file
4
www/tutorial/assets/font-awesome/css/font-awesome-codes.css
vendored
Executable 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'; } /* '' */
|
57
www/tutorial/assets/font-awesome/css/font-awesome-embedded.css
vendored
Executable file
57
www/tutorial/assets/font-awesome/css/font-awesome-embedded.css
vendored
Executable file
File diff suppressed because one or more lines are too long
4
www/tutorial/assets/font-awesome/css/font-awesome-ie7-codes.css
vendored
Executable file
4
www/tutorial/assets/font-awesome/css/font-awesome-ie7-codes.css
vendored
Executable file
|
@ -0,0 +1,4 @@
|
|||
|
||||
.icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-angle-circled-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-angle-circled-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
15
www/tutorial/assets/font-awesome/css/font-awesome-ie7.css
vendored
Executable file
15
www/tutorial/assets/font-awesome/css/font-awesome-ie7.css
vendored
Executable 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 = ' '); }
|
||||
.icon-angle-circled-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||
.icon-angle-circled-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
60
www/tutorial/assets/font-awesome/css/font-awesome.css
vendored
Executable file
60
www/tutorial/assets/font-awesome/css/font-awesome.css
vendored
Executable 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'; } /* '' */
|
311
www/tutorial/assets/font-awesome/demo.html
Executable file
311
www/tutorial/assets/font-awesome/demo.html
Executable 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"></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"></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"></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>
|
BIN
www/tutorial/assets/font-awesome/font/font-awesome.eot
Executable file
BIN
www/tutorial/assets/font-awesome/font/font-awesome.eot
Executable file
Binary file not shown.
16
www/tutorial/assets/font-awesome/font/font-awesome.svg
Executable file
16
www/tutorial/assets/font-awesome/font/font-awesome.svg
Executable 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="" 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="" 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="" 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 |
BIN
www/tutorial/assets/font-awesome/font/font-awesome.ttf
Executable file
BIN
www/tutorial/assets/font-awesome/font/font-awesome.ttf
Executable file
Binary file not shown.
BIN
www/tutorial/assets/font-awesome/font/font-awesome.woff
Executable file
BIN
www/tutorial/assets/font-awesome/font/font-awesome.woff
Executable file
Binary file not shown.
BIN
www/tutorial/assets/font-awesome/font/font-awesome.woff2
Executable file
BIN
www/tutorial/assets/font-awesome/font/font-awesome.woff2
Executable file
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -22,7 +22,6 @@ gulp.task('copy', function(){
|
|||
'app/**/*.css',
|
||||
'app/**/*.html',
|
||||
'tutorial_files/**',
|
||||
'node_modules/shellwords/src/shellwords.coffee',
|
||||
])
|
||||
.pipe(gcopy('dist/'));
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
|
|
BIN
www/tutorial/inspec-logo-white.png
Normal file
BIN
www/tutorial/inspec-logo-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
|
@ -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",
|
||||
|
|
|
@ -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: [1mhelp[0m\n\n"
|
||||
# To find out how to use it, type: [1mhelp[0m
|
||||
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)
|
||||
|
|
Loading…
Add table
Reference in a new issue