Skip to content
New issue

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

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

Already on GitHub? # to your account

bundle validate: new alpha flag --output=<text|json-alpha1> to format results #3011

Merged
merged 4 commits into from
May 28, 2020
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
16 changes: 16 additions & 0 deletions changelog/fragments/json-hidden-flag.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# entries is a list of entries to include in
# release notes and/or the migration guide
entries:
- description: >
Add new hidden alpha flag `--output` to print the result of `operator-sdk bundle validate` in JSON format to stdout. Logs are printed to stderr.
# kind is one of:
# - addition
# - change
# - deprecation
# - removal
# - bugfix
kind: "addition"
# Is this a breaking change?
breaking: false
28 changes: 28 additions & 0 deletions cmd/operator-sdk/bundle/internal/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2020 The Operator-SDK Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package internal

import (
"io"

log "github.com/sirupsen/logrus"
)

// NewLoggerTo returns a logger that writes logs to w.
func NewLoggerTo(w io.Writer) *log.Logger {
logger := log.New()
logger.SetOutput(w)
return logger
}
166 changes: 166 additions & 0 deletions cmd/operator-sdk/bundle/internal/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright 2020 The Operator-SDK Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package internal

import (
"encoding/json"
"errors"
"fmt"
"os"

registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
)

const (
JSONAlpha1 = "json-alpha1"
Text = "text"
)

// Result represents the final result
type Result struct {
Passed bool `json:"passed"`
Outputs []output `json:"outputs"`
}

// output represents the logs which are used to return the final result in the JSON format
type output struct {
Type string `json:"type"`
Message string `json:"message"`
}

// NewResult return a new result object which starts with passed == true since has no errors
func NewResult() Result {
return Result{Passed: true}
}

// AddInfo will add a log to the result with the Info Level
func (o *Result) AddInfo(msg string) {
o.Outputs = append(o.Outputs, output{
Type: logrus.InfoLevel.String(),
Message: msg,
})
}

// AddError will add a log to the result with the Error Level
func (o *Result) AddError(err error) {
verr := registrybundle.ValidationError{}
if errors.As(err, &verr) {
for _, valErr := range verr.Errors {
o.Outputs = append(o.Outputs, output{
Type: logrus.ErrorLevel.String(),
Message: valErr.Error(),
})
}
} else {
o.Outputs = append(o.Outputs, output{
Type: logrus.ErrorLevel.String(),
Message: err.Error(),
})
}
o.Passed = false
}

// AddWarn will add a log to the result with the Warn Level
func (o *Result) AddWarn(err error) {
o.Outputs = append(o.Outputs, output{
Type: logrus.WarnLevel.String(),
Message: err.Error(),
})
}

// printText will print the output in human readable format
func (o *Result) printText(logger *logrus.Entry) error {
for _, obj := range o.Outputs {
lvl, err := logrus.ParseLevel(obj.Type)
if err != nil {
return err
}
switch lvl {
case logrus.InfoLevel:
logger.Info(obj.Message)
case logrus.WarnLevel:
logger.Warn(obj.Message)
case logrus.ErrorLevel:
logger.Error(obj.Message)
default:
return fmt.Errorf("unknown output level %q", obj.Type)
}
}

return nil
}

// printJSON will print the output in JSON format
func (o *Result) printJSON() error {
prettyJSON, err := json.MarshalIndent(o, "", " ")
if err != nil {
return fmt.Errorf("error marshaling JSON output: %v", err)
}
fmt.Printf("%s\n", string(prettyJSON))
return nil
}

// prepare should be used when writing an Result to a non-log writer.
// it will ensure that the passed boolean will properly set in the case of the setters were not properly used
func (o *Result) prepare() error {
o.Passed = true
for i, obj := range o.Outputs {
lvl, err := logrus.ParseLevel(obj.Type)
if err != nil {
return err
}
if o.Passed && lvl == logrus.ErrorLevel {
o.Passed = false
}
lvlBytes, _ := lvl.MarshalText()
o.Outputs[i].Type = string(lvlBytes)
}
return nil
}

// PrintWithFormat prints output to w in format, and exits if some object in output
// is not in a passing state.
func (o *Result) PrintWithFormat(format string) (err error) {
// the prepare will ensure the result data if the setters were not used
if err = o.prepare(); err != nil {
return fmt.Errorf("error to prepare output: %v", err)
}

printf := o.getPrintFuncFormat(format)
if err = printf(*o); err == nil && !o.Passed {
os.Exit(1) // Exit with error when any Error type was added
}
return err
}

// getPrintFuncFormat returns a function that writes an Result to w in a given
// format, defaulting to "text" if format is not recognized.
func (o *Result) getPrintFuncFormat(format string) func(Result) error {
// PrintWithFormat output in desired format.
switch format {
case JSONAlpha1:
return func(o Result) error {
return o.printJSON()
}
}

// Address all to the Stdout when the type is not JSON
logger := log.NewEntry(NewLoggerTo(os.Stdout))
return func(o Result) error {
return o.printText(logger)
}
}
Loading