Skip to content

Commit 336ce97

Browse files
Test presigned url for upload
1 parent 02b0763 commit 336ce97

File tree

4 files changed

+107
-54
lines changed

4 files changed

+107
-54
lines changed

dist/index.js

Lines changed: 47 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/artifacts.ts

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,12 @@ export async function fileUploadMultipart(
158158
core.info(`Multipart: initiated, uploadId obtained for ${file}`)
159159

160160
// 2. Upload all parts in parallel (MULTIPART_CONCURRENCY at a time).
161+
// Each part: GET a presigned S3 URL from the proxy (auth + tiny payload),
162+
// then PUT the part body directly to S3 — data bypasses the nginx proxy
163+
// and the node NIC entirely.
161164
const etags: {partNumber: number; etag: string}[] = []
162-
const partUrl = new URL(
163-
path.join('/upload-multipart/part/', buildName, filePath),
165+
const presignPartBaseUrl = new URL(
166+
path.join('/presign-upload-part/', buildName, filePath),
164167
baseUrl
165168
).toString()
166169

@@ -171,29 +174,59 @@ export async function fileUploadMultipart(
171174
core.info(
172175
`Multipart: uploading part ${partNumber}/${partCount} (${Math.round(partSize / 1e6)}MB) for ${file}`
173176
)
177+
178+
// Step 2a — get presigned URL (authenticated, lightweight).
179+
const presignResp = await client.get(presignPartBaseUrl, {
180+
params: {partNumber, uploadId},
181+
timeout: 30000
182+
})
183+
const s3PartUrl = (presignResp.data as string).trim()
184+
185+
// Step 2b — PUT part directly to S3 using raw https.request.
186+
// Axios re-encodes presigned URL query strings via the URL API, which
187+
// decodes %2B → + and re-serialises it as + (space in query strings).
188+
// This corrupts the AWS Signature V2 and causes 403 at Scaleway.
189+
// Using https.request preserves the query string exactly as returned
190+
// by the proxy.
174191
const partStream = fs.createReadStream(file, {start, end})
175-
let resp
176-
try {
177-
resp = await client.put(partUrl, partStream, {
178-
params: {partNumber, uploadId},
179-
headers: {'Content-Length': partSize.toString()},
180-
maxBodyLength: Infinity,
181-
maxContentLength: Infinity,
182-
maxRedirects: 0,
183-
timeout: 600000
184-
})
185-
} catch (e: unknown) {
186-
if (axios.isAxiosError(e) && e.response) {
187-
core.error(
188-
`Multipart: part ${partNumber}/${partCount} failed with status ${e.response.status}: ${JSON.stringify(e.response.data)}`
189-
)
190-
}
191-
throw e
192-
}
193-
const etag = resp.headers['etag'] as string
194-
if (!etag) {
195-
throw new Error(`No ETag returned for part ${partNumber} of ${file}`)
196-
}
192+
const s3Url = new URL(s3PartUrl)
193+
const etag = await new Promise<string>((resolve, reject) => {
194+
const req = https.request(
195+
{
196+
method: 'PUT',
197+
hostname: s3Url.hostname,
198+
port: s3Url.port ? parseInt(s3Url.port) : 443,
199+
path: s3Url.pathname + s3Url.search,
200+
headers: {'Content-Length': String(partSize)}
201+
},
202+
res => {
203+
let body = ''
204+
res.on('data', (chunk: Buffer) => {
205+
body += chunk.toString()
206+
})
207+
res.on('end', () => {
208+
if (res.statusCode === 200) {
209+
const tag = res.headers['etag'] as string
210+
if (!tag) {
211+
reject(
212+
new Error(`No ETag returned for part ${partNumber} of ${file}`)
213+
)
214+
} else {
215+
resolve(tag)
216+
}
217+
} else {
218+
reject(
219+
new Error(
220+
`Multipart: part ${partNumber}/${partCount} failed with status ${res.statusCode}: ${body}`
221+
)
222+
)
223+
}
224+
})
225+
}
226+
)
227+
req.on('error', reject)
228+
partStream.pipe(req)
229+
})
197230
etags.push({partNumber, etag})
198231
core.info(`Multipart: part ${partNumber}/${partCount} done for ${file}`)
199232
}

src/methods.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as process from 'process'
1010
import {
1111
artifactsName,
1212
artifactsPatternName,
13-
fileUpload,
13+
fileUploadPresigned,
1414
fileUploadMultipart,
1515
fileVersion,
1616
MULTIPART_THRESHOLD,
@@ -143,11 +143,7 @@ async function upload_one_file(
143143
)
144144
await fileUploadMultipart(client, url, name, file, artifactsPath)
145145
} else {
146-
const uploadUrl: string = new URL(
147-
path.join('/upload/', name, artifactsPath),
148-
url
149-
).toString()
150-
await fileUpload(client, uploadUrl, file)
146+
await fileUploadPresigned(client, url, name, file, artifactsPath)
151147
}
152148
const elapsed = (Date.now() - uploadStart) / 1000
153149
const mbps = (fileSize / 1e6 / elapsed).toFixed(1)

0 commit comments

Comments
 (0)