Repository navigation
feat(racer-object): add S3 origin and HTTP sidecar #877
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 44 commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
281c7e4
feat(racer-object): add S3 origin and HTTP sidecar
jveski 69c1192
refactor(racer-object): drop pprof support and move package to intern…
jveski 201b930
fix(racer-object): opt out of optional S3 checksums
jveski 2054dc5
chore(racer-object): tidy go.mod
jveski 5de8013
chore(racer-object): regenerate NOTICE
jveski b8c07cb
fix(racer-object): clarify and cap origin request timeout
jveski c64b7fa
fix(racer-object): bound sidecar read offsets before int64 conversion
jveski ae2d5c2
fix(racer-object): clamp parsed range numbers to int64
jveski a634118
fix(racer-object): let sidecar timeouts reach the client
jveski 011bbd4
fix(racer-object): return 200 for ranged HEAD like S3
jveski eda189d
fix(racer-object): parse range numbers as int64
jveski 13be55e
chore(racer-object): drop deployment examples
jveski 03345eb
chore(racer-object): use Containerfile and drop guide
jveski ed0175f
docs(racer-object): add design doc
jveski 793583d
chore(racer-object): match project base images
jveski 3ce402c
test(net): wait for fake informer watch registration
jveski 3e311fc
fix(racer-object): accept optional SDK checksum requests
jveski d38affe
fix(racer-object): align Go builder version
jveski 8e76475
fix(racer-object): enforce sidecar header size bound
jveski 11e59c9
fix(racer-object): classify origin transport failures as unavailable
jveski fc8a6e0
docs(racer-object): clarify region configuration
jveski c63ffa7
fix(racer-object): return S3 XML for oversized headers
jveski a1b371d
fix(racer-object): honor Trivy for local image builds
jveski 930ed0f
fix(gantry): synchronize cold-exhausted rediscovery test
jveski 5fd5ebf
fix(racer-object): compare content encodings case-insensitively
jveski 476ca33
fix(racer-object): reject edge whitespace in sidecar version IDs
jveski 7667486
style(racer-object): format sidecar test cleanup
jveski d9c24c0
fix(racer-object): reject sidecar timeout overflow
jveski 3005f6d
fix(racer-object): retain origin connections across request bursts
jveski 1598ed8
docs(racer-object): clarify origin directory ownership
jveski c30bbb8
fix(racer-object): distinguish internal pin failures from client cond…
jveski 4df7b96
fix(racer-object): handle HTTP range units correctly
jveski 9536e85
fix(racer-object): preserve HTTP deadlines for empty reads
jveski 3757f50
fix(racer-object): normalize unsupported S3 content types
jveski 58af74f
fix(racer-object): classify S3 request send failures
jveski a05f78c
fix(racer-object): validate the effective S3 endpoint
jveski e9fc44b
fix(racer-object): preserve SDK HTTP client behavior and pool cleanup
jveski 2ce61c3
fix(racer-object): drain requests before shutdown cancellation
jveski 8f33e69
fix(racer-object): add safe operational diagnostics
jveski fc86eb5
docs(racer-object): correct gateway contracts and ownership
jveski f3f541d
fix(images): pass checkout provenance to container builds
jveski f1e17da
fix(racer-object): preserve configuration diagnostic causes
jveski 6676239
Merge remote-tracking branch 'origin/main' into maint/pr877-20261013
jveski 87c7065
fix(racer-object): reject all nil S3 client kinds
jveski 54fec08
fix(racer-object): pin GET to the HEAD version and retry unpinned races
jveski d24e4f2
fix(racer-object): map S3 delete-marker 405 to not found
jveski 8fc208d
fix(racer-object): ignore invalid and unsupported Range headers
jveski 8d530a9
fix(racer-object): read GETs without Stat unless the range needs the …
jveski 44fe3dc
docs(racer-object): describe direct GET flow and concurrency limits
jveski 94eff73
fix(racer-object): sub-categorize invalid S3 responses and TLS failures
jveski aa6ba06
fix(racer-object): log sidecar 5xx responses by category
jveski 28e88bf
fix(racer-object): demote expected outcomes and rate limit diagnostics
jveski 2b382d0
fix(racer-object): route AWS SDK warnings to the process log
jveski 92586f5
docs(racer-object): describe diagnostic categories and log levels
jveski fd5ca14
fix(racer-object): reconcile 405 diagnostics and direct GET reporting…
jveski 1efd0d5
fix(racer-object): never print AWS SDK log message text
jveski 4a2fdc0
fix(racer-object): bound the final response flush by the request dead…
jveski 5bde091
fix(racer-object): report empty read completion failures
jveski 82689c1
fix(racer-object): ignore reversed ranges beyond the int64 limit
jveski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/aws/aws-sdk-go-v2/aws" | ||
| awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" | ||
| "github.com/aws/aws-sdk-go-v2/service/s3" | ||
| ) | ||
|
|
||
| type originS3Client struct { | ||
| *s3.Client | ||
| pool originConnectionPool | ||
| } | ||
|
|
||
| func (c *originS3Client) Close() error { | ||
| c.pool.close() | ||
| return nil | ||
| } | ||
|
|
||
| func newOriginS3Client(cfg aws.Config, o options) (*originS3Client, error) { | ||
| owned := &originS3Client{} | ||
|
|
||
| client := s3.NewFromConfig(cfg, func(s *s3.Options) { | ||
| s.UsePathStyle = o.pathStyle | ||
| if o.endpoint != "" { | ||
| s.BaseEndpoint = aws.String(o.endpoint) | ||
| } | ||
|
|
||
| if buildable, ok := s.HTTPClient.(*awshttp.BuildableClient); ok { | ||
| s.HTTPClient = buildable.WithTransportOptions(func(tr *http.Transport) { | ||
| tr.DialContext = owned.pool.dial(tr.DialContext) | ||
| }) | ||
| } | ||
| }) | ||
| if err := validateEndpoint(aws.ToString(client.Options().BaseEndpoint)); err != nil { | ||
| return nil, safeDiagnostic("invalid effective S3 endpoint: must be an HTTP(S) origin without credentials, query, fragment, or path prefix, with a valid port", "config", err) | ||
| } | ||
|
|
||
| owned.Client = client | ||
|
|
||
| return owned, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "errors" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/aws/aws-sdk-go-v2/aws" | ||
| ) | ||
|
|
||
| func isolateOriginAWSConfig(t *testing.T) string { | ||
| t.Helper() | ||
|
|
||
| dir := t.TempDir() | ||
| for key, value := range map[string]string{ | ||
| "AWS_CONFIG_FILE": filepath.Join(dir, "config"), "AWS_SHARED_CREDENTIALS_FILE": filepath.Join(dir, "credentials"), | ||
| "AWS_PROFILE": "", "AWS_DEFAULT_PROFILE": "", "AWS_EC2_METADATA_DISABLED": "true", | ||
| "AWS_ACCESS_KEY_ID": "test-access", "AWS_SECRET_ACCESS_KEY": "test-secret", "AWS_SESSION_TOKEN": "test-session", | ||
| "AWS_ENDPOINT_URL": "", "AWS_ENDPOINT_URL_S3": "", "AWS_IGNORE_CONFIGURED_ENDPOINT_URLS": "false", | ||
| "AWS_CA_BUNDLE": "", | ||
| } { | ||
| t.Setenv(key, value) | ||
| } | ||
|
|
||
| return dir | ||
| } | ||
|
|
||
| func TestOriginEffectiveEndpoint(t *testing.T) { | ||
| for _, source := range []string{"global-env", "service-env", "global-shared", "service-shared"} { | ||
| t.Run(source, func(t *testing.T) { | ||
| for _, endpoint := range []string{ | ||
| "", "https://s3.example", "https://s3.example/", "http://localhost:9000", | ||
| "https://private-user:private-password@s3.example", "https://s3.example/private-path", | ||
| "https://s3.example?private-query=secret", "https://s3.example#private-fragment", "https://s3.example:99999", | ||
| "https://s3.example/%zz", | ||
| } { | ||
| t.Run(endpoint, func(t *testing.T) { | ||
| dir := isolateOriginAWSConfig(t) | ||
| setOriginEndpointSource(t, dir, source, endpoint) | ||
|
|
||
| o := options{region: "us-east-1", requestTimeout: time.Minute, pathStyle: true} | ||
|
|
||
| cfg, err := originAWSConfig(t.Context(), o) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| client, err := newOriginS3Client(cfg, o) | ||
| if validateEndpoint(endpoint) != nil { | ||
| if err == nil || !strings.HasPrefix(err.Error(), "invalid effective S3 endpoint:") || strings.Contains(err.Error(), endpoint) { | ||
| t.Fatalf("error = %v, want redacted endpoint error", err) | ||
| } | ||
|
|
||
| cause := errors.Unwrap(err) | ||
| if cause == nil || cause.Error() != validateEndpoint(endpoint).Error() || !errors.Is(err, cause) || !strings.Contains(err.Error(), "category=config") { | ||
| t.Fatalf("endpoint diagnostic cause or category lost: %v", err) | ||
| } | ||
|
|
||
| if err := runOrigin(t.Context(), o); err == nil || !strings.HasPrefix(err.Error(), "invalid effective S3 endpoint:") { | ||
| t.Fatalf("startup error = %v, want endpoint rejection before directory/socket setup", err) | ||
| } | ||
|
|
||
| return | ||
| } | ||
|
|
||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| if got := aws.ToString(client.Options().BaseEndpoint); got != endpoint || !client.Options().UsePathStyle { | ||
| t.Fatalf("endpoint = %q, want %q with path style", got, endpoint) | ||
| } | ||
| }) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func setOriginEndpointSource(t *testing.T, dir, source, endpoint string) { | ||
| t.Helper() | ||
|
|
||
| switch source { | ||
| case "global-env": | ||
| t.Setenv("AWS_ENDPOINT_URL", endpoint) | ||
| case "service-env": | ||
| t.Setenv("AWS_ENDPOINT_URL_S3", endpoint) | ||
| case "global-shared", "service-shared": | ||
| text := "[default]\nendpoint_url = " + endpoint + "\n" | ||
| if source == "service-shared" { | ||
| text = "[default]\nservices = local\n[services local]\ns3 =\n endpoint_url = " + endpoint + "\n" | ||
| } | ||
|
|
||
| if err := os.WriteFile(filepath.Join(dir, "config"), []byte(text), 0o600); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| default: | ||
| t.Fatal("unknown endpoint source") | ||
| } | ||
| } | ||
|
|
||
| func TestOriginEndpointOverride(t *testing.T) { | ||
| for _, source := range []string{"global-env", "service-env", "global-shared", "service-shared"} { | ||
| t.Run(source, func(t *testing.T) { | ||
| dir := isolateOriginAWSConfig(t) | ||
| setOriginEndpointSource(t, dir, source, "https://private-user:private-password@s3.example/private-path?secret=value") | ||
|
|
||
| o := options{region: "us-east-1", requestTimeout: time.Minute, endpoint: "https://override.example"} | ||
|
|
||
| cfg, err := originAWSConfig(t.Context(), o) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| client, err := newOriginS3Client(cfg, o) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| if got := aws.ToString(client.Options().BaseEndpoint); got != o.endpoint { | ||
| t.Fatalf("endpoint = %q, want flag override %q", got, o.endpoint) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestOriginServiceEndpointPrecedence(t *testing.T) { | ||
| isolateOriginAWSConfig(t) | ||
| t.Setenv("AWS_ENDPOINT_URL", "https://private-user:private-password@s3.example/private-path") | ||
| t.Setenv("AWS_ENDPOINT_URL_S3", "https://service.example") | ||
|
|
||
| o := options{region: "us-east-1", requestTimeout: time.Minute} | ||
|
|
||
| cfg, err := originAWSConfig(t.Context(), o) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| client, err := newOriginS3Client(cfg, o) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| defer closeResource(client) | ||
|
|
||
| if got := aws.ToString(client.Options().BaseEndpoint); got != "https://service.example" { | ||
| t.Fatalf("endpoint = %q, want service environment override", got) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.