diff --git a/pkg/imagefilter/formatter.go b/pkg/imagefilter/formatter.go new file mode 100644 index 0000000000..775612f9bf --- /dev/null +++ b/pkg/imagefilter/formatter.go @@ -0,0 +1,93 @@ +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 { + // this should be copy/paste friendly + if _, err := fmt.Fprintf(w, "%s arch:%s type:%s\n", res.Distro.Name(), res.Arch.Name(), res.ImgType.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) +} diff --git a/pkg/imagefilter/formatter_test.go b/pkg/imagefilter/formatter_test.go new file mode 100644 index 0000000000..78f43e6c72 --- /dev/null +++ b/pkg/imagefilter/formatter_test.go @@ -0,0 +1,58 @@ +package imagefilter_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/osbuild/images/pkg/distrofactory" + "github.com/osbuild/images/pkg/imagefilter" +) + +func TestResultsFormatter(t *testing.T) { + fac := distrofactory.NewTestDefault() + + for _, tc := range []struct { + formatter string + distro, arch, imgType string + expectsOutput string + }{ + { + "", + "test-distro-1", "test_arch3", "qcow2", + `test-distro-1 arch:test_arch3 type:qcow2` + "\n", + }, + { + "text", + "test-distro-1", "test_arch3", "qcow2", + `test-distro-1 arch:test_arch3 type:qcow2` + "\n", + }, + { + "json", + "test-distro-1", "test_arch3", "qcow2", + `[{"distro":{"name":"test-distro-1"},"arch":{"name":"test_arch3"},"image_type":{"name":"qcow2"}}]` + "\n", + }, + } { + // XXX: it would be nice if TestDistro would support constructing + // like GetDistro("rhel-8.1:i386,amd64:ami,qcow2") instead of + // the current very static setup + di := fac.GetDistro(tc.distro) + require.NotNil(t, di) + ar, err := di.GetArch(tc.arch) + require.NoError(t, err) + im, err := ar.GetImageType(tc.imgType) + require.NoError(t, err) + + var buf bytes.Buffer + res := []imagefilter.Result{ + {Distro: di, Arch: ar, ImgType: im}, + } + 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) + } +}