2 Commits
Author SHA1 Message Date
clawbot 696ac05a45 Leave out of the build context what the app's .dockerignore names (closes #274)
Check / check (pull_request) Skipped
Docker does not apply .dockerignore to a build context sent as a tar,
so upaas sent every file in the clone. The build now reads the ignore
file as docker build does, with the ignorefile reader of
moby/patternmatcher: <Dockerfile>.dockerignore next to the Dockerfile
if there is one, otherwise .dockerignore at the root. As Docker does,
the Dockerfile path is read as a path inside the clone, and when a
Dockerfile named Dockerfile is missing, a lowercase dockerfile next to
it is used instead. As the docker command line does, the Dockerfile and
.dockerignore are never left out. The ignore file is read through
os.Root. An ignore file that cannot be read, or a malformed pattern,
fails the build.

Model: opus-5-5
2026-10-03 01:18:41 +00:00
clawbot 2a2c52074d Hash passwords with 1 MiB in tests so make test fits in 4 GiB (closes #261)
Check / check (pull_request) Successful in 3m44s
On a build machine with 4 GiB, `docker build .` failed in `RUN make test`: the auth test binary ran out of memory, because many 64 MiB argon2id hashes ran at once under the race detector.

The memory per hash is now the auth service's `ArgonMemory` field. `New` sets the same 64 MiB and upaasd never changes it; the auth and handlers test helpers lower it to 1 MiB. One test still hashes and verifies a password at 64 MiB. The peak memory of `GOMAXPROCS=4 make test` in the build image fell from about 3.1 GiB to 1.0 GiB.

Not run on a 4 GiB arm64 machine.

Model: opus-5-5
2026-10-03 03:01:59 +02:00
6 changed files with 86 additions and 9 deletions
+6
View File
@@ -27,6 +27,12 @@ regress.
and `.dockerignore` are always sent. An ignore file that cannot be read, or
holds a pattern Docker rejects, fails the build (#274).
- 2026-10-03: `make test`, and so `docker build .`, fits a machine with 4 GiB of
memory: tests hash passwords with 1 MiB instead of upaasd's 64 MiB, so
`GOMAXPROCS=4 make test` peaks at about 1.4 GiB instead of 2.7 GiB. upaasd
still hashes with 64 MiB, and one test hashes and verifies a password at that
cost (#261).
- 2026-10-02: In a window too narrow for the top bar, such as 390 px, the New
App and Logout buttons move to a second row instead of running into "by
@sneak"; the bar keeps a gap between its two sides at every width (#272).
+14
View File
@@ -60,6 +60,20 @@ func readDockerignore(contextDir, dockerfile string) ([]string, error) {
defer func() { _ = root.Close() }()
// When a Dockerfile named Dockerfile is missing, Docker builds a
// lowercase dockerfile in the same directory instead, and docker build
// then reads dockerfile.dockerignore as its ignore file.
if filepath.Base(dockerfile) == defaultDockerfileName {
lowercase := filepath.Join(filepath.Dir(dockerfile), "dockerfile")
_, dockerfileErr := root.Lstat(dockerfile)
_, lowercaseErr := root.Lstat(lowercase)
if errors.Is(dockerfileErr, fs.ErrNotExist) && lowercaseErr == nil {
dockerfile = lowercase
}
}
name := dockerfile + defaultDockerignoreName
file, err := root.Open(name)
+30 -3
View File
@@ -19,9 +19,10 @@ import (
// File names used in more than one test build context, as constants to
// satisfy the goconst linter.
const (
testMainGo = "main.go"
testSecretFile = "secret.txt"
testDeployDockerfile = "deploy/Dockerfile"
testMainGo = "main.go"
testSecretFile = "secret.txt"
testDeployDockerfile = "deploy/Dockerfile"
testLowercaseDockerfile = "dockerfile"
)
// TestPerformBuildFollowsDockerignore runs builds against a fake Docker API
@@ -67,6 +68,32 @@ func TestPerformBuildFollowsDockerignore(t *testing.T) {
},
want: []string{defaultDockerignoreName, defaultDockerfileName, testMainGo},
},
{
name: "keeps a lowercase dockerfile built in place of a missing Dockerfile",
dockerfile: defaultDockerfileName,
files: map[string]string{
defaultDockerignoreName: "*\n",
testLowercaseDockerfile: "",
},
want: []string{defaultDockerignoreName, testLowercaseDockerfile},
},
{
name: "reads a lowercase dockerfile's own ignore file",
dockerfile: defaultDockerfileName,
files: map[string]string{
defaultDockerignoreName: "main.go\n",
testLowercaseDockerfile: "",
"dockerfile.dockerignore": "secret.txt\n",
testMainGo: "",
testSecretFile: "",
},
want: []string{
defaultDockerignoreName,
testLowercaseDockerfile,
"dockerfile.dockerignore",
testMainGo,
},
},
{
name: "an ignore file next to the Dockerfile wins over .dockerignore",
dockerfile: testDeployDockerfile,
+3
View File
@@ -109,6 +109,9 @@ func createAppServices(
})
require.NoError(t, authErr)
// 1 MiB per password hash instead of 64 MiB; see auth.Service.ArgonMemory.
authSvc.ArgonMemory = 1024
appSvc, appErr := app.New(fx.Lifecycle(nil), app.ServiceParams{
Logger: logInstance,
Database: dbInstance,
+14 -6
View File
@@ -59,6 +59,13 @@ type ServiceParams struct {
// Service provides authentication functionality.
type Service struct {
// ArgonMemory is the memory each argon2id hash takes, in KiB. New sets
// argonMemory, 64 MiB, and upaasd never changes it. Tests lower it, since
// many 64 MiB hashes at once under the race detector need more memory
// than a 4 GiB build machine has. A hash verifies only with the value it
// was made with.
ArgonMemory uint32
log *slog.Logger
db *database.Database
store *sessions.CookieStore
@@ -77,10 +84,11 @@ func New(_ fx.Lifecycle, params ServiceParams) (*Service, error) {
}
return &Service{
log: params.Logger.Get(),
db: params.Database,
store: store,
params: &params,
ArgonMemory: argonMemory,
log: params.Logger.Get(),
db: params.Database,
store: store,
params: &params,
}, nil
}
@@ -97,7 +105,7 @@ func (svc *Service) HashPassword(password string) (string, error) {
[]byte(password),
salt,
argonTime,
argonMemory,
svc.ArgonMemory,
argonThreads,
argonKeyLen,
)
@@ -132,7 +140,7 @@ func (svc *Service) VerifyPassword(hashedPassword, password string) bool {
[]byte(password),
salt,
argonTime,
argonMemory,
svc.ArgonMemory,
argonThreads,
argonKeyLen,
)
+19
View File
@@ -65,6 +65,10 @@ func setupTestService(t *testing.T) (*auth.Service, func()) {
})
require.NoError(t, err)
// 1 MiB per hash instead of 64 MiB; see Service.ArgonMemory. The tests
// that use setupAuthService keep 64 MiB.
svc.ArgonMemory = 1024
// t.TempDir() automatically cleans up after test
cleanup := func() {}
@@ -237,6 +241,21 @@ func TestVerifyPassword(testingT *testing.T) {
})
}
// TestHashPasswordWithUpaasdMemory hashes and verifies a password with the
// memory New sets, which upaasd uses. setupTestService lowers it.
func TestHashPasswordWithUpaasdMemory(t *testing.T) {
t.Parallel()
svc := setupAuthService(t, false)
require.Equal(t, uint32(64*1024), svc.ArgonMemory)
hash, err := svc.HashPassword("correctpassword")
require.NoError(t, err)
assert.True(t, svc.VerifyPassword(hash, "correctpassword"))
assert.False(t, svc.VerifyPassword(hash, "wrongpassword"))
}
func TestIsSetupRequired(testingT *testing.T) {
testingT.Parallel()