Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions builder/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"

Expand All @@ -17,8 +19,10 @@ import (
)

const (
DockerHubMirror = "registry.apppackcdn.net"
CacheDirectory = "/tmp/apppack-cache"
DockerHubMirror = "registry.apppackcdn.net"
CacheDirectory = "/tmp/apppack-cache"
DefaultMaxCacheSizeGB = 7
MaxCacheSizeEnvVar = "APPPACK_MAX_CACHE_SIZE_GB"
)

func stripParamPrefix(params map[string]string, prefix string, final *map[string]string) {
Expand Down Expand Up @@ -220,8 +224,50 @@ func (b *Build) pushImages(config *containers.BuildConfig) error {
return nil
}

func dirSize(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
size += info.Size()
}
return nil
})
return size, err
}

func getMaxCacheSizeGB() int {
maxGB := DefaultMaxCacheSizeGB
if val := os.Getenv(MaxCacheSizeEnvVar); val != "" {
parsed, err := strconv.Atoi(val)
if err != nil {
fmt.Printf("WARNING: Invalid %s value '%s', using default %dGB\n", MaxCacheSizeEnvVar, val, DefaultMaxCacheSizeGB)
} else if parsed < 0 {
fmt.Printf("WARNING: Negative %s value '%d', using default %dGB\n", MaxCacheSizeEnvVar, parsed, DefaultMaxCacheSizeGB)
} else {
maxGB = parsed // 0 disables the limit
}
}
return maxGB
}

func (b *Build) archiveCache() error {
fmt.Println("Archiving build cache to S3 ...")
maxGB := getMaxCacheSizeGB()
if maxGB > 0 {
maxSize := int64(maxGB) * 1024 * 1024 * 1024
size, err := dirSize(CacheDirectory)
if err != nil {
b.Log().Warn().Err(err).Msg("failed to calculate cache directory size")
} else if size > maxSize {
sizeMB := size / (1024 * 1024)
fmt.Printf("WARNING: Cache directory is %dMB, exceeding %dGB limit. Skipping cache upload.\n", sizeMB, maxGB)
b.Log().Warn().Int64("size_bytes", size).Int("max_gb", maxGB).Msg("cache directory exceeds size limit, skipping upload")
return nil
}
}
quiet := b.Log().GetLevel() > zerolog.DebugLevel
return b.aws.SyncToS3(CacheDirectory, b.ArtifactBucket, "cache", quiet)
}
79 changes: 79 additions & 0 deletions builder/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package build

import (
"fmt"
"os"
"path/filepath"
"testing"
)

Expand Down Expand Up @@ -94,3 +96,80 @@ func TestGenerateDockerEnvStrings(t *testing.T) {
t.Errorf("expected %d elements, got %d", len(expected), len(actual))
}
}

func TestGetMaxCacheSizeGB(t *testing.T) {
tests := []struct {
name string
envValue string
want int
}{
{"unset uses default", "", DefaultMaxCacheSizeGB},
{"valid value", "10", 10},
{"zero disables limit", "0", 0},
{"invalid string falls back to default", "abc", DefaultMaxCacheSizeGB},
{"negative falls back to default", "-5", DefaultMaxCacheSizeGB},
{"float falls back to default", "7.5", DefaultMaxCacheSizeGB},
{"whitespace falls back to default", " ", DefaultMaxCacheSizeGB},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.envValue == "" {
os.Unsetenv(MaxCacheSizeEnvVar)
} else {
os.Setenv(MaxCacheSizeEnvVar, tt.envValue)
}
defer os.Unsetenv(MaxCacheSizeEnvVar)

got := getMaxCacheSizeGB()
if got != tt.want {
t.Errorf("getMaxCacheSizeGB() = %d, want %d", got, tt.want)
}
})
}
}

func TestDirSize(t *testing.T) {
// Create a temp directory with known file sizes
tmpDir, err := os.MkdirTemp("", "dirsize-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

// Create files with known sizes
file1 := filepath.Join(tmpDir, "file1.txt")
file2 := filepath.Join(tmpDir, "file2.txt")
if err := os.WriteFile(file1, make([]byte, 1000), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(file2, make([]byte, 500), 0644); err != nil {
t.Fatal(err)
}

// Create subdirectory with file
subDir := filepath.Join(tmpDir, "subdir")
if err := os.Mkdir(subDir, 0755); err != nil {
t.Fatal(err)
}
file3 := filepath.Join(subDir, "file3.txt")
if err := os.WriteFile(file3, make([]byte, 250), 0644); err != nil {
t.Fatal(err)
}

size, err := dirSize(tmpDir)
if err != nil {
t.Errorf("dirSize() error = %v", err)
}
expectedSize := int64(1750)
if size != expectedSize {
t.Errorf("dirSize() = %d, want %d", size, expectedSize)
}
}

func TestDirSizeNonExistent(t *testing.T) {
_, err := dirSize("/nonexistent/path/that/does/not/exist")
if err == nil {
t.Error("dirSize() expected error for non-existent path, got nil")
}
}
Loading