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
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ COMPOSE := docker compose
endif

# Phony targets are targets that don't represent actual files
.PHONY: help up down logs test clean clean-all setup-minio build
.PHONY: help up down logs test test-go clean clean-all setup-minio build

help: ## Show this help message
@echo "Usage: make [target]"
Expand Down Expand Up @@ -59,15 +59,20 @@ minio-logs: ## Follow logs for the Minio service
@echo "Following logs for Minio service (Ctrl+C to stop)..."
$(COMPOSE) -f $(COMPOSE_FILE) logs -f minio

test: up setup-minio ## Start services, ensure Minio is set up, then run tests
@echo "Running tests..."
test-go: ## Run Go unit tests
@echo "Running Go unit tests..."
go test -v ./internal/s3/...
@echo "Go unit tests completed."

test: up setup-minio test-go ## Start services, ensure Minio is set up, then run all tests
@echo "Running integration tests..."
@if [ -x "$(TEST_SCRIPT)" ]; then \
$(TEST_SCRIPT); \
else \
echo "Test script $(TEST_SCRIPT) not found or not executable. Please run 'chmod +x $(TEST_SCRIPT)'."; \
exit 1; \
fi
@echo "Tests finished. Run 'make down' to stop services."
@echo "All tests finished. Run 'make down' to stop services."

build: ## Build or rebuild the Go proxy Docker image
@echo "Building Go proxy Docker image..."
Expand Down
22 changes: 10 additions & 12 deletions internal/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import (
"github.com/sirupsen/logrus"
)

// S3Client interface for testing
type S3Client interface {
GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error)
Options() s3.Options
}

func NewS3ClientFromConfig(cfg config.FrontendAssetProxyConfig, log *logrus.Logger) *s3.Client {
var loadOpts []func(*awsconfig.LoadOptions) error
loadOpts = append(loadOpts, awsconfig.WithRegion(cfg.Region))
Expand Down Expand Up @@ -50,7 +56,7 @@ func NewS3ClientFromConfig(cfg config.FrontendAssetProxyConfig, log *logrus.Logg
}

// ProxyS3 resolves bucket/key from full path "/bucket/..." and streams from S3/MinIO
func ProxyS3(w http.ResponseWriter, r *http.Request, s3c *s3.Client, cfg config.FrontendAssetProxyConfig, full string, log *logrus.Logger) {
func ProxyS3(w http.ResponseWriter, r *http.Request, s3c S3Client, cfg config.FrontendAssetProxyConfig, full string, log *logrus.Logger) {
path := strings.TrimPrefix(full, "/")
idx := strings.IndexByte(path, '/')
if idx <= 0 || idx >= len(path)-1 {
Expand Down Expand Up @@ -102,18 +108,10 @@ func ProxyS3(w http.ResponseWriter, r *http.Request, s3c *s3.Client, cfg config.

// Map common S3 errors to HTTP status
status := s3ErrorToStatus(err)
// Optional SPA fallback: on 403/404, serve SPA entry if configured
// Ensure we only attempt the fallback once by checking current path against SPA path

if status == http.StatusNotFound || status == http.StatusForbidden {
if spa := cfg.SPAEntrypointPath; spa != "" {
spaPath := JoinPath(cfg.BucketPathPrefix, spa)
if full != spaPath { // guard against recursive fallback
if base := s3c.Options().Logger; base != nil {
logging.WithContext(ctx, base).Logf(logging.Debug, "s3 proxy request fallback to SPA entrypoint")
}
ProxyS3(w, r, s3c, cfg, spaPath, log)
return
}
if base := s3c.Options().Logger; base != nil {
logging.WithContext(ctx, base).Logf(logging.Debug, "s3 proxy request not found or forbidden bucket=%s key=%s", bucket, key)
}
}
http.Error(w, http.StatusText(status), status)
Expand Down
Loading