feat(agent): report and refuse Compose services built from source; honour --env-file (#7)
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
@@ -209,3 +216,77 @@ func TestSnapshot_ComposeConfigFilesIsRawLabel(t *testing.T) {
|
||||
require.Len(t, got, 1)
|
||||
assert.Equal(t, "/s/a/compose.yaml,/s/a/compose.override.yaml", got[0].ConfigFiles)
|
||||
}
|
||||
|
||||
type fakeBuildCheck struct {
|
||||
built map[string]bool
|
||||
err error
|
||||
calls int
|
||||
}
|
||||
|
||||
func (f *fakeBuildCheck) BuiltServices(context.Context, string, string, string, string) (map[string]bool, error) {
|
||||
f.calls++
|
||||
return f.built, f.err
|
||||
}
|
||||
|
||||
func buildCollector(t *testing.T, check BuildCheck, files string) (*Collector, *fakeDocker) {
|
||||
t.Helper()
|
||||
ct := composeContainer("abc", "app", "web", "app-web", "sha256:img1", "running")
|
||||
ct.Labels[labelConfigFiles] = files
|
||||
other := composeContainer("def", "app", "db", "pg:16", "sha256:img2", "running")
|
||||
other.Labels[labelConfigFiles] = files
|
||||
cli := &fakeDocker{containers: []container.Summary{ct, other}}
|
||||
return New(cli, "").WithBuildCheck(check, slog.New(slog.NewTextHandler(io.Discard, nil))), cli
|
||||
}
|
||||
|
||||
func TestSnapshot_BuiltServiceIsMarked(t *testing.T) {
|
||||
check := &fakeBuildCheck{built: map[string]bool{"web": true, "db": false}}
|
||||
c, _ := buildCollector(t, check, "/s/app/compose.yaml")
|
||||
got, err := c.Snapshot(context.Background())
|
||||
require.NoError(t, err)
|
||||
byName := map[string]protocol.Service{}
|
||||
for _, s := range got {
|
||||
byName[s.ComposeService] = s
|
||||
}
|
||||
assert.True(t, byName["web"].Built)
|
||||
assert.False(t, byName["db"].Built)
|
||||
}
|
||||
|
||||
func TestSnapshot_BuildCheckIsCachedUntilFilesChange(t *testing.T) {
|
||||
file := filepath.Join(t.TempDir(), "compose.yaml")
|
||||
require.NoError(t, os.WriteFile(file, []byte("services: {}\n"), 0o644))
|
||||
check := &fakeBuildCheck{built: map[string]bool{"web": true}}
|
||||
c, _ := buildCollector(t, check, file)
|
||||
|
||||
for range 3 {
|
||||
_, err := c.Snapshot(context.Background())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, 1, check.calls, "one check per project while files are unchanged")
|
||||
|
||||
require.NoError(t, os.WriteFile(file, []byte("services:\n web: {}\n"), 0o644))
|
||||
_, err := c.Snapshot(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, check.calls, "a changed file re-runs the check")
|
||||
}
|
||||
|
||||
func TestSnapshot_BuildCheckFailureIsNotFatalAndRetriesLater(t *testing.T) {
|
||||
check := &fakeBuildCheck{err: errors.New("no mounts")}
|
||||
c, _ := buildCollector(t, check, "/s/app/compose.yaml")
|
||||
now := time.Now()
|
||||
c.now = func() time.Time { return now }
|
||||
var logs bytes.Buffer
|
||||
c.log = slog.New(slog.NewTextHandler(&logs, nil))
|
||||
|
||||
got, err := c.Snapshot(context.Background())
|
||||
require.NoError(t, err)
|
||||
for _, s := range got {
|
||||
assert.False(t, s.Built)
|
||||
}
|
||||
_, _ = c.Snapshot(context.Background())
|
||||
assert.Equal(t, 1, check.calls, "a failure is not retried on every snapshot")
|
||||
|
||||
now = now.Add(buildRetry + time.Second)
|
||||
_, _ = c.Snapshot(context.Background())
|
||||
assert.Equal(t, 2, check.calls)
|
||||
assert.Equal(t, 1, strings.Count(logs.String(), "compose_build_check_failed"), "logged once per project")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user