Skip to content

Commit 5469cdf

Browse files
committed
fix(data-drains): address bugbot review of 6336948
- gcs: allow 1-char dot-separated bucket components (e.g. "a.bucket") to match GCS naming rules — overall name is 3-63 (or up to 222 with dots), but per-component minimum is 1 per Google's spec - bigquery: drain the 401 response body before re-issuing the request with a refreshed token so undici can return the socket to the keep-alive pool - snowflake: hoist getJwt() above the perAttempt timer in executeStatement so JWT signing doesn't eat the network budget (matches the order already used in pollStatement)
1 parent 6336948 commit 5469cdf

4 files changed

Lines changed: 10 additions & 7 deletions

File tree

apps/sim/lib/api/contracts/data-drains.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ const S3_BUCKET_NAME_RE = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/
88
const S3_IPV4_LIKE_RE = /^(\d{1,3}\.){3}\d{1,3}$/
99
const AWS_REGION_RE = /^[a-z]{2,}(-[a-z]+)+-\d+$/
1010
/** GCS bucket component: lowercase alnum + _ / -, start/end alnum. Mirrors gcs.ts. */
11-
const GCS_BUCKET_COMPONENT_RE = /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/
11+
const GCS_BUCKET_COMPONENT_RE = /^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$/
1212
const GOOGLE_RESERVED_PREFIX_RE = /^(goog|google|g00gle)/i
1313
const GOOGLE_CONTAINS_RE = /(google|g00gle)/i
1414
function validateGcsBucketComponents(v: string): string | null {
1515
if (v.length < 3 || v.length > 222) return 'bucket must be 3-222 characters'
1616
const components = v.split('.')
1717
for (const c of components) {
18-
if (c.length < 3 || c.length > 63) {
19-
return 'each dot-separated component must be 3-63 characters'
18+
if (c.length < 1 || c.length > 63) {
19+
return 'each dot-separated component must be 1-63 characters'
2020
}
2121
if (!GCS_BUCKET_COMPONENT_RE.test(c)) {
2222
return 'each component must be lowercase, start/end alphanumeric, letters/digits/_/- only'

apps/sim/lib/data-drains/destinations/bigquery.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ async function insertAll(input: InsertAllInput): Promise<void> {
206206
if (response.status === 401 && !refreshedOnce) {
207207
refreshedOnce = true
208208
logger.debug('BigQuery returned 401; refreshing access token and retrying once')
209+
/** Drain the 401 body before discarding so undici can return the socket to the keep-alive pool. */
210+
await response.text().catch(() => '')
209211
response = await postInsertAll(input, url, body, true)
210212
}
211213
if (!RETRYABLE_STATUSES.has(response.status)) break

apps/sim/lib/data-drains/destinations/gcs.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const MAX_CUSTOM_METADATA_BYTES = 8 * 1024
2727
/** GCS object names are at most 1024 bytes when UTF-8 encoded (flat-namespace buckets). */
2828
const MAX_OBJECT_NAME_BYTES = 1024
2929

30-
const GCS_BUCKET_COMPONENT_RE = /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/
30+
const GCS_BUCKET_COMPONENT_RE = /^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$/
3131
const IPV4_LIKE_RE = /^(\d{1,3}\.){3}\d{1,3}$/
3232
const GOOGLE_RESERVED_RE = /^(goog|google|g00gle)/i
3333
const GOOGLE_CONTAINS_RE = /(google|g00gle)/i
@@ -36,8 +36,8 @@ function validateGcsBucketComponents(v: string): string | null {
3636
if (v.length < 3 || v.length > 222) return 'bucket must be 3-222 characters'
3737
const components = v.split('.')
3838
for (const c of components) {
39-
if (c.length < 3 || c.length > 63) {
40-
return 'each dot-separated component must be 3-63 characters'
39+
if (c.length < 1 || c.length > 63) {
40+
return 'each dot-separated component must be 1-63 characters'
4141
}
4242
if (!GCS_BUCKET_COMPONENT_RE.test(c)) {
4343
return 'each component must be lowercase, start/end alphanumeric, letters/digits/_/- only'

apps/sim/lib/data-drains/destinations/snowflake.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,9 @@ async function executeStatement(input: ExecuteInput): Promise<void> {
227227
let lastError: unknown
228228
for (let attempt = 1; attempt <= EXECUTE_MAX_ATTEMPTS; attempt++) {
229229
if (input.signal.aborted) throw input.signal.reason ?? new Error('Aborted')
230-
const perAttempt = AbortSignal.any([input.signal, AbortSignal.timeout(PER_ATTEMPT_TIMEOUT_MS)])
230+
/** Acquire JWT before starting the per-attempt timer so token signing doesn't eat the network budget (mirrors pollStatement). */
231231
const jwt = await input.getJwt()
232+
const perAttempt = AbortSignal.any([input.signal, AbortSignal.timeout(PER_ATTEMPT_TIMEOUT_MS)])
232233
const params = new URLSearchParams({ requestId })
233234
if (attempt > 1) params.set('retry', 'true')
234235
const url = `${baseUrl}?${params.toString()}`

0 commit comments

Comments
 (0)