-
Notifications
You must be signed in to change notification settings - Fork 85
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
Add export option #292
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
components
andstandard
iterations are very similar. Could this be condensed into a for each loop?e.g.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tracked in #295
There was a problem hiding this comment.
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.