diff --git a/pkg/imagefilter/formatter.go b/pkg/imagefilter/formatter.go new file mode 100644 index 0000000000..5084f3b886 --- /dev/null +++ b/pkg/imagefilter/formatter.go @@ -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) +} diff --git a/pkg/imagefilter/formatter_test.go b/pkg/imagefilter/formatter_test.go new file mode 100644 index 0000000000..c866d2a307 --- /dev/null +++ b/pkg/imagefilter/formatter_test.go @@ -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) + } +}