Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add export option #292

Merged
merged 1 commit into from
Jun 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

components and standard iterations are very similar. Could this be condensed into a for each loop?

e.g.

    arrayOpenControl := [2]string{"components", "standards"}

    for index,element := range arrayOpenControl{
        if len(p.element) > 0 {
           .... do stuff
        }     
    }   
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shawndwells this actually would probably be a separate function as it deals with more than just strings.
I was hoping to avoid code cleanup in these type of PRs because of how big they are.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked in #295

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Thanks for opening the ticket to track.

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