Skip to content

Commit

Permalink
Add export option
Browse files Browse the repository at this point in the history
- Fixes #267
  • Loading branch information
redhatrises committed Jun 11, 2018
1 parent 57dff90 commit e937da2
Show file tree
Hide file tree
Showing 16 changed files with 1,004 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/masonry/masonry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Usage:
Available Commands:
diff Compliance Diff Gap Analysis
docs Create compliance documentation
export Export to consolidated output
get Install compliance dependencies
help Help about any command
Expand Down
86 changes: 86 additions & 0 deletions pkg/cli/export/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package export

import (
"fmt"
"io"

"github.com/opencontrol/compliance-masonry/pkg/cli/clierrors"
"github.com/opencontrol/compliance-masonry/tools/constants"
"github.com/spf13/cobra"
)

// docxtemplater boolean variable
var docxtemplater bool

// flatten boolean variable
var flattenFlag bool

// keys boolen flag
var keysFlag bool

// NewCmdExport exports to consolidated output
func NewCmdExport(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "export",
Short: "Export to consolidated output",
Run: func(cmd *cobra.Command, args []string) {
err := RunExport(out, cmd, args)
clierrors.CheckError(err)
},
}
cmd.Flags().StringP("opencontrol", "o", constants.DefaultDestination, "Set opencontrol directory")
cmd.Flags().StringP("dest", "d", constants.DefaultJSONFile, "Destination file for output")
cmd.Flags().BoolVarP(&flattenFlag, "flatten", "n", false, "Flatten results file")
cmd.Flags().StringP("format", "f", constants.DefaultOutputFormat, "Output format for destination file")
cmd.Flags().BoolVarP(&keysFlag, "keys", "k", false, "Keys to use when processing arrays while flattening")
cmd.Flags().BoolVarP(&docxtemplater, "docxtemplater", "x", false, "Use docxtemplater format")
cmd.Flags().StringP("separator", "s", constants.DefaultKeySeparator, "Separator to use when flattening keys")
return cmd
}

// RunExport runs export when specified in cli
func RunExport(out io.Writer, cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("certification type not specified")
}

// read parms
parmOpencontrols := cmd.Flag("opencontrols").Value.String()
parmDestination := cmd.Flag("destination").Value.String()
parmOutputFormat := cmd.Flag("format").Value.String()
parmFlatten := false
parmInferKeys := false
parmDocxtemplater := false
parmKeySeparator := cmd.Flag("separator").Value.String()

// convert to enum
outputFormat, err := ToOutputFormat(parmOutputFormat)
if err != nil {
return clierrors.NewExitError(err.Error(), 1)
}

// --docxtemplater always forces --flatten
if parmDocxtemplater {
parmFlatten = true
}

// construct args
config := Config{
Certification: args[0],
OpencontrolDir: parmOpencontrols,
DestinationFile: parmDestination,
OutputFormat: outputFormat,
Flatten: parmFlatten,
InferKeys: parmInferKeys,
Docxtemplater: parmDocxtemplater,
KeySeparator: parmKeySeparator,
}

// invoke command
errs := Export(config)
if errs != nil && len(errs) > 0 {
err := clierrors.NewMultiError(errs...)
return clierrors.NewExitError(err.Error(), 1)
}
return nil
}
157 changes: 157 additions & 0 deletions pkg/cli/export/exportFormat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package export

import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"gopkg.in/yaml.v2"
"io"
"log"
"os"
"strings"

"github.com/opencontrol/compliance-masonry/pkg/lib"
"github.com/opencontrol/compliance-masonry/pkg/lib/common"
"github.com/opencontrol/compliance-masonry/tools/certifications"
)

////////////////////////////////////////////////////////////////////////
// Package functions

// exportJSON - JSON output
func exportJSON(config *Config, workspace common.Workspace, output *exportOutput, writer io.Writer) []error {
// result
var errors []error

// work vars
var byteSlice []byte
var err error

// do the work
byteSlice, err = json.Marshal(output)
if err != nil {
return returnErrors(err)
}

// flatten output?
if config.Flatten {
log.Println("JSON: Flatten")

// decode json first
mapped := map[string]interface{}{}
err = json.Unmarshal(byteSlice, &mapped)
if err != nil {
return returnErrors(err)
}

// flatten the JSON (recursive)
var flattened = make(map[string]interface{})
var lkey string
err := flatten(config, mapped, lkey, &flattened)
if err != nil {
return returnErrors(err)
}
var flattenedByteSlice []byte
flattenedByteSlice, err = json.Marshal(flattened)
if err != nil {
return returnErrors(err)
}
writer.Write(flattenedByteSlice)
} else {
// direct output
writer.Write(byteSlice)
}

return errors
}

// exportYAML - YAML output
func exportYAML(config *Config, workspace common.Workspace, output *exportOutput, writer io.Writer) []error {
// result
var dummyErrors []error

// work vars
var byteSlice []byte
var err error

// do the work
byteSlice, err = yaml.Marshal(output)
if err != nil {
return returnErrors(err)
}

// flatten output?
if config.Flatten {
// we do not support flatten for YAML - returns 'map[interface {}]interface {}'
return returnErrors(errors.New("--flatten unsupported for YAML"))
}
// direct output
writer.Write(byteSlice)

// just so we can return an empty array (not nil)
return dummyErrors
}

// internal - handle export
func export(config *Config, workspace common.Workspace) []error {
// sanity
if len(strings.TrimSpace(config.DestinationFile)) == 0 {
return returnErrors(errors.New("empty destination files"))
}

// create our work object
var output exportOutput
output.Config = config
output.Data.Certification = workspace.GetCertification()
output.Data.Components = workspace.GetAllComponents()
output.Data.Standards = workspace.GetAllStandards()

// handle output destination
var writer io.Writer
if config.DestinationFile == "-" {
// send to stdout
writer = os.Stdout
} else if config.DestinationFile == "-str-" {
// send to null buffer
var b bytes.Buffer
writer = bufio.NewWriter(&b)
} else {
// send to file
file, err := os.Create(config.DestinationFile)
if err != nil {
return returnErrors(err)
}
writer = file
defer file.Close()
}

// handle the output
switch config.OutputFormat {
case FormatJSON:
return exportJSON(config, workspace, &output, writer)
case FormatYAML:
return exportYAML(config, workspace, &output, writer)
default:
return returnErrors(fmt.Errorf("unsupported OutputFormat '%s'", config.OutputFormat))
}
}

// Export loads the inventory and writes output to destinaation
func Export(config Config) []error {
// resolve the actual certification to use
certificationPath, errs := certifications.GetCertification(config.OpencontrolDir, config.Certification)
if errs != nil && len(errs) > 0 {
return errs
}

// load all workspace data
workspace, errs := lib.LoadData(config.OpencontrolDir, certificationPath)
if errs != nil && len(errs) > 0 {
return errs
}

// retrieve workspace data and write to output
return export(&config, workspace)
}
114 changes: 114 additions & 0 deletions pkg/cli/export/export_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package export

import (
"bytes"
"encoding/json"

lib_certifications "github.com/opencontrol/compliance-masonry/pkg/lib/certifications"
"github.com/opencontrol/compliance-masonry/pkg/lib/common"
)

////////////////////////////////////////////////////////////////////////
// Package structures

// Config contains settings for this object
type Config struct {
// remainder are configuration settings local to Export
Certification string
OpencontrolDir string
DestinationFile string
OutputFormat OutputFormat
Flatten bool
InferKeys bool
Docxtemplater bool
KeySeparator string
}

// internal - structure for JSON / YAML output
type exportData struct {
Certification common.Certification
Components []common.Component
Standards []common.Standard
}

// MarshalJSON provides JSON support
func (p *exportData) MarshalJSON() (b []byte, e error) {
// start the output
buffer := bytes.NewBufferString("{")

// certification
buffer.WriteString("\"certification\":")
bytesJSON, err := lib_certifications.MarshalJSON(p.Certification)
if err != nil {
return nil, err
}
buffer.WriteString(string(bytesJSON))

// iterate over components
if len(p.Components) > 0 {
buffer.WriteString(",\"components\":[")
for i, v := range p.Components {
bytesJSON, err := json.Marshal(v)
if err != nil {
return nil, err
}
if i > 0 {
buffer.WriteString(",")
}
buffer.WriteString(string(bytesJSON))
}
buffer.WriteString("]")
}

// iterate over standards
if len(p.Standards) > 0 {
buffer.WriteString(",\"standards\":[")
for i, v := range p.Standards {
bytesJSON, err := json.Marshal(v)
if err != nil {
return nil, err
}
if i > 0 {
buffer.WriteString(",")
}
buffer.WriteString(string(bytesJSON))
}
buffer.WriteString("]")
}

// finish json
buffer.WriteString("}")
return buffer.Bytes(), nil
}

// internal - structure for all exported data
type exportOutput struct {
Config *Config
Data exportData
}

// MarshalJSON provides JSON support
func (p *exportOutput) MarshalJSON() (b []byte, e error) {
// start the output
buffer := bytes.NewBufferString("{")

// config section
buffer.WriteString("\"config\":")
bytesConfig, err := json.Marshal(p.Config)
if err != nil {
return nil, err
}
buffer.WriteString(string(bytesConfig))

// data section
buffer.WriteString(",\"data\":")
bytesData, err := json.Marshal(&p.Data)
if err != nil {
return nil, err
}
buffer.WriteString(string(bytesData))

// close output
buffer.WriteString("}")
return buffer.Bytes(), nil
}
Loading

0 comments on commit e937da2

Please sign in to comment.