Skip to content

Commit 2911aed

Browse files
authored
Support gs:// URI scheme in hammer (#314)
* Support `gs://` URI scheme in hammer * Move `GSFetcher` to `internal/client/gcp` package
1 parent 7269acb commit 2911aed

File tree

4 files changed

+86
-5
lines changed

4 files changed

+86
-5
lines changed

internal/client/fetcher.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (h HTTPFetcher) fetch(ctx context.Context, p string) ([]byte, error) {
8181
break
8282
case http.StatusNotFound:
8383
// Need to return ErrNotExist here, by contract.
84-
return nil, fmt.Errorf("get(%q): %v", u.String(), os.ErrNotExist)
84+
return nil, fmt.Errorf("get(%q): %w", u.String(), os.ErrNotExist)
8585
default:
8686
return nil, fmt.Errorf("get(%q): %v", u.String(), r.StatusCode)
8787
}

internal/client/gcp/fetcher.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2025 The Tessera authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package gcp
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"io"
21+
22+
gcs "cloud.google.com/go/storage"
23+
"github.com/transparency-dev/tessera/api/layout"
24+
)
25+
26+
// NewGSFetcher creates a new GSFetcher for the Google Cloud Storage bucket, using
27+
// the provided GCS client.
28+
//
29+
// bucket should not contain any slash.
30+
// c may be nil, in which case a new GCS client will be used.
31+
func NewGSFetcher(ctx context.Context, bucket string, c *gcs.Client) (*GSFetcher, error) {
32+
if c == nil {
33+
var err error
34+
c, err = gcs.NewClient(ctx, gcs.WithJSONReads())
35+
if err != nil {
36+
return nil, err
37+
}
38+
}
39+
return &GSFetcher{
40+
bucket: bucket,
41+
c: c,
42+
}, nil
43+
}
44+
45+
// GSFetcher knows how to fetch log artifacts from a Google Cloud Storage bucket.
46+
type GSFetcher struct {
47+
bucket string
48+
c *gcs.Client
49+
}
50+
51+
func (f GSFetcher) fetch(ctx context.Context, p string) ([]byte, error) {
52+
r, err := f.c.Bucket(f.bucket).Object(p).NewReader(ctx)
53+
if err != nil {
54+
return nil, fmt.Errorf("getObject: failed to create reader for object %q in bucket %q: %w", p, f.bucket, err)
55+
}
56+
57+
d, err := io.ReadAll(r)
58+
if err != nil {
59+
return nil, fmt.Errorf("failed to read %q: %v", p, err)
60+
}
61+
return d, r.Close()
62+
}
63+
64+
func (f GSFetcher) ReadCheckpoint(ctx context.Context) ([]byte, error) {
65+
return f.fetch(ctx, layout.CheckpointPath)
66+
}
67+
68+
func (f GSFetcher) ReadTile(ctx context.Context, l, i uint64, p uint8) ([]byte, error) {
69+
return f.fetch(ctx, layout.TilePath(l, i, p))
70+
}
71+
72+
func (f GSFetcher) ReadEntryBundle(ctx context.Context, i uint64, p uint8) ([]byte, error) {
73+
return f.fetch(ctx, fmt.Sprintf("tile/data/%s", layout.NWithSuffix(0, i, p)))
74+
}

internal/hammer/hammer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func main() {
112112
klog.Exitf("Failed to create verifier: %v", err)
113113
}
114114

115-
f, w, err := loadtest.NewLogClients(logURL, writeLogURL, loadtest.ClientOpts{
115+
f, w, err := loadtest.NewLogClients(ctx, logURL, writeLogURL, loadtest.ClientOpts{
116116
Client: hc,
117117
BearerToken: *bearerToken,
118118
BearerTokenWrite: *bearerTokenWrite,

internal/hammer/loadtest/client.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"time"
3030

3131
"github.com/transparency-dev/tesseract/internal/client"
32+
"github.com/transparency-dev/tesseract/internal/client/gcp"
3233
"github.com/transparency-dev/tesseract/internal/types/rfc6962"
3334
"github.com/transparency-dev/tesseract/internal/types/staticct"
3435
"k8s.io/klog/v2"
@@ -51,7 +52,7 @@ type ClientOpts struct {
5152

5253
// NewLogClients returns a fetcher and a writer that will read
5354
// and write leaves to all logs in the `log_url` flag set.
54-
func NewLogClients(readLogURLs, writeLogURLs []string, opts ClientOpts) (LogReader, LeafWriter, error) {
55+
func NewLogClients(ctx context.Context, readLogURLs, writeLogURLs []string, opts ClientOpts) (LogReader, LeafWriter, error) {
5556
if len(readLogURLs) == 0 {
5657
return nil, nil, fmt.Errorf("URL(s) for reading log must be provided")
5758
}
@@ -75,7 +76,7 @@ func NewLogClients(readLogURLs, writeLogURLs []string, opts ClientOpts) (LogRead
7576

7677
fetchers := []fetcher{}
7778
for _, s := range readLogURLs {
78-
fetchers = append(fetchers, newFetcher(rootUrlOrDie(s), opts.BearerToken))
79+
fetchers = append(fetchers, newFetcher(ctx, rootUrlOrDie(s), opts.BearerToken))
7980
}
8081
writers := []httpLeafWriter{}
8182
for _, s := range writeLogURLs {
@@ -89,7 +90,7 @@ func NewLogClients(readLogURLs, writeLogURLs []string, opts ClientOpts) (LogRead
8990
}
9091

9192
// newFetcher creates a Fetcher for the log at the given root location.
92-
func newFetcher(root *url.URL, bearerToken string) fetcher {
93+
func newFetcher(ctx context.Context, root *url.URL, bearerToken string) fetcher {
9394
switch root.Scheme {
9495
case "http", "https":
9596
c, err := client.NewHTTPFetcher(root, nil)
@@ -102,6 +103,12 @@ func newFetcher(root *url.URL, bearerToken string) fetcher {
102103
return c
103104
case "file":
104105
return client.FileFetcher{Root: root.Path}
106+
case "gs":
107+
c, err := gcp.NewGSFetcher(ctx, root.Host, nil)
108+
if err != nil {
109+
klog.Exitf("NewGSFetcher: %v", err)
110+
}
111+
return c
105112
}
106113
klog.Exitf("Unknown scheme on log URL: %q", root.Scheme)
107114
return nil

0 commit comments

Comments
 (0)