mirror of
https://github.com/strongdm/comply
synced 2024-11-25 00:54:54 +00:00
the data model for Narratives and Policies have converged, allowing both to be represented by a common Docume
nt struct.
This commit is contained in:
parent
ff626a5ee2
commit
815e7e5f61
@ -91,8 +91,8 @@ func ReadStandards() ([]*Standard, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadNarratives loads narrative descriptions from the filesystem.
|
// ReadNarratives loads narrative descriptions from the filesystem.
|
||||||
func ReadNarratives() ([]*Narrative, error) {
|
func ReadNarratives() ([]*Document, error) {
|
||||||
var narratives []*Narrative
|
var narratives []*Document
|
||||||
|
|
||||||
files, err := path.Narratives()
|
files, err := path.Narratives()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,7 +100,7 @@ func ReadNarratives() ([]*Narrative, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
n := &Narrative{}
|
n := &Document{}
|
||||||
mdmd := loadMDMD(f.FullPath)
|
mdmd := loadMDMD(f.FullPath)
|
||||||
err = yaml.Unmarshal([]byte(mdmd.yaml), &n)
|
err = yaml.Unmarshal([]byte(mdmd.yaml), &n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -141,8 +141,8 @@ func ReadProcedures() ([]*Procedure, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadPolicies loads policy documents from the filesystem.
|
// ReadPolicies loads policy documents from the filesystem.
|
||||||
func ReadPolicies() ([]*Policy, error) {
|
func ReadPolicies() ([]*Document, error) {
|
||||||
var policies []*Policy
|
var policies []*Document
|
||||||
|
|
||||||
files, err := path.Policies()
|
files, err := path.Policies()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -150,7 +150,7 @@ func ReadPolicies() ([]*Policy, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
p := &Policy{}
|
p := &Document{}
|
||||||
mdmd := loadMDMD(f.FullPath)
|
mdmd := loadMDMD(f.FullPath)
|
||||||
err = yaml.Unmarshal([]byte(mdmd.yaml), &p)
|
err = yaml.Unmarshal([]byte(mdmd.yaml), &p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,8 +2,8 @@ package model
|
|||||||
|
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Standards []*Standard
|
Standards []*Standard
|
||||||
Narratives []*Narrative
|
Narratives []*Document
|
||||||
Policies []*Policy
|
Policies []*Document
|
||||||
Procedures []*Procedure
|
Procedures []*Procedure
|
||||||
Tickets []*Ticket
|
Tickets []*Ticket
|
||||||
Audits []*Audit
|
Audits []*Audit
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type Narrative struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Acronym string `yaml:"acronym"`
|
|
||||||
|
|
||||||
Revisions []Revision `yaml:"majorRevisions"`
|
|
||||||
Satisfies Satisfaction `yaml:"satisfies"`
|
|
||||||
FullPath string
|
|
||||||
OutputFilename string
|
|
||||||
ModifiedAt time.Time
|
|
||||||
Body string
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type Policy struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Acronym string `yaml:"acronym"`
|
|
||||||
|
|
||||||
Revisions []Revision `yaml:"majorRevisions"`
|
|
||||||
Satisfies Satisfaction `yaml:"satisfies"`
|
|
||||||
FullPath string
|
|
||||||
OutputFilename string
|
|
||||||
ModifiedAt time.Time
|
|
||||||
Body string
|
|
||||||
}
|
|
@ -32,8 +32,8 @@ type renderData struct {
|
|||||||
Name string
|
Name string
|
||||||
Project *project
|
Project *project
|
||||||
Stats *stats
|
Stats *stats
|
||||||
Narratives []*model.Narrative
|
Narratives []*model.Document
|
||||||
Policies []*model.Policy
|
Policies []*model.Document
|
||||||
Procedures []*model.Procedure
|
Procedures []*model.Procedure
|
||||||
Standards []*model.Standard
|
Standards []*model.Standard
|
||||||
Tickets []*model.Ticket
|
Tickets []*model.Ticket
|
||||||
|
@ -17,20 +17,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// TODO: refactor and eliminate duplication among narrative, policy renderers
|
// TODO: refactor and eliminate duplication among narrative, policy renderers
|
||||||
func renderPolicyToDisk(wg *sync.WaitGroup, errOutputCh chan error, data *renderData, policy *model.Policy, live bool) {
|
func renderToFilesystem(wg *sync.WaitGroup, errOutputCh chan error, data *renderData, doc *model.Document, live bool) {
|
||||||
// only files that have been touched
|
// only files that have been touched
|
||||||
if !isNewer(policy.FullPath, policy.ModifiedAt) {
|
if !isNewer(doc.FullPath, doc.ModifiedAt) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
recordModified(policy.FullPath, policy.ModifiedAt)
|
recordModified(doc.FullPath, doc.ModifiedAt)
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(p *model.Policy) {
|
go func(p *model.Document) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
outputFilename := p.OutputFilename
|
outputFilename := p.OutputFilename
|
||||||
// save preprocessed markdown
|
// save preprocessed markdown
|
||||||
err := preprocessPolicy(data, p, filepath.Join(".", "output", outputFilename+".md"))
|
err := preprocessDoc(data, p, filepath.Join(".", "output", outputFilename+".md"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errOutputCh <- errors.Wrap(err, "unable to preprocess")
|
errOutputCh <- errors.Wrap(err, "unable to preprocess")
|
||||||
return
|
return
|
||||||
@ -50,10 +50,10 @@ func renderPolicyToDisk(wg *sync.WaitGroup, errOutputCh chan error, data *render
|
|||||||
rel = p.FullPath
|
rel = p.FullPath
|
||||||
}
|
}
|
||||||
fmt.Printf("%s -> %s\n", rel, filepath.Join("output", p.OutputFilename))
|
fmt.Printf("%s -> %s\n", rel, filepath.Join("output", p.OutputFilename))
|
||||||
}(policy)
|
}(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func preprocessPolicy(data *renderData, pol *model.Policy, fullPath string) error {
|
func preprocessDoc(data *renderData, pol *model.Document, fullPath string) error {
|
||||||
cfg := config.Config()
|
cfg := config.Config()
|
||||||
|
|
||||||
var w bytes.Buffer
|
var w bytes.Buffer
|
@ -1,124 +0,0 @@
|
|||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/strongdm/comply/internal/config"
|
|
||||||
"github.com/strongdm/comply/internal/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: refactor and eliminate duplication among narrative, policy renderers
|
|
||||||
func renderNarrativeToDisk(wg *sync.WaitGroup, errOutputCh chan error, data *renderData, narrative *model.Narrative, live bool) {
|
|
||||||
// only files that have been touched
|
|
||||||
if !isNewer(narrative.FullPath, narrative.ModifiedAt) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
recordModified(narrative.FullPath, narrative.ModifiedAt)
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func(p *model.Narrative) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
outputFilename := p.OutputFilename
|
|
||||||
// save preprocessed markdown
|
|
||||||
err := preprocessNarrative(data, p, filepath.Join(".", "output", outputFilename+".md"))
|
|
||||||
if err != nil {
|
|
||||||
errOutputCh <- errors.Wrap(err, "unable to preprocess")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pandoc(outputFilename, errOutputCh)
|
|
||||||
|
|
||||||
// remove preprocessed markdown
|
|
||||||
err = os.Remove(filepath.Join(".", "output", outputFilename+".md"))
|
|
||||||
if err != nil {
|
|
||||||
errOutputCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
}(narrative)
|
|
||||||
}
|
|
||||||
|
|
||||||
func preprocessNarrative(data *renderData, pol *model.Narrative, fullPath string) error {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
doc := fmt.Sprintf(`%% %s
|
|
||||||
%% %s
|
|
||||||
%% %s
|
|
||||||
|
|
||||||
---
|
|
||||||
header-includes: yes
|
|
||||||
head-content: "%s"
|
|
||||||
foot-content: "%s confidential %d"
|
|
||||||
---
|
|
||||||
|
|
||||||
%s
|
|
||||||
|
|
||||||
%s
|
|
||||||
|
|
||||||
\newpage
|
|
||||||
%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,
|
|
||||||
)
|
|
||||||
err = ioutil.WriteFile(fullPath, []byte(doc), os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to write preprocessed narrative to disk")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -25,7 +25,7 @@ func pdf(output string, live bool, errCh chan error, wg *sync.WaitGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
renderPolicyToDisk(&pdfWG, errOutputCh, data, policy, live)
|
renderToFilesystem(&pdfWG, errOutputCh, data, policy, live)
|
||||||
}
|
}
|
||||||
|
|
||||||
narratives, err := model.ReadNarratives()
|
narratives, err := model.ReadNarratives()
|
||||||
@ -35,7 +35,7 @@ func pdf(output string, live bool, errCh chan error, wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, narrative := range narratives {
|
for _, narrative := range narratives {
|
||||||
renderNarrativeToDisk(&pdfWG, errOutputCh, data, narrative, live)
|
renderToFilesystem(&pdfWG, errOutputCh, data, narrative, live)
|
||||||
}
|
}
|
||||||
|
|
||||||
pdfWG.Wait()
|
pdfWG.Wait()
|
||||||
|
Loading…
Reference in New Issue
Block a user