Skip to content

Commit ec9bd19

Browse files
Merge pull request #28 from imbue-ai/danver/fix-modal-create-command
Fix modal-create justfile command
2 parents 5e12d9b + 0596ae6 commit ec9bd19

File tree

5 files changed

+117
-172
lines changed

5 files changed

+117
-172
lines changed

.dockerignore

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Rust build artifacts (can be gigabytes)
2+
/target/
3+
target/
4+
5+
# Version control
6+
.git/
7+
.jj/
8+
9+
# IDE/editor files
10+
.idea/
11+
.vscode/
12+
*.swp
13+
*.swo
14+
*~
15+
\#*\#
16+
17+
# OS files
18+
.DS_Store
19+
Thumbs.db
20+
21+
# Python
22+
__pycache__/
23+
*.pyc
24+
.venv/
25+
venv/
26+
.pytest_cache/
27+
.ruff_cache/
28+
29+
# Node
30+
node_modules/
31+
32+
# Test output
33+
/test-results/
34+
test-results/
35+
36+
# Local caches
37+
.offload/
38+
.beads/
39+
40+
# Claude Code agent files
41+
.claude/
42+
43+
# Secrets (should never be uploaded)
44+
*.pem
45+
*_key
46+
id_rsa*
47+
id_ed25519*
48+
49+
# Local config overrides
50+
offload.local.toml

offload-modal.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ stream_output = true
99
[provider]
1010
type = "modal"
1111
app_name = "offload-sandbox"
12-
image_type = { dockerfile = ".devcontainer/Dockerfile" }
12+
dockerfile = ".devcontainer/Dockerfile"
1313
timeout_secs = 600
1414

1515
[groups.unit]

src/cache.rs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub struct ImageCacheEntry {
2727
/// ISO 8601 timestamp when the entry was created
2828
pub created_at: String,
2929

30-
/// Type of image: "dockerfile" or "preset" (e.g., "default", "rust")
30+
/// Type of image (e.g., "dockerfile")
3131
pub image_type: String,
3232
}
3333

@@ -228,12 +228,12 @@ mod tests {
228228
// Create and save a cache
229229
let mut cache = ImageCache::default();
230230
cache.insert(
231-
"default".to_string(),
231+
"dockerfile:test".to_string(),
232232
ImageCacheEntry {
233233
image_id: "im-abc123".to_string(),
234-
dockerfile_hash: None,
234+
dockerfile_hash: Some("abc123".to_string()),
235235
created_at: "2024-01-01T00:00:00Z".to_string(),
236-
image_type: "preset".to_string(),
236+
image_type: "dockerfile".to_string(),
237237
},
238238
);
239239

@@ -243,27 +243,29 @@ mod tests {
243243
let loaded_cache = ImageCache::load(temp_dir.path());
244244
assert_eq!(loaded_cache.entries.len(), 1);
245245

246-
let entry = loaded_cache.get("default").ok_or("Entry not found")?;
246+
let entry = loaded_cache
247+
.get("dockerfile:test")
248+
.ok_or("Entry not found")?;
247249
assert_eq!(entry.image_id, "im-abc123");
248-
assert_eq!(entry.image_type, "preset");
249-
assert!(entry.dockerfile_hash.is_none());
250+
assert_eq!(entry.image_type, "dockerfile");
251+
assert_eq!(entry.dockerfile_hash, Some("abc123".to_string()));
250252
Ok(())
251253
}
252254

253255
#[test]
254256
fn test_cache_get() {
255257
let mut cache = ImageCache::default();
256258
cache.insert(
257-
"default".to_string(),
259+
"dockerfile:test".to_string(),
258260
ImageCacheEntry {
259261
image_id: "im-abc123".to_string(),
260-
dockerfile_hash: None,
262+
dockerfile_hash: Some("abc123".to_string()),
261263
created_at: "2024-01-01T00:00:00Z".to_string(),
262-
image_type: "preset".to_string(),
264+
image_type: "dockerfile".to_string(),
263265
},
264266
);
265267

266-
assert!(cache.get("default").is_some());
268+
assert!(cache.get("dockerfile:test").is_some());
267269
assert!(cache.get("nonexistent").is_none());
268270
}
269271

@@ -363,28 +365,28 @@ mod tests {
363365
let mut cache = ImageCache::default();
364366

365367
cache.insert(
366-
"default".to_string(),
368+
"dockerfile:test".to_string(),
367369
ImageCacheEntry {
368370
image_id: "im-abc123".to_string(),
369-
dockerfile_hash: None,
371+
dockerfile_hash: Some("hash1".to_string()),
370372
created_at: "2024-01-01T00:00:00Z".to_string(),
371-
image_type: "preset".to_string(),
373+
image_type: "dockerfile".to_string(),
372374
},
373375
);
374376

375377
// Insert again with different ID
376378
cache.insert(
377-
"default".to_string(),
379+
"dockerfile:test".to_string(),
378380
ImageCacheEntry {
379381
image_id: "im-xyz789".to_string(),
380-
dockerfile_hash: None,
382+
dockerfile_hash: Some("hash2".to_string()),
381383
created_at: "2024-01-02T00:00:00Z".to_string(),
382-
image_type: "preset".to_string(),
384+
image_type: "dockerfile".to_string(),
383385
},
384386
);
385387

386388
// Should have the new ID
387-
let entry = cache.get("default").ok_or("Entry not found")?;
389+
let entry = cache.get("dockerfile:test").ok_or("Entry not found")?;
388390
assert_eq!(entry.image_id, "im-xyz789");
389391
assert_eq!(cache.entries.len(), 1);
390392
Ok(())

src/config/schema.rs

Lines changed: 5 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ pub enum ProviderConfig {
127127
/// Run tests using Modal cloud sandboxes.
128128
///
129129
/// Provides first-class integration with Modal for ephemeral compute.
130-
/// Supports both custom Dockerfiles and Modal's preset images.
131130
Modal(ModalProviderConfig),
132131

133132
/// Run tests using custom shell commands.
@@ -180,42 +179,6 @@ pub struct LocalProviderConfig {
180179
fn default_shell() -> String {
181180
"/bin/sh".to_string()
182181
}
183-
184-
/// Image type specification for Modal provider.
185-
///
186-
/// Determines whether to use a custom Dockerfile or a Modal preset image.
187-
/// The enum uses untagged deserialization - it checks for a "dockerfile" or
188-
/// "preset" field to determine the variant.
189-
///
190-
/// # Example: Dockerfile
191-
///
192-
/// ```toml
193-
/// [provider]
194-
/// type = "modal"
195-
/// image_type = { dockerfile = ".devcontainer/Dockerfile" }
196-
/// ```
197-
///
198-
/// # Example: Preset
199-
///
200-
/// ```toml
201-
/// [provider]
202-
/// type = "modal"
203-
/// image_type = { preset = "rust" }
204-
/// ```
205-
#[derive(Debug, Clone, Deserialize, Serialize)]
206-
#[serde(untagged)]
207-
pub enum ModalImageType {
208-
/// Build image from a Dockerfile.
209-
///
210-
/// Path is relative to the project root.
211-
Dockerfile { dockerfile: String },
212-
213-
/// Use a Modal preset image.
214-
///
215-
/// Common presets: "default", "rust", "python", "node".
216-
Preset { preset: String },
217-
}
218-
219182
/// Configuration for the Modal cloud provider.
220183
///
221184
/// Modal provides ephemeral cloud sandboxes with first-class Docker support.
@@ -228,20 +191,10 @@ pub enum ModalImageType {
228191
/// [provider]
229192
/// type = "modal"
230193
/// app_name = "offload-sandbox"
231-
/// image_type = { dockerfile = ".devcontainer/Dockerfile" }
194+
/// dockerfile = ".devcontainer/Dockerfile"
232195
/// working_dir = "/workspace"
233196
/// timeout_secs = 600
234197
/// ```
235-
///
236-
/// # Example: Preset Image
237-
///
238-
/// ```toml
239-
/// [provider]
240-
/// type = "modal"
241-
/// app_name = "test-runner"
242-
/// image_type = { preset = "rust" }
243-
/// timeout_secs = 3600
244-
/// ```
245198
#[derive(Debug, Clone, Deserialize, Serialize)]
246199
pub struct ModalProviderConfig {
247200
/// Modal app name for the sandbox.
@@ -257,8 +210,8 @@ pub struct ModalProviderConfig {
257210

258211
/// Image configuration for the sandbox.
259212
///
260-
/// Either a path to a Dockerfile or a Modal preset name.
261-
pub image_type: ModalImageType,
213+
/// A path to the Dockerfile on which this image is based
214+
pub dockerfile: String,
262215

263216
/// Working directory inside the sandbox.
264217
///
@@ -646,7 +599,7 @@ mod tests {
646599
[provider]
647600
type = "modal"
648601
app_name = "offload-sandbox"
649-
image_type = { dockerfile = ".devcontainer/Dockerfile" }
602+
dockerfile = ".devcontainer/Dockerfile"
650603
timeout_secs = 600
651604
652605
[groups.test]
@@ -664,56 +617,7 @@ mod tests {
664617
assert_eq!(modal_config.app_name, "offload-sandbox");
665618
assert_eq!(modal_config.timeout_secs, 600);
666619
assert!(modal_config.working_dir.is_none());
667-
668-
assert!(
669-
matches!(&modal_config.image_type, ModalImageType::Dockerfile { .. }),
670-
"Expected Dockerfile image type"
671-
);
672-
673-
if let ModalImageType::Dockerfile { dockerfile } = &modal_config.image_type {
674-
assert_eq!(dockerfile, ".devcontainer/Dockerfile");
675-
}
676-
}
677-
678-
Ok(())
679-
}
680-
681-
#[test]
682-
fn test_modal_provider_with_preset() -> Result<(), Box<dyn std::error::Error>> {
683-
let toml = r#"
684-
[offload]
685-
max_parallel = 4
686-
687-
[provider]
688-
type = "modal"
689-
app_name = "test-runner"
690-
image_type = { preset = "rust" }
691-
working_dir = "/workspace"
692-
693-
[groups.test]
694-
type = "cargo"
695-
"#;
696-
697-
let config: Config = toml::from_str(toml)?;
698-
699-
assert!(
700-
matches!(&config.provider, ProviderConfig::Modal(_)),
701-
"Expected Modal provider"
702-
);
703-
704-
if let ProviderConfig::Modal(modal_config) = &config.provider {
705-
assert_eq!(modal_config.app_name, "test-runner");
706-
assert_eq!(modal_config.timeout_secs, 3600); // default value
707-
assert_eq!(modal_config.working_dir, Some(PathBuf::from("/workspace")));
708-
709-
assert!(
710-
matches!(&modal_config.image_type, ModalImageType::Preset { .. }),
711-
"Expected Preset image type"
712-
);
713-
714-
if let ModalImageType::Preset { preset } = &modal_config.image_type {
715-
assert_eq!(preset, "rust");
716-
}
620+
assert_eq!(&modal_config.dockerfile, ".devcontainer/Dockerfile");
717621
}
718622

719623
Ok(())

0 commit comments

Comments
 (0)