osbuild: Add validation error logging
osbuild can return a json object with details about manifest validation errors. This adds support for saving those, and printing them when the Write function is called. eg. when using composer-cli compose log UUID Includes tests for new behavior.
This commit is contained in:
parent
8398f27742
commit
b57a0d322f
3 changed files with 103 additions and 0 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
|
|
@ -13,6 +14,13 @@ type Result struct {
|
|||
Error json.RawMessage `json:"error,omitempty"`
|
||||
Log map[string]PipelineResult `json:"log"`
|
||||
Metadata map[string]PipelineMetadata `json:"metadata"`
|
||||
Errors []ValidationError `json:"errors,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
type ValidationError struct {
|
||||
Message string `json:"message"`
|
||||
Path []string `json:"path"`
|
||||
}
|
||||
|
||||
type PipelineResult []StageResult
|
||||
|
|
@ -116,6 +124,14 @@ func (res *Result) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
func (res *Result) Write(writer io.Writer) error {
|
||||
// Error may be included, print them first
|
||||
if res != nil && len(res.Errors) > 0 {
|
||||
fmt.Fprintf(writer, "Error %s\n", res.Title)
|
||||
for _, e := range res.Errors {
|
||||
fmt.Fprintf(writer, "%s: %s\n", strings.Join(e.Path, "."), e.Message)
|
||||
}
|
||||
}
|
||||
|
||||
if res == nil || res.Log == nil {
|
||||
fmt.Fprintf(writer, "The compose result is empty.\n")
|
||||
return nil
|
||||
|
|
@ -155,3 +171,34 @@ func (res *Result) Write(writer io.Writer) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// The ValidationError path from osbuild can contain strings or numbers
|
||||
// json represents all numbers as float64 but since we know they are really
|
||||
// ints any fractional part is truncated when converting to a string.
|
||||
func (ve *ValidationError) UnmarshalJSON(data []byte) error {
|
||||
var raw struct {
|
||||
Message string
|
||||
Path []interface{}
|
||||
}
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
ve.Message = raw.Message
|
||||
|
||||
// Convert the path elements to strings
|
||||
var path []string
|
||||
for _, p := range raw.Path {
|
||||
switch v := p.(type) {
|
||||
// json converts numbers, even 0, to float64 not int
|
||||
case float64:
|
||||
path = append(path, fmt.Sprintf("[%0.0f]", v))
|
||||
case string:
|
||||
path = append(path, v)
|
||||
default:
|
||||
return fmt.Errorf("Unexpected type in ValidationError Path: %#v", v)
|
||||
}
|
||||
}
|
||||
ve.Path = path
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
)
|
||||
|
|
@ -203,6 +204,20 @@ func TestUnmarshalV2Failure(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalValidationFailure(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
var result Result
|
||||
err := json.Unmarshal([]byte(validationResultFailure), &result)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.False(result.Success)
|
||||
assert.Equal(result.Title, "JSON Schema validation failed")
|
||||
require.Len(result.Errors, 2)
|
||||
assert.Len(result.Errors[0].Path, 2)
|
||||
assert.Len(result.Errors[1].Path, 5)
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
result := Result{
|
||||
|
|
@ -335,3 +350,29 @@ func TestWriteEmpty(t *testing.T) {
|
|||
assert.NoError(result.Write(&b))
|
||||
assert.Equal("The compose result is empty.\n", b.String())
|
||||
}
|
||||
|
||||
func TestValidationFailWrite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
result := Result{
|
||||
Type: "https://osbuild.org/validation-error",
|
||||
Success: false,
|
||||
Title: "JSON Schema validation failed",
|
||||
Errors: []ValidationError{{
|
||||
Message: "Additional properties are not allowed ('homer' was unexpected)",
|
||||
Path: []string{"sources", "org.osbuild.curl"},
|
||||
}, {
|
||||
Message: "Additional properties are not allowed ('file_contextso' was unexpected)",
|
||||
Path: []string{"pipelines", "[0]", "stages", "[1]", "options"},
|
||||
}},
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
assert.NoError(result.Write(&b))
|
||||
expectedOutput := `Error JSON Schema validation failed
|
||||
sources.org.osbuild.curl: Additional properties are not allowed ('homer' was unexpected)
|
||||
pipelines.[0].stages.[1].options: Additional properties are not allowed ('file_contextso' was unexpected)
|
||||
The compose result is empty.
|
||||
`
|
||||
|
||||
assert.Equal(expectedOutput, b.String())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7872,3 +7872,18 @@ const fullResultRaw = `
|
|||
}
|
||||
}
|
||||
`
|
||||
|
||||
const validationResultFailure = `
|
||||
{
|
||||
"type": "https://osbuild.org/validation-error",
|
||||
"success": false,
|
||||
"title": "JSON Schema validation failed",
|
||||
"errors": [{
|
||||
"message": "Additional properties are not allowed ('homer' was unexpected)",
|
||||
"path": ["sources", "org.osbuild.curl"]
|
||||
},{
|
||||
"message": "Additional properties are not allowed ('file_contextso' was unexpected)",
|
||||
"path": ["pipelines", 0, "stages", 1, "options"]
|
||||
}]
|
||||
}
|
||||
`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue