-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
imagefilter: add
ResultFormatter
type to support flexible output
This commit adds a new `ResultFormatter` interface that supports outputing the results as text or JSON. Note that the text output should be copy/paste friendly, i.e. the "image-builder" cmdline (or other consuemrs) should support: ``` $ image-builder manifest centos-9 type:qcow2 arch:s390 $ image-builder build centos-9 type:qcow2 arch:x86_64 ``` (and of course `build` will need to error there is an architecture mistach like host-arch != target-arch).
- Loading branch information
1 parent
70fd73a
commit abb3328
Showing
2 changed files
with
177 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package imagefilter | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
) | ||
|
||
// OutputFormat contains the valid output formats for formatting results | ||
type OutputFormat string | ||
|
||
const ( | ||
OutputFormatDefault OutputFormat = "" | ||
OutputFormatText OutputFormat = "text" | ||
OutputFormatJSON OutputFormat = "json" | ||
) | ||
|
||
// ResultFormatter will format the given result list to the given io.Writer | ||
type ResultsFormatter interface { | ||
Output(io.Writer, []Result) error | ||
} | ||
|
||
// NewResultFormatter will create a formatter based on the given format. | ||
func NewResultsFormatter(format OutputFormat) (ResultsFormatter, error) { | ||
switch format { | ||
case OutputFormatDefault, OutputFormatText: | ||
return &textResultsFormatter{}, nil | ||
case OutputFormatJSON: | ||
return &jsonResultsFormatter{}, nil | ||
default: | ||
return nil, fmt.Errorf("unsupported formatter %q", format) | ||
} | ||
} | ||
|
||
type textResultsFormatter struct{} | ||
|
||
func (*textResultsFormatter) Output(w io.Writer, all []Result) error { | ||
var errs []error | ||
|
||
for _, res := range all { | ||
// The should be copy/paste friendly, i.e. the "image-builder" | ||
// cmdline should support: | ||
// image-builder manifest centos-9 type:qcow2 arch:s390 | ||
// image-builder build centos-9 type:qcow2 arch:x86_64 | ||
if _, err := fmt.Fprintf(w, "%s type:%s arch:%s\n", res.Distro.Name(), res.ImgType.Name(), res.Arch.Name()); err != nil { | ||
errs = append(errs, err) | ||
} | ||
} | ||
if len(errs) > 0 { | ||
return errors.Join(errs...) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type jsonResultsFormatter struct{} | ||
|
||
type distroResultJSON struct { | ||
Name string `json:"name"` | ||
} | ||
|
||
type archResultJSON struct { | ||
Name string `json:"name"` | ||
} | ||
|
||
type imgTypeResultJSON struct { | ||
Name string `json:"name"` | ||
} | ||
|
||
type filteredResultJSON struct { | ||
Distro distroResultJSON `json:"distro"` | ||
Arch archResultJSON `json:"arch"` | ||
ImgType imgTypeResultJSON `json:"image_type"` | ||
} | ||
|
||
func (*jsonResultsFormatter) Output(w io.Writer, all []Result) error { | ||
var out []filteredResultJSON | ||
|
||
for _, res := range all { | ||
out = append(out, filteredResultJSON{ | ||
Distro: distroResultJSON{ | ||
Name: res.Distro.Name(), | ||
}, | ||
Arch: archResultJSON{ | ||
Name: res.Arch.Name(), | ||
}, | ||
ImgType: imgTypeResultJSON{ | ||
Name: res.ImgType.Name(), | ||
}, | ||
}) | ||
} | ||
|
||
enc := json.NewEncoder(w) | ||
return enc.Encode(out) | ||
} |
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,81 @@ | ||
package imagefilter_test | ||
|
||
import ( | ||
"bytes" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/osbuild/images/pkg/distrofactory" | ||
"github.com/osbuild/images/pkg/imagefilter" | ||
) | ||
|
||
func newFakeResult(t *testing.T, resultSpec string) imagefilter.Result { | ||
fac := distrofactory.NewTestDefault() | ||
|
||
l := strings.Split(resultSpec, ":") | ||
require.Equal(t, len(l), 3) | ||
|
||
// XXX: it would be nice if TestDistro would support constructing | ||
// like GetDistro("rhel-8.1:i386,amd64:ami,qcow2") that then | ||
// creates test distro/type/arch on the fly instead of the current | ||
// very static setup | ||
di := fac.GetDistro(l[0]) | ||
require.NotNil(t, di) | ||
ar, err := di.GetArch(l[2]) | ||
require.NoError(t, err) | ||
im, err := ar.GetImageType(l[1]) | ||
require.NoError(t, err) | ||
return imagefilter.Result{di, ar, im} | ||
} | ||
|
||
func TestResultsFormatter(t *testing.T) { | ||
|
||
for _, tc := range []struct { | ||
formatter string | ||
fakeResults []string | ||
expectsOutput string | ||
}{ | ||
{ | ||
"", | ||
[]string{"test-distro-1:qcow2:test_arch3"}, | ||
"test-distro-1 type:qcow2 arch:test_arch3\n", | ||
}, | ||
{ | ||
"text", | ||
[]string{"test-distro-1:qcow2:test_arch3"}, | ||
"test-distro-1 type:qcow2 arch:test_arch3\n", | ||
}, | ||
{ | ||
"text", | ||
[]string{ | ||
"test-distro-1:qcow2:test_arch3", | ||
"test-distro-1:test_type:test_arch", | ||
}, | ||
"test-distro-1 type:qcow2 arch:test_arch3\n" + | ||
"test-distro-1 type:test_type arch:test_arch\n", | ||
}, | ||
{ | ||
"json", | ||
[]string{ | ||
"test-distro-1:qcow2:test_arch3", | ||
"test-distro-1:test_type:test_arch", | ||
}, | ||
`[{"distro":{"name":"test-distro-1"},"arch":{"name":"test_arch3"},"image_type":{"name":"qcow2"}},{"distro":{"name":"test-distro-1"},"arch":{"name":"test_arch"},"image_type":{"name":"test_type"}}]` + "\n", | ||
}, | ||
} { | ||
res := make([]imagefilter.Result, len(tc.fakeResults)) | ||
for i, resultSpec := range tc.fakeResults { | ||
res[i] = newFakeResult(t, resultSpec) | ||
} | ||
|
||
var buf bytes.Buffer | ||
fmter, err := imagefilter.NewResultsFormatter(imagefilter.OutputFormat(tc.formatter)) | ||
require.NoError(t, err) | ||
err = fmter.Output(&buf, res) | ||
assert.NoError(t, err) | ||
assert.Equal(t, tc.expectsOutput, buf.String(), tc) | ||
} | ||
} |