Skip to content

Commit 7f77147

Browse files
authored
Merge pull request #5 from wherobots/copilot/fix-cli-file-upload-issue
Fix jobs run file upload when org bucketName is returned as S3 URI
2 parents bc2aec3 + efbff86 commit 7f77147

2 files changed

Lines changed: 62 additions & 0 deletions

File tree

internal/commands/jobs.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,11 @@ func (r *jobsRunner) resolveManagedUploadTarget(ctx context.Context, uploadPathO
428428
}
429429

430430
bucket := strings.TrimSpace(gjson.GetBytes(orgBody, "fileStore.bucketName").String())
431+
// Normalize bucket: if the API returns a full S3 URI (e.g. "s3://bucket-name"),
432+
// extract only the bucket name portion.
433+
if parsedBucket, _, ok := splitS3Path(bucket); ok && parsedBucket != "" {
434+
bucket = parsedBucket
435+
}
431436
if bucket == "" {
432437
return "", "", fmt.Errorf("unable to resolve managed storage directory via API: organization fileStore bucket not available")
433438
}

internal/commands/jobs_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,63 @@ func TestJobsRunAutoUploadFallsBackToFilesDirWhenIntegrationDirFails(t *testing.
233233
}
234234
}
235235

236+
func TestJobsRunAutoUploadHandlesBucketNameAsS3URI(t *testing.T) {
237+
t.Parallel()
238+
239+
dir := t.TempDir()
240+
script := dir + "/script.py"
241+
if err := os.WriteFile(script, []byte("print('ok')\n"), 0o644); err != nil {
242+
t.Fatalf("WriteFile() error = %v", err)
243+
}
244+
245+
var dirQueryParam string
246+
var sawUpload bool
247+
var sawCreateRun bool
248+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
249+
w.Header().Set("Content-Type", "application/json")
250+
switch {
251+
case r.Method == http.MethodGet && r.URL.Path == "/organization":
252+
// Return bucketName as a full S3 URI (as some API responses may include the s3:// prefix)
253+
_, _ = io.WriteString(w, `{"fileStore":{"bucketName":"s3://managed-bucket"}}`)
254+
case r.Method == http.MethodGet && r.URL.Path == "/files/dir":
255+
dirQueryParam = r.URL.Query().Get("dir")
256+
_, _ = io.WriteString(w, `{"name":"root","path":"s3://managed-bucket/customer/root"}`)
257+
case r.Method == http.MethodPost && r.URL.Path == "/files/upload-url":
258+
_, _ = io.WriteString(w, fmt.Sprintf(`{"uploadUrl":%q}`, serverURLWithPath(serverURLFromRequest(r), "/upload")))
259+
case r.Method == http.MethodPut && r.URL.Path == "/upload":
260+
sawUpload = true
261+
w.WriteHeader(http.StatusOK)
262+
case r.Method == http.MethodPost && r.URL.Path == "/runs":
263+
sawCreateRun = true
264+
body, _ := io.ReadAll(r.Body)
265+
if !strings.Contains(string(body), "s3://managed-bucket/customer/root/test-job-001/script.py") {
266+
t.Fatalf("expected auto-uploaded s3 URI in payload, got %s", string(body))
267+
}
268+
_, _ = io.WriteString(w, `{"id":"run-auto","status":"PENDING"}`)
269+
default:
270+
http.NotFound(w, r)
271+
}
272+
}))
273+
defer server.Close()
274+
275+
root := buildJobsTestRoot(server.URL)
276+
var out bytes.Buffer
277+
root.SetOut(&out)
278+
root.SetErr(&bytes.Buffer{})
279+
root.SetArgs([]string{"jobs", "run", script, "--name", "test-job-001"})
280+
281+
if err := root.Execute(); err != nil {
282+
t.Fatalf("Execute() error = %v", err)
283+
}
284+
if !sawUpload || !sawCreateRun {
285+
t.Fatalf("expected upload and create-run calls; upload=%v create=%v", sawUpload, sawCreateRun)
286+
}
287+
// The dir query param passed to /files/dir should be s3://managed-bucket/ (not s3://s3://managed-bucket/)
288+
if dirQueryParam != "s3://managed-bucket/" {
289+
t.Fatalf("expected dir param s3://managed-bucket/, got %q", dirQueryParam)
290+
}
291+
}
292+
236293
func TestJobsRunNoUploadWithLocalScriptFails(t *testing.T) {
237294
t.Parallel()
238295

0 commit comments

Comments
 (0)