diff --git a/cmd/airgap/listimages.go b/cmd/airgap/listimages.go index 1291de8a22e7..34cf42a7adc0 100644 --- a/cmd/airgap/listimages.go +++ b/cmd/airgap/listimages.go @@ -48,8 +48,11 @@ func NewAirgapListImagesCmd() *cobra.Command { return fmt.Errorf("failed to get config: %w", err) } + out := cmd.OutOrStdout() for _, uri := range airgap.GetImageURIs(clusterConfig.Spec, all) { - fmt.Fprintln(cmd.OutOrStdout(), uri) + if _, err := fmt.Fprintln(out, uri); err != nil { + return err + } } return nil }, diff --git a/cmd/airgap/listimages_test.go b/cmd/airgap/listimages_test.go index 38c42aaf0959..be20ffc85a87 100644 --- a/cmd/airgap/listimages_test.go +++ b/cmd/airgap/listimages_test.go @@ -17,18 +17,18 @@ limitations under the License. package airgap import ( - "bufio" - "bytes" "errors" "fmt" - "io" "os" "path/filepath" "runtime" + "strings" "testing" "testing/iotest" + internalio "github.com/k0sproject/k0s/internal/io" "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" + "github.com/spf13/cobra" "github.com/stretchr/testify/assert" @@ -44,11 +44,28 @@ func TestAirgapListImages(t *testing.T) { defaultImage := v1beta1.DefaultEnvoyProxyImage().URI() + t.Run("HonorsIOErrors", func(t *testing.T) { + var writes uint + underTest := NewAirgapListImagesCmd() + underTest.SetIn(iotest.ErrReader(errors.New("unexpected read from standard input"))) + underTest.SilenceUsage = true // Cobra writes usage to stdout on errors 🤔 + underTest.SetOut(internalio.WriterFunc(func(p []byte) (int, error) { + writes++ + return 0, assert.AnError + })) + var stderr strings.Builder + underTest.SetErr(&stderr) + + assert.Same(t, assert.AnError, underTest.Execute()) + assert.Equal(t, uint(1), writes, "Expected a single write to stdout") + assert.Equal(t, fmt.Sprintf("Error: %v\n", assert.AnError), stderr.String()) + }) + t.Run("All", func(t *testing.T) { underTest, out, err := newAirgapListImagesCmdWithConfig(t, "{}", "--all") require.NoError(t, underTest.Execute()) - lines := intoLines(out) + lines := strings.Split(out.String(), "\n") if runtime.GOARCH == "arm" { assert.NotContains(t, lines, defaultImage) } else { @@ -88,7 +105,7 @@ spec: require.NoError(t, underTest.Execute()) - lines := intoLines(out) + lines := strings.Split(out.String(), "\n") for _, contained := range test.contained { if runtime.GOARCH == "arm" { assert.NotContains(t, lines, contained) @@ -105,11 +122,11 @@ spec: }) } -func newAirgapListImagesCmdWithConfig(t *testing.T, config string, args ...string) (_ *cobra.Command, out, err *bytes.Buffer) { +func newAirgapListImagesCmdWithConfig(t *testing.T, config string, args ...string) (_ *cobra.Command, out, err *strings.Builder) { configFile := filepath.Join(t.TempDir(), "k0s.yaml") require.NoError(t, os.WriteFile(configFile, []byte(config), 0644)) - out, err = new(bytes.Buffer), new(bytes.Buffer) + out, err = new(strings.Builder), new(strings.Builder) cmd := NewAirgapListImagesCmd() cmd.SetArgs(append([]string{"--config=" + configFile}, args...)) cmd.SetIn(iotest.ErrReader(errors.New("unexpected read from standard input"))) @@ -117,12 +134,3 @@ func newAirgapListImagesCmdWithConfig(t *testing.T, config string, args ...strin cmd.SetErr(err) return cmd, out, err } - -func intoLines(in io.Reader) (lines []string) { - scanner := bufio.NewScanner(in) - scanner.Split(bufio.ScanLines) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - return -}