Skip to content
Merged
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
30 changes: 17 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,25 @@ By default, `gobuildcache` uses an on-disk cache stored in the OS default tempor
For "production" use-cases in CI, you'll want to configure `gobuildcache` to use S3 Express One Zone, or a similarly low latency distributed backend.

```bash
export BACKEND_TYPE=s3
export S3_BUCKET=$BUCKET_NAME
export GOBUILDCACHE_BACKEND_TYPE=s3
export GOBUILDCACHE_S3_BUCKET=$BUCKET_NAME
```

You'll also have to provide AWS credentials. `gobuildcache` embeds the AWS V2 S3 SDK so any method of providing credentials to that library will work, but the simplest is to use environment variables as demonstrated below.

```bash
export GOCACHEPROG=gobuildcache
export BACKEND_TYPE=s3
export S3_BUCKET=$BUCKET_NAME
export GOBUILDCACHE_BACKEND_TYPE=s3
export GOBUILDCACHE_S3_BUCKET=$BUCKET_NAME
export AWS_REGION=$BUCKET_REGION
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
go build ./...
go test ./...
```

> **Note**: All configuration environment variables support both `GOBUILDCACHE_<KEY>` and `<KEY>` forms (e.g., both `GOBUILDCACHE_S3_BUCKET` and `S3_BUCKET` work). The prefixed version takes precedence if both are set. The prefixed form is recommended to avoid conflicts with other tools. If the prefixed variable is set to an empty string, it falls through to the unprefixed version (or default).

Your credentials must have the following permissions:

```json
Expand Down Expand Up @@ -145,16 +147,18 @@ The clear commands take the same flags / environment variables as the regular `g

`gobuildcache` ships with reasonable defaults, but this section provides a complete overview of flags / environment variables that can be used to override behavior.

All environment variables support both `GOBUILDCACHE_<KEY>` and `<KEY>` forms (e.g., `GOBUILDCACHE_S3_BUCKET` or `S3_BUCKET`). The prefixed version takes precedence if both are set.

| Flag | Environment Variable | Default | Description |
|------|---------------------|---------|-------------|
| `-backend` | `BACKEND_TYPE` | `disk` | Backend type: `disk` or `s3` |
| `-lock-type` | `LOCK_TYPE` | `fslock` | Mechanism for locking: `fslock` (filesystem) or `memory` |
| `-cache-dir` | `CACHE_DIR` | `/$OS_TMP/gobuildcache/cache` | Local cache directory |
| `-lock-dir` | `LOCK_DIR` | `/$OS_TMP/gobuildcache/locks` | Local directory for storing filesystem locks |
| `-s3-bucket` | `S3_BUCKET` | (none) | S3 bucket name (required for S3) |
| `-s3-prefix` | `S3_PREFIX` | (empty) | S3 key prefix |
| `-debug` | `DEBUG` | `false` | Enable debug logging |
| `-stats` | `PRINT_STATS` | `false` | Print cache statistics on exit |
|------|----------------------|---------|-------------|
| `-backend` | `GOBUILDCACHE_BACKEND_TYPE` | `disk` | Backend type: `disk` or `s3` |
| `-lock-type` | `GOBUILDCACHE_LOCK_TYPE` | `fslock` | Locking: `fslock` or `memory` |
| `-cache-dir` | `GOBUILDCACHE_CACHE_DIR` | `$TMPDIR/gobuildcache/cache` | Local cache directory |
| `-lock-dir` | `GOBUILDCACHE_LOCK_DIR` | `$TMPDIR/gobuildcache/locks` | Filesystem lock directory |
| `-s3-bucket` | `GOBUILDCACHE_S3_BUCKET` | (none) | S3 bucket name (required for S3) |
| `-s3-prefix` | `GOBUILDCACHE_S3_PREFIX` | (empty) | S3 key prefix |
| `-debug` | `GOBUILDCACHE_DEBUG` | `false` | Enable debug logging |
| `-stats` | `GOBUILDCACHE_PRINT_STATS` | `false` | Print cache statistics on exit |


# How it Works
Expand Down
284 changes: 284 additions & 0 deletions env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package main

import (
"testing"
)

func TestGetEnvWithPrefix(t *testing.T) {
tests := []struct {
name string
key string
defaultValue string
envVars map[string]string
expected string
}{
{
name: "returns default when neither env var is set",
key: "TEST_KEY",
defaultValue: "default",
envVars: map[string]string{},
expected: "default",
},
{
name: "returns unprefixed value when only unprefixed is set",
key: "TEST_KEY",
defaultValue: "default",
envVars: map[string]string{"TEST_KEY": "unprefixed_value"},
expected: "unprefixed_value",
},
{
name: "returns prefixed value when only prefixed is set",
key: "TEST_KEY",
defaultValue: "default",
envVars: map[string]string{"GOBUILDCACHE_TEST_KEY": "prefixed_value"},
expected: "prefixed_value",
},
{
name: "prefixed value takes precedence over unprefixed",
key: "TEST_KEY",
defaultValue: "default",
envVars: map[string]string{
"TEST_KEY": "unprefixed_value",
"GOBUILDCACHE_TEST_KEY": "prefixed_value",
},
expected: "prefixed_value",
},
{
name: "works with S3_BUCKET style keys",
key: "S3_BUCKET",
defaultValue: "",
envVars: map[string]string{"GOBUILDCACHE_S3_BUCKET": "my-bucket"},
expected: "my-bucket",
},
{
name: "works with BACKEND_TYPE style keys",
key: "BACKEND_TYPE",
defaultValue: "disk",
envVars: map[string]string{"GOBUILDCACHE_BACKEND_TYPE": "s3"},
expected: "s3",
},
{
name: "empty prefixed value falls through to unprefixed",
key: "TEST_KEY",
defaultValue: "default",
envVars: map[string]string{
"TEST_KEY": "unprefixed_value",
"GOBUILDCACHE_TEST_KEY": "",
},
expected: "unprefixed_value",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear environment variables (t.Setenv auto-restores on test completion)
t.Setenv(tt.key, "")
t.Setenv("GOBUILDCACHE_"+tt.key, "")
// Set test-specific environment variables
for k, v := range tt.envVars {
t.Setenv(k, v)
}

result := getEnvWithPrefix(tt.key, tt.defaultValue)
if result != tt.expected {
t.Errorf("getEnvWithPrefix(%q, %q) = %q, want %q", tt.key, tt.defaultValue, result, tt.expected)
}
})
}
}

func TestGetEnvBoolWithPrefix(t *testing.T) {
tests := []struct {
name string
key string
defaultValue bool
envVars map[string]string
expected bool
}{
{
name: "returns default when neither env var is set",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{},
expected: false,
},
{
name: "returns true default when neither env var is set",
key: "TEST_BOOL",
defaultValue: true,
envVars: map[string]string{},
expected: true,
},
{
name: "returns unprefixed value when only unprefixed is set",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{"TEST_BOOL": "true"},
expected: true,
},
{
name: "returns prefixed value when only prefixed is set",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{"GOBUILDCACHE_TEST_BOOL": "true"},
expected: true,
},
{
name: "prefixed value takes precedence over unprefixed",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{
"TEST_BOOL": "false",
"GOBUILDCACHE_TEST_BOOL": "true",
},
expected: true,
},
{
name: "prefixed false overrides unprefixed true",
key: "TEST_BOOL",
defaultValue: true,
envVars: map[string]string{
"TEST_BOOL": "true",
"GOBUILDCACHE_TEST_BOOL": "false",
},
expected: false,
},
{
name: "accepts 1 as true",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{"GOBUILDCACHE_TEST_BOOL": "1"},
expected: true,
},
{
name: "accepts yes as true",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{"GOBUILDCACHE_TEST_BOOL": "yes"},
expected: true,
},
{
name: "accepts YES as true (case insensitive)",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{"GOBUILDCACHE_TEST_BOOL": "YES"},
expected: true,
},
{
name: "empty prefixed value falls through to unprefixed",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{
"TEST_BOOL": "true",
"GOBUILDCACHE_TEST_BOOL": "",
},
expected: true,
},
{
name: "invalid prefixed value falls through to unprefixed",
key: "TEST_BOOL",
defaultValue: false,
envVars: map[string]string{
"TEST_BOOL": "true",
"GOBUILDCACHE_TEST_BOOL": "not-a-bool",
},
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear environment variables (t.Setenv auto-restores on test completion)
t.Setenv(tt.key, "")
t.Setenv("GOBUILDCACHE_"+tt.key, "")
// Set test-specific environment variables
for k, v := range tt.envVars {
t.Setenv(k, v)
}

result := getEnvBoolWithPrefix(tt.key, tt.defaultValue)
if result != tt.expected {
t.Errorf("getEnvBoolWithPrefix(%q, %v) = %v, want %v", tt.key, tt.defaultValue, result, tt.expected)
}
})
}
}

func TestGetEnvFloatWithPrefix(t *testing.T) {
tests := []struct {
name string
key string
defaultValue float64
envVars map[string]string
expected float64
}{
{
name: "returns default when neither env var is set",
key: "TEST_FLOAT",
defaultValue: 0.5,
envVars: map[string]string{},
expected: 0.5,
},
{
name: "returns unprefixed value when only unprefixed is set",
key: "TEST_FLOAT",
defaultValue: 0.0,
envVars: map[string]string{"TEST_FLOAT": "0.75"},
expected: 0.75,
},
{
name: "returns prefixed value when only prefixed is set",
key: "TEST_FLOAT",
defaultValue: 0.0,
envVars: map[string]string{"GOBUILDCACHE_TEST_FLOAT": "0.25"},
expected: 0.25,
},
{
name: "prefixed value takes precedence over unprefixed",
key: "TEST_FLOAT",
defaultValue: 0.0,
envVars: map[string]string{
"TEST_FLOAT": "0.5",
"GOBUILDCACHE_TEST_FLOAT": "0.9",
},
expected: 0.9,
},
{
name: "returns default for invalid prefixed value, falls back to unprefixed",
key: "TEST_FLOAT",
defaultValue: 0.0,
envVars: map[string]string{
"TEST_FLOAT": "0.5",
"GOBUILDCACHE_TEST_FLOAT": "not-a-number",
},
expected: 0.5,
},
{
name: "empty prefixed value falls through to unprefixed",
key: "TEST_FLOAT",
defaultValue: 0.0,
envVars: map[string]string{
"TEST_FLOAT": "0.75",
"GOBUILDCACHE_TEST_FLOAT": "",
},
expected: 0.75,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear environment variables (t.Setenv auto-restores on test completion)
t.Setenv(tt.key, "")
t.Setenv("GOBUILDCACHE_"+tt.key, "")
// Set test-specific environment variables
for k, v := range tt.envVars {
t.Setenv(k, v)
}

result := getEnvFloatWithPrefix(tt.key, tt.defaultValue)
if result != tt.expected {
t.Errorf("getEnvFloatWithPrefix(%q, %v) = %v, want %v", tt.key, tt.defaultValue, result, tt.expected)
}
})
}
}
6 changes: 3 additions & 3 deletions examples/github_actions_s3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ jobs:
run: go install github.com/richardartoul/gobuildcache@latest
- name: Run short tests
env:
BACKEND_TYPE: s3
S3_BUCKET: EXAMPLE_BUCKET
S3_PREFIX: EXAMPLE_PREFIX
GOBUILDCACHE_BACKEND_TYPE: s3
GOBUILDCACHE_S3_BUCKET: EXAMPLE_BUCKET
GOBUILDCACHE_S3_PREFIX: EXAMPLE_PREFIX
AWS_REGION: EXAMPLE_REGION
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Expand Down
9 changes: 4 additions & 5 deletions examples/github_actions_tigris.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@ jobs:
run: go install github.com/richardartoul/gobuildcache@latest
- name: Run short tests
env:
BACKEND_TYPE: s3
S3_BUCKET: EXAMPLE_BUCKET
S3_PREFIX: EXAMPLE_PREFIX
AWS_REGION: EXAMPLE_REGION
GOBUILDCACHE_BACKEND_TYPE: s3
GOBUILDCACHE_S3_BUCKET: EXAMPLE_BUCKET
GOBUILDCACHE_S3_PREFIX: EXAMPLE_PREFIX
AWS_REGION: auto
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_ENDPOINT_URL_S3: https://t3.storage.dev
AWS_ENDPOINT_URL_IAM: https://iam.storage.dev
AWS_REGION: auto
GOCACHEPROG: gobuildcache
run: go test ./...
Loading