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

commands/.../scorecard: add support for JSON output #1228

Merged
merged 22 commits into from
May 1, 2019

Conversation

AlexNPavel
Copy link
Contributor

Description of the change: Add JSON output support based on proposal from PR #1049.

Motivation for the change: Closes issue #1189.

This PR will be considered WIP until PR #1049 is merged.

@AlexNPavel AlexNPavel added the scorecard Issue relates to the scorecard subcomponent label Mar 19, 2019
@openshift-ci-robot openshift-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 19, 2019
@AlexNPavel
Copy link
Contributor Author

Note: Since this is a draft PR, Travis-CI will not run until I mark it ready for review

@openshift-ci-robot openshift-ci-robot added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Apr 1, 2019
@openshift-ci-robot openshift-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Apr 1, 2019
@AlexNPavel
Copy link
Contributor Author

NOTE: I will be basing this PR on top of #1272, so this will be blocked until that PR is merged

@openshift-ci-robot openshift-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Apr 8, 2019
@AlexNPavel
Copy link
Contributor Author

Copy link
Member

@joelanford joelanford left a comment

Choose a reason for hiding this comment

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

What do you think about abstracting the output formatter as an interface or function definition such that we do the setup of the output formatter implementation near the top of the function and then just use the abstraction without needing the conditionals when we're doing the actual outputting?

If we did something like that, I think it would help separate the result output from the non-result output and make it more obvious what the output implementation actually requires (it looks like the suites and the log?).

@@ -51,6 +51,7 @@ func NewCmd() *cobra.Command {
scorecardCmd.Flags().String(scorecard.ProxyImageOpt, fmt.Sprintf("quay.io/operator-framework/scorecard-proxy:%s", strings.TrimSuffix(version.Version, "+git")), "Image name for scorecard proxy")
scorecardCmd.Flags().String(scorecard.ProxyPullPolicyOpt, "Always", "Pull policy for scorecard proxy image")
scorecardCmd.Flags().String(scorecard.CRDsDirOpt, scaffold.CRDsDir, "Directory containing CRDs (all CRD manifest filenames must have the suffix 'crd.yaml')")
scorecardCmd.Flags().String(scorecard.OutputFormatOpt, "human-readable", "Output format for results. Valid values: human-readable, json")
Copy link
Member

Choose a reason for hiding this comment

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

My vote would be -o, --output, which would align with kubectl's usage.

origStdout := os.Stdout
readLog, writeLog, _ := os.Pipe()
deferLogPrint := true
// suppress all output except the resulting json
Copy link
Member

Choose a reason for hiding this comment

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

I'm having trouble following what gets written to os.Stdout and os.Stderr when they're being redirected to writeLog. Is it just the log.Errorf lines (227, 244, and 258)?

Rather than the complex logic to temporarily redirect stdout and stderr, I'm wondering if it's possible for us to explicitly control where non-results output goes with something like:

buf := &bytes.Buffer{}
...
log.SetOutput(buf)
...
fmt.Fprintf(buf, "something I want to print that isn't part of the results output")

And then when we're printing results, include the contents of buf in the output based on the output format.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've changed the way the logging is handled for this. We instead configure a global logrus var called log and configure it as you suggested (using SetOutput) and all logging is handled through log. Only the final printing of the results goes through fmt.Printf, so we don't need all the workarounds I previously had.


var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "scorecard", Version: "v1alpha1"}
Copy link
Member

Choose a reason for hiding this comment

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

Should the scorecard group include a domain to make it more unique? I don't know if we have a convention for SDK APIs (if not, we probably should). As a point of reference, OLM currently uses operators.coreos.com.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it would be a good idea to include a domain. I'm not quite sure what to use though. Since we are creating this now, I feel like it would be better to go with an openshift URL (like the metering-operator, which uses metering.openshift.io) instead of a coreos URL.

Copy link
Member

Choose a reason for hiding this comment

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

Oh yeah, I agree with using openshift.io. I'd be good with any of these:

  • operator-sdk.openshift.io
  • operatorsdk.openshift.io
  • osdk.openshift.io

And the kinds are ScorecardOutput and ScorecardOutputList, so since "Scorecard" is in the kind name, I figure we don't need "scorecard" in the group?

Copy link
Member

Choose a reason for hiding this comment

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

@AlexNPavel Ping on this question about the domain.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll use osdk.openshift.io for the domain

internal/pkg/scorecard/scorecard.go Outdated Show resolved Hide resolved
This commit configures a global logrus log variable that can be
used by the entire package and is configured based on the output
format. All logging should happen via the `log` var and fmt.Printf
should only be used for the final output.
}
}
if viper.GetString(OutputFormatOpt) == "json" {
log, err := ioutil.ReadAll(logReadWriter)
Copy link
Member

Choose a reason for hiding this comment

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

With this, ioutil.ReadAll() will run before the deferred log statements, so I don't think we'll see any of them in the output.

The simplest way to fix this would be to move lines 108-291 into a separate function so that the deferred log statements run before we output the results.

Long term, this could potentially be improved further by using a scorecardRunner struct to decouple the actual scorecard run from the cobra CLI stuff to make it more reusable.

Copy link
Member

@joelanford joelanford left a comment

Choose a reason for hiding this comment

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

Looks just about ready to me. Just the one remaining question about the API domain.

Not for this PR, but I am curious if you had thoughts on my question in this comment. It looks like the complication would be that for human-readable we intermingle the log and the results and for json we collect the log and include it in the results.


var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "scorecard", Version: "v1alpha1"}
Copy link
Member

Choose a reason for hiding this comment

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

@AlexNPavel Ping on this question about the domain.

@AlexNPavel
Copy link
Contributor Author

@joelanford We could collect the logs in a buffer during the human-readable output as well. For instance:

Logs:
---
...
---

Basic Test:
...

Now that the scorecard run function is separated from the output, all the logs appear before the final results anyway. I feel that keeping the streaming logs might be useful for the human-readble output though.

Copy link
Member

@joelanford joelanford left a comment

Choose a reason for hiding this comment

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

Agreed that streaming output in human-readable mode is valuable. We can put that thought on the backburner for now.

LGTM

Copy link
Member

@estroz estroz left a comment

Choose a reason for hiding this comment

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

A few small things, otherwise LGTM

internal/pkg/scorecard/scorecard.go Outdated Show resolved Hide resolved
pkg/apis/scorecard/v1alpha1/types.go Outdated Show resolved Hide resolved
internal/pkg/scorecard/helpers.go Outdated Show resolved Hide resolved
AlexNPavel and others added 3 commits April 26, 2019 15:05
Co-Authored-By: AlexNPavel <alexpavel123@gmail.com>
Co-Authored-By: AlexNPavel <alexpavel123@gmail.com>
Copy link
Member

@shawn-hurley shawn-hurley left a comment

Choose a reason for hiding this comment

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

I think we need to be more careful with potential nil pointer exceptions

hack/tests/scorecard-subcommand.sh Show resolved Hide resolved
@@ -35,3 +40,85 @@ func ResultsCumulative(results []TestResult) (earned, max int) {
}
return earned, max
}

// CalculateResult returns a ScorecardSuiteResult with the state and Tests fields set based on a slice of ScorecardTestResults
func CalculateResult(tests []*scapiv1alpha1.ScorecardTestResult) *scapiv1alpha1.ScorecardSuiteResult {
Copy link
Member

Choose a reason for hiding this comment

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

Why are we choosing to pass in pointers and then not nil checking?

I think that the correct thing to do is not take pointers in the slice.

I also would like to understand why a pointer back? this just means callers will have to check for nil.

}

// UpdateState updates the state of a TestResult.
func (res *TestResult) UpdateState() {
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be close to the TestResult struct IMO. having it here would be confusing and hard to find IMO.

fmt.Printf("\nTotal Score: %.0f%%\n", totalScore)
// Print suggestions
for _, suite := range suites {
for _, result := range suite.TestResults {
Copy link
Member

Choose a reason for hiding this comment

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

So is the place I am worried about. Technically suite could have a slice in TestResults like:

[]{nil, nil, nil}

We should really make sure that this is not the case so we don't nil panic here. I would think instead if you don't have the structs of the slice as pointers (which I can't see a reason to have them be a pointer at the moment) then this would not be a concern.

@AlexNPavel
Copy link
Contributor Author

Fixed

/cc @shawn-hurley

@AlexNPavel AlexNPavel merged commit 052bd65 into operator-framework:master May 1, 2019
@AlexNPavel AlexNPavel deleted the json-scorecard branch May 1, 2019 20:05
estroz pushed a commit to estroz/operator-sdk that referenced this pull request May 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scorecard Issue relates to the scorecard subcomponent size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants