debian-forge-composer/internal/osbuildexecutor/runner-impl-aws-ec2_test.go
Michael Vogt 86b1143923 osbuildexecutor: show full osbuild exector on json decode errors
This is a short term workaround to get better visibility why
the osbuild executor sometimes sends non-json data. When reading
the result from the executor the entire output is now read and
if the json parsing goes wrong it will use the entire body
in the error message for better debug visibility.
2024-08-05 14:51:40 +02:00

240 lines
7.3 KiB
Go

package osbuildexecutor_test
import (
"archive/tar"
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/osbuild/images/pkg/osbuild"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/internal/osbuildexecutor"
)
func TestWaitForSI(t *testing.T) {
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
defer cancel()
require.False(t, osbuildexecutor.WaitForSI(ctx, server.URL))
server.Start()
ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second*1)
defer cancel2()
require.True(t, osbuildexecutor.WaitForSI(ctx2, server.URL))
}
func TestWriteInputArchive(t *testing.T) {
cacheDir := t.TempDir()
storeDir := filepath.Join(cacheDir, "store")
require.NoError(t, os.Mkdir(storeDir, 0755))
storeSubDir := filepath.Join(storeDir, "subdir")
require.NoError(t, os.Mkdir(storeSubDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(storeDir, "contents"), []byte("storedata"), 0600))
require.NoError(t, os.WriteFile(filepath.Join(storeSubDir, "contents"), []byte("storedata"), 0600))
archive, err := osbuildexecutor.WriteInputArchive(cacheDir, storeDir, []string{"image"}, []byte("{\"version\": 2}"))
require.NoError(t, err)
cmd := exec.Command("tar",
"-tf",
archive,
)
out, err := cmd.CombinedOutput()
require.NoError(t, err)
require.ElementsMatch(t, []string{
"control.json",
"manifest.json",
"store/",
"store/subdir/",
"store/subdir/contents",
"store/contents",
"",
}, strings.Split(string(out), "\n"))
}
func TestHandleBuild(t *testing.T) {
buildServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
input, err := io.ReadAll(r.Body)
require.NoError(t, err)
require.Equal(t, []byte("test"), input)
w.WriteHeader(http.StatusCreated)
osbuildResult := osbuild.Result{
Success: true,
}
data, err := json.Marshal(osbuildResult)
require.NoError(t, err)
_, err = w.Write(data)
require.NoError(t, err)
}))
cacheDir := t.TempDir()
inputArchive := filepath.Join(cacheDir, "test.tar")
require.NoError(t, os.WriteFile(inputArchive, []byte("test"), 0600))
osbuildResult, err := osbuildexecutor.HandleBuild(inputArchive, buildServer.URL)
require.NoError(t, err)
require.True(t, osbuildResult.Success)
}
func TestHandleBuildNoJSON(t *testing.T) {
buildServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := io.ReadAll(r.Body)
require.NoError(t, err)
w.WriteHeader(http.StatusCreated)
_, err = w.Write([]byte("bad non-json text"))
require.NoError(t, err)
}))
cacheDir := t.TempDir()
inputArchive := filepath.Join(cacheDir, "test.tar")
require.NoError(t, os.WriteFile(inputArchive, []byte("test"), 0600))
_, err := osbuildexecutor.HandleBuild(inputArchive, buildServer.URL)
require.ErrorContains(t, err, `Unable to decode response body "bad non-json text" into osbuild result:`)
}
func TestHandleOutputArchive(t *testing.T) {
serverDir := t.TempDir()
serverOutputDir := filepath.Join(serverDir, "output")
require.NoError(t, os.Mkdir(serverOutputDir, 0755))
serverImageDir := filepath.Join(serverOutputDir, "image")
require.NoError(t, os.Mkdir(serverImageDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(serverImageDir, "disk.img"), []byte("image"), 0600))
serverOutput := filepath.Join(serverDir, "server-output.tar")
cmd := exec.Command("tar",
"-C",
serverDir,
"-cf",
serverOutput,
filepath.Base(serverOutputDir),
)
require.NoError(t, cmd.Run())
resultServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
file, err := os.Open(serverOutput)
if err != nil {
require.NoError(t, err)
}
defer file.Close()
_, err = io.Copy(w, file)
require.NoError(t, err)
}))
outputDir := t.TempDir()
archive, err := osbuildexecutor.FetchOutputArchive(outputDir, resultServer.URL)
require.NoError(t, err)
extractDir := filepath.Join(outputDir, "extracted")
require.NoError(t, os.Mkdir(extractDir, 0755))
require.NoError(t, osbuildexecutor.ExtractOutputArchive(extractDir, archive))
content, err := os.ReadFile(filepath.Join(extractDir, "image", "disk.img"))
require.NoError(t, err)
require.Equal(t, []byte("image"), content)
}
func makeTestTarfile(t *testing.T, content map[*tar.Header]string) string {
tmpdir := t.TempDir()
testTarPath := filepath.Join(tmpdir, "test.tar")
f, err := os.Create(testTarPath)
assert.NoError(t, err)
defer f.Close()
atar := tar.NewWriter(f)
for hdr, fcnt := range content {
if hdr.Mode == 0 {
hdr.Mode = 0644
}
hdr.Size = int64(len(fcnt))
err := atar.WriteHeader(hdr)
assert.NoError(t, err)
_, err = atar.Write([]byte(fcnt))
assert.NoError(t, err)
}
return testTarPath
}
func TestValidateOutputArchiveHappy(t *testing.T) {
testTarPath := makeTestTarfile(t, map[*tar.Header]string{
&tar.Header{Name: "file1"}: "some content",
&tar.Header{Name: "path/to/file"}: "other content",
&tar.Header{
Name: "path/to/dir",
Typeflag: tar.TypeDir,
}: "",
})
err := osbuildexecutor.ValidateOutputArchive(testTarPath)
assert.NoError(t, err)
}
func makeSparseFile(t *testing.T, path string) {
output, err := exec.Command("truncate", "-s", "10M", path).CombinedOutput()
assert.Equal(t, "", string(output))
assert.NoError(t, err)
}
func TestValidateOutputArchiveHappySparseFile(t *testing.T) {
// go tar makes support for sparse files very hard, see also
// https://github.com/golang/go/issues/22735
tmpdir := t.TempDir()
makeSparseFile(t, filepath.Join(tmpdir, "big.img"))
testTarPath := filepath.Join(t.TempDir(), "test.tar")
output, err := exec.Command("tar", "--strip-components=1", "-C", tmpdir, "-c", "-S", "-f", testTarPath, "big.img").CombinedOutput()
assert.Equal(t, "", string(output))
assert.NoError(t, err)
err = osbuildexecutor.ValidateOutputArchive(testTarPath)
assert.NoError(t, err)
}
func TestValidateOutputArchiveSadDotDot(t *testing.T) {
testTarPath := makeTestTarfile(t, map[*tar.Header]string{
&tar.Header{Name: "file1/.."}: "some content",
})
err := osbuildexecutor.ValidateOutputArchive(testTarPath)
assert.EqualError(t, err, `name "file1/.." not clean, got "." after cleaning`)
}
func TestValidateOutputArchiveSadAbsolutePath(t *testing.T) {
testTarPath := makeTestTarfile(t, map[*tar.Header]string{
&tar.Header{Name: "/file1"}: "some content",
})
err := osbuildexecutor.ValidateOutputArchive(testTarPath)
assert.EqualError(t, err, `name "/file1" must not start with an absolute path`)
}
func TestValidateOutputArchiveSadBadType(t *testing.T) {
testTarPath := makeTestTarfile(t, map[*tar.Header]string{
&tar.Header{Name: "dev/sda", Typeflag: tar.TypeBlock}: "",
})
err := osbuildexecutor.ValidateOutputArchive(testTarPath)
assert.EqualError(t, err, `name "dev/sda" must be a file/dir, is header type '4'`)
}
func TestValidateOutputArchiveSadExecutable(t *testing.T) {
testTarPath := makeTestTarfile(t, map[*tar.Header]string{
&tar.Header{Name: "exe", Mode: 0755}: "#!/bin/sh p0wned",
})
err := osbuildexecutor.ValidateOutputArchive(testTarPath)
assert.EqualError(t, err, `name "exe" must not be executable (is mode 0755)`)
}