-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
commands/.../scorecard: add support for JSON output #1228
Conversation
Note: Since this is a draft PR, Travis-CI will not run until I mark it ready for review |
e915fd0
to
d48f70f
Compare
NOTE: I will be basing this PR on top of #1272, so this will be blocked until that PR is merged |
We can use viper directly now, so we can ignore the return values for cobra flags
3be48f4
to
5503e0c
Compare
0a1eda8
to
3d098db
Compare
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.
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?).
cmd/operator-sdk/scorecard/cmd.go
Outdated
@@ -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") |
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.
My vote would be -o, --output
, which would align with kubectl's usage.
internal/pkg/scorecard/scorecard.go
Outdated
origStdout := os.Stdout | ||
readLog, writeLog, _ := os.Pipe() | ||
deferLogPrint := true | ||
// suppress all output except the resulting json |
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.
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.
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.
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"} |
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.
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
.
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.
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.
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.
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?
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.
@AlexNPavel Ping on this question about the domain.
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.
I'll use osdk.openshift.io
for the domain
7f8bd9d
to
aaa4ea3
Compare
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.
aaa4ea3
to
83d48bc
Compare
} | ||
} | ||
if viper.GetString(OutputFormatOpt) == "json" { | ||
log, err := ioutil.ReadAll(logReadWriter) |
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.
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.
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.
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"} |
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.
@AlexNPavel Ping on this question about the domain.
@joelanford We could collect the logs in a buffer during the human-readable output as well. For instance:
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 |
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.
Agreed that streaming output in human-readable
mode is valuable. We can put that thought on the backburner for now.
LGTM
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.
A few small things, otherwise LGTM
Co-Authored-By: AlexNPavel <alexpavel123@gmail.com>
Co-Authored-By: AlexNPavel <alexpavel123@gmail.com>
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.
I think we need to be more careful with potential nil pointer exceptions
internal/pkg/scorecard/helpers.go
Outdated
@@ -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 { |
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.
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.
internal/pkg/scorecard/helpers.go
Outdated
} | ||
|
||
// UpdateState updates the state of a TestResult. | ||
func (res *TestResult) UpdateState() { |
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.
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 { |
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.
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.
Fixed /cc @shawn-hurley |
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.