2018-05-15 21:13:11 +00:00
package render
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"text/template"
"time"
2018-08-29 22:47:02 +00:00
"os/exec"
2018-05-15 21:13:11 +00:00
"github.com/pkg/errors"
"github.com/strongdm/comply/internal/config"
"github.com/strongdm/comply/internal/model"
)
2021-08-03 22:19:24 +00:00
// Pull array of file extensions from config
// for each pdf passed in, iterate other extensions and pass to pandoc.
// This way, all types will be rendered BEFORE the system deletes the markdown preprocessed file
2018-05-15 21:13:11 +00:00
// TODO: refactor and eliminate duplication among narrative, policy renderers
2018-05-28 22:05:56 +00:00
func renderToFilesystem ( wg * sync . WaitGroup , errOutputCh chan error , data * renderData , doc * model . Document , live bool ) {
2018-05-15 21:13:11 +00:00
// only files that have been touched
2018-05-28 22:05:56 +00:00
if ! isNewer ( doc . FullPath , doc . ModifiedAt ) {
2018-05-15 21:13:11 +00:00
return
}
2018-05-28 22:05:56 +00:00
recordModified ( doc . FullPath , doc . ModifiedAt )
2018-05-15 21:13:11 +00:00
wg . Add ( 1 )
2018-05-28 22:05:56 +00:00
go func ( p * model . Document ) {
2018-05-15 21:13:11 +00:00
defer wg . Done ( )
outputFilename := p . OutputFilename
2021-08-03 22:19:24 +00:00
2018-05-15 21:13:11 +00:00
// save preprocessed markdown
2018-05-28 22:05:56 +00:00
err := preprocessDoc ( data , p , filepath . Join ( "." , "output" , outputFilename + ".md" ) )
2018-05-15 21:13:11 +00:00
if err != nil {
errOutputCh <- errors . Wrap ( err , "unable to preprocess" )
return
}
2021-08-03 22:19:24 +00:00
// get list of output file types from config
fileExtensions := config . GetFileExtensions ( )
for i := range fileExtensions {
// pass file extension and let pandoc handle output format
pandoc ( outputFilename , fileExtensions [ i ] , errOutputCh )
rel , err := filepath . Rel ( config . ProjectRoot ( ) , p . FullPath )
if err != nil {
rel = p . FullPath
}
fmt . Printf ( "%s -> %s\n" , rel , filepath . Join ( "output" , p . OutputFilename + fileExtensions [ i ] ) )
}
2018-05-15 21:13:11 +00:00
// remove preprocessed markdown
err = os . Remove ( filepath . Join ( "." , "output" , outputFilename + ".md" ) )
if err != nil {
errOutputCh <- err
return
}
2018-05-18 05:52:39 +00:00
2018-05-28 22:05:56 +00:00
} ( doc )
2018-05-15 21:13:11 +00:00
}
2018-08-29 22:39:50 +00:00
func getGitApprovalInfo ( pol * model . Document ) ( string , error ) {
cfg := config . Config ( )
2018-08-29 22:47:02 +00:00
// if no approved branch specified in config.yaml, then nothing gets added to the document
if cfg . ApprovedBranch == "" {
return "" , nil
}
2018-08-29 22:39:50 +00:00
// Decide whether we are on the git branch that contains the approved policies
2018-08-29 22:47:02 +00:00
gitBranchArgs := [ ] string { "rev-parse" , "--abbrev-ref" , "HEAD" }
2018-08-29 22:39:50 +00:00
gitBranchCmd := exec . Command ( "git" , gitBranchArgs ... )
gitBranchInfo , err := gitBranchCmd . CombinedOutput ( )
if err != nil {
fmt . Println ( string ( gitBranchInfo ) )
return "" , errors . Wrap ( err , "error looking up git branch" )
}
// if on a different branch than the approved branch, then nothing gets added to the document
2018-08-29 22:47:02 +00:00
if strings . Compare ( strings . TrimSpace ( fmt . Sprintf ( "%s" , gitBranchInfo ) ) , cfg . ApprovedBranch ) != 0 {
2018-08-29 22:39:50 +00:00
return "" , nil
}
// Grab information related to commit, so that we can put approval information in the document
2018-08-29 22:47:02 +00:00
gitArgs := [ ] string { "log" , "-n" , "1" , "--pretty=format:Last edit made by %an (%aE) on %aD.\n\nApproved by %cn (%cE) on %cD in commit %H." , "--" , pol . FullPath }
2018-08-29 22:39:50 +00:00
cmd := exec . Command ( "git" , gitArgs ... )
gitApprovalInfo , err := cmd . CombinedOutput ( )
if err != nil {
fmt . Println ( string ( gitApprovalInfo ) )
return "" , errors . Wrap ( err , "error looking up git committer and author data" )
}
2018-08-29 22:47:02 +00:00
return fmt . Sprintf ( "%s\n%s" , "# Authorship and Approval" , gitApprovalInfo ) , nil
2018-08-29 22:39:50 +00:00
}
2018-05-28 22:05:56 +00:00
func preprocessDoc ( data * renderData , pol * model . Document , fullPath string ) error {
2018-05-15 21:13:11 +00:00
cfg := config . Config ( )
var w bytes . Buffer
bodyTemplate , err := template . New ( "body" ) . Parse ( pol . Body )
if err != nil {
w . WriteString ( fmt . Sprintf ( "# Error processing template:\n\n%s\n" , err . Error ( ) ) )
} else {
bodyTemplate . Execute ( & w , data )
}
body := w . String ( )
revisionTable := ""
satisfiesTable := ""
// ||Date|Comment|
// |---+------|
// | 4 Jan 2018 | Initial Version |
// Table: Document history
if len ( pol . Satisfies ) > 0 {
rows := ""
for standard , keys := range pol . Satisfies {
rows += fmt . Sprintf ( "| %s | %s |\n" , standard , strings . Join ( keys , ", " ) )
}
satisfiesTable = fmt . Sprintf ( "|Standard|Controls Satisfied|\n|-------+--------------------------------------------|\n%s\nTable: Control satisfaction\n" , rows )
}
if len ( pol . Revisions ) > 0 {
rows := ""
for _ , rev := range pol . Revisions {
rows += fmt . Sprintf ( "| %s | %s |\n" , rev . Date , rev . Comment )
}
revisionTable = fmt . Sprintf ( "|Date|Comment|\n|---+--------------------------------------------|\n%s\nTable: Document history\n" , rows )
}
2018-08-29 22:39:50 +00:00
gitApprovalInfo , err := getGitApprovalInfo ( pol )
if err != nil {
return err
}
2018-05-15 21:13:11 +00:00
doc := fmt . Sprintf ( ` % % % s
% % % s
% % % s
-- -
header - includes : yes
head - content : "%s"
foot - content : "%s confidential %d"
-- -
% s
% s
\ newpage
2018-08-29 22:39:50 +00:00
% s
2018-05-15 21:13:11 +00:00
% s ` ,
pol . Name ,
cfg . Name ,
fmt . Sprintf ( "%s %d" , pol . ModifiedAt . Month ( ) . String ( ) , pol . ModifiedAt . Year ( ) ) ,
pol . Name ,
cfg . Name ,
time . Now ( ) . Year ( ) ,
satisfiesTable ,
revisionTable ,
body ,
2018-08-29 22:39:50 +00:00
gitApprovalInfo ,
2018-05-15 21:13:11 +00:00
)
err = ioutil . WriteFile ( fullPath , [ ] byte ( doc ) , os . FileMode ( 0644 ) )
if err != nil {
return errors . Wrap ( err , "unable to write preprocessed policy to disk" )
}
return nil
}