feat: master/agent phase 2 — jobs and manual update (#7) #20
@@ -62,7 +62,7 @@ type Collector struct {
|
|||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
cache map[string]buildEntry // by Compose project
|
cache map[string]buildEntry // by Compose project
|
||||||
warned map[string]bool // projects whose check failure was logged
|
warned map[string]bool // logged warnings: "<project>" (check failed) and "<project>/<service>" (service not in config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildEntry is the cached outcome of a build check. It is valid as long as
|
// buildEntry is the cached outcome of a build check. It is valid as long as
|
||||||
@@ -313,15 +313,22 @@ func (c *Collector) builtServices(ctx context.Context, project string, l stackLa
|
|||||||
built, err := c.check.BuiltServices(ctx, project, l.workingDir, l.configFiles, l.envFile)
|
built, err := c.check.BuiltServices(ctx, project, l.workingDir, l.configFiles, l.envFile)
|
||||||
c.cache[project] = buildEntry{key: key, built: built, err: err, at: c.now()}
|
c.cache[project] = buildEntry{key: key, built: built, err: err, at: c.now()}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
delete(c.warned, project)
|
// Forget the project's warnings so they are logged again if the
|
||||||
|
// problem returns. Project names cannot contain "/".
|
||||||
|
for k := range c.warned {
|
||||||
|
if k == project || strings.HasPrefix(k, project+"/") {
|
||||||
|
delete(c.warned, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c.warnOnceLocked(project, "compose_build_check_failed", "project", project, "error", err)
|
c.warnOnceLocked(project, "compose_build_check_failed", "project", project, "error", err)
|
||||||
}
|
}
|
||||||
return built
|
return built
|
||||||
}
|
}
|
||||||
|
|
||||||
// warnOnce logs msg the first time it is called for key (until the next
|
// warnOnce logs msg the first time it is called for key. key is a project
|
||||||
// successful check of the project resets it).
|
// name (its build check failed) or "<project>/<service>" (the service is not
|
||||||
|
// in the project's config); a successful check of the project forgets both.
|
||||||
func (c *Collector) warnOnce(key, msg string, args ...any) {
|
func (c *Collector) warnOnce(key, msg string, args ...any) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|||||||
@@ -309,3 +309,30 @@ func TestSnapshot_ServiceAbsentFromConfigFailsClosed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, 1, strings.Count(logs.String(), "compose_service_not_in_config"), "logged once per project")
|
assert.Equal(t, 1, strings.Count(logs.String(), "compose_service_not_in_config"), "logged once per project")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSnapshot_MissingServiceIsLoggedAgainAfterItCameBack(t *testing.T) {
|
||||||
|
file := filepath.Join(t.TempDir(), "compose.yaml")
|
||||||
|
write := func(body string) {
|
||||||
|
require.NoError(t, os.WriteFile(file, []byte(body), 0o644))
|
||||||
|
}
|
||||||
|
write("v1")
|
||||||
|
check := &fakeBuildCheck{built: map[string]bool{"db": false}} // "web" missing
|
||||||
|
c, _ := buildCollector(t, check, file)
|
||||||
|
var logs bytes.Buffer
|
||||||
|
c.log = slog.New(slog.NewTextHandler(&logs, nil))
|
||||||
|
snap := func() {
|
||||||
|
_, err := c.Snapshot(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
snap()
|
||||||
|
write("v2 changed")
|
||||||
|
check.built = map[string]bool{"db": false, "web": false} // back
|
||||||
|
snap()
|
||||||
|
assert.Empty(t, c.warned, "warnings are forgotten once the project checks out")
|
||||||
|
write("v3 changed again")
|
||||||
|
check.built = map[string]bool{"db": false} // missing again
|
||||||
|
snap()
|
||||||
|
|
||||||
|
assert.Equal(t, 2, strings.Count(logs.String(), "compose_service_not_in_config"))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user