Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The application follows a command-line interface pattern using the Cobra library

- **main.go**: Core CLI structure with command definitions and configuration management
- **auth.go**: OAuth2 device flow authentication with JWT token handling
- **jobs.go**: Job result operations (list and download job files) via the Hasura REST job endpoint and the job file download endpoint
- **datasets.go**: Dataset operations (list, download, upload, status) with REST API integration
- **registries.go**: Registry operations (list, config, add, update, registrator) with REST API integration
- **packages.go**: Package operations (search, dependency) with REST API primary path (`/api/v1/ui/packages/info`), GraphQL fallback, and documentation API (`/docs/{registry}/{package}/stable/pkg.json`)
Expand Down Expand Up @@ -45,6 +46,7 @@ The application follows a command-line interface pattern using the Cobra library

3. **Command Structure**:
- `jh auth`: Authentication commands (login, refresh, status, env)
- `jh job result`: Job result files (list, download)
- `jh dataset`: Dataset operations (list, download, upload, status)
- `jh registry`: Registry operations (list, config — all via REST API)
- `jh registry config`: Show registry JSON config by name; subcommands add/update accept JSON via stdin or `--file`
Expand Down Expand Up @@ -134,6 +136,16 @@ go run . package dependency JuMP --json
go run . package info JuMP --json
```

### Test job result operations
```bash
go run . job result list jr-abc123xyz
go run . job result list --all jr-abc123xyz
go run . job result list --json jr-abc123xyz
go run . job result download jr-abc123xyz
go run . job result download jr-abc123xyz results.tar.gz -o previous-run.tar.gz
go run . job result download jr-abc123xyz code.jl --type source -o src/
```

### Test registry operations
```bash
go run . registry list
Expand Down Expand Up @@ -562,6 +574,15 @@ jh run setup

## Implementation Details

### Job Results (`jobs.go`)

- `fetchJobDetails` GETs `/api/rest/jobs/{jobname}` (Hasura REST, `X-Hasura-Role: jhuser`); the response is `{"details": [job]}`, an empty list meaning the job does not exist or is not visible. Each job's `files` entries have `name`, `type` (`input`, `source`, `project`, `result`), `size` and `hash` (both null while an upload is in progress) and `upload_timestamp`
- `jh job result list` shows `result` files by default, `--all` for every type; `--json` prints `{"job", "status", "files"}`
- `planJobDownloads` validates `--type`, selects files (download-all skips pending files with a stderr notice; a named pending file is an error) and resolves every destination up front, rejecting two files that would land on the same path. Download-all always treats `-o` as a directory; a named download treats it as a file unless it is an existing directory or ends in a separator
- `downloadJobFile` GETs `/jobs/{jobname}/files/{filename}?filetype=<type>`, which 302-redirects to a short-lived presigned object-store URL. `newJobHTTPClient` strips `Authorization` and `Referer` on every redirect: Go only strips Authorization cross-domain, and the object store rejects a second auth mechanism. It bounds only `ResponseHeaderTimeout`, not the whole request, so large downloads are not cut off
- Downloads stream to a temp file in the destination directory, are checked against the recorded `size` and `sha2_256` hash (stored as hex; base64 also accepted), then moved into place. Files with no usable hash are reported as "(not verified)". Without `--force`, `moveJobFileIntoPlace` uses `os.Link` so a file created at the destination during the download is not replaced
- Destination file names use only the base name of the server-supplied file name and must pass `filepath.IsLocal` (which also rejects Windows device names), so they cannot escape the output directory

### Registry Operations (`registries.go`)

**Shared helpers:**
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ A command-line interface for interacting with JuliaHub, a platform for Julia com

- **Authentication**: OAuth2 device flow authentication with JWT token handling
- **Dataset Management**: List, download, upload, and check status of datasets
- **Job Results**: List and download a job's result files (and its other input/source/project files)
- **Package Management**: Search and explore Julia packages across registries via REST API (GraphQL fallback), with dependency analysis
- **Registry Management**: List, add, and update Julia package registries
- **Project Management**: List and filter projects using GraphQL API
Expand Down Expand Up @@ -155,6 +156,11 @@ go build -o jh .
- `jh dataset upload [dataset-id] <file-path>` - Upload a dataset
- `jh dataset status <dataset-id> [version]` - Show dataset status

### Job Results (`jh job result`)

- `jh job result list <job-name>` - List a job's result files (`--all` for every job file, `--json` for scripts)
- `jh job result download <job-name> [file-name]` - Download one or all result files (`-o` destination, `--force` to overwrite, `--type` for other job file types)

### Package Management (`jh package`)

- `jh package search [search-term]` - Search for Julia packages
Expand Down Expand Up @@ -360,6 +366,27 @@ jh dataset upload --new ./my-data.tar.gz
jh dataset upload my-dataset ./updated-data.tar.gz
```

### Job Result Operations

A job produces a result file by setting `ENV["RESULTS_FILE"]` to a file path; JuliaHub uploads it when the job finishes.

```bash
# List a job's result files
jh job result list jr-abc123xyz

# List every file attached to the job, with its type
jh job result list --all jr-abc123xyz

# Download all result files into the current directory
jh job result download jr-abc123xyz

# Download one result file to a specific path (e.g. to reuse a previous run's output)
jh job result download jr-abc123xyz results.tar.gz -o previous-run.tar.gz

# Download into a directory, overwriting existing files
jh job result download jr-abc123xyz -o results/ --force
```

### Package Operations

```bash
Expand Down
15 changes: 15 additions & 0 deletions e2e/job_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,18 @@ func TestJobList(t *testing.T) {
t.Error("job list produced no output")
}
}

// `jh job result list` against a job name that cannot exist: exercises the real
// GET /api/rest/jobs/{jobname} response shape (an empty "details" list) without
// depending on a job with results on the instance.
func TestJobResultListUnknownJob(t *testing.T) {
requireCreds(t)
res := runJH(t, "job", "result", "list", "jr-jhe2enonexistent")
if res.exitCode == 0 {
t.Fatalf("job result list for an unknown job exited 0\nstdout: %s", res.stdout)
}
skipIfUnsupported(t, res)
if !strings.Contains(res.stderr, "not found") {
t.Errorf("want a not-found error, got stderr: %s", res.stderr)
}
}
Loading
Loading