@@ -15,21 +15,26 @@ defmodule Terrarium do
1515
1616 ## Configuration
1717
18- Configure a default provider so you don't have to pass it on every call :
18+ Configure multiple providers and set a default :
1919
20- # config/config.exs
21- config :terrarium, provider: Terrarium.Providers.Local
22-
23- # config/prod.exs
24- config :terrarium, provider: Terrarium.Daytona
20+ config :terrarium,
21+ default: :daytona,
22+ providers: [
23+ daytona: {Terrarium.Daytona, api_key: System.fetch_env!("DAYTONA_API_KEY")},
24+ e2b: {Terrarium.E2B, api_key: System.fetch_env!("E2B_API_KEY")},
25+ local: Terrarium.Providers.Local
26+ ]
2527
2628 ## Usage
2729
28- # Using the configured default provider
30+ # Uses the default provider
2931 {:ok, sandbox} = Terrarium.create(image: "debian:12")
3032
31- # Or explicitly passing a provider
32- {:ok, sandbox} = Terrarium.create(MyApp.Sandbox.Daytona, image: "debian:12")
33+ # Uses a named provider
34+ {:ok, sandbox} = Terrarium.create(:e2b, image: "debian:12")
35+
36+ # Uses an explicit provider module
37+ {:ok, sandbox} = Terrarium.create(Terrarium.Daytona, image: "debian:12", api_key: "...")
3338
3439 # Execute commands
3540 {:ok, result} = Terrarium.exec(sandbox, "echo hello")
@@ -45,43 +50,52 @@ defmodule Terrarium do
4550 alias Terrarium.Sandbox
4651
4752 @ doc """
48- Creates a new sandbox using the given provider, or the configured default.
53+ Creates a new sandbox.
54+
55+ Can be called in three ways:
56+
57+ - `Terrarium.create(opts)` — uses the configured default provider
58+ - `Terrarium.create(:name, opts)` — uses a named provider from config
59+ - `Terrarium.create(ProviderModule, opts)` — uses the module directly
60+
61+ Provider-specific options from config are merged with call-site opts,
62+ with call-site opts taking precedence.
4963
5064 ## Options
5165
5266 Options are provider-specific. Common options include:
5367
5468 - `:image` — the base image for the sandbox
5569 - `:resources` — CPU, memory, and disk configuration
70+ - `:provider` — inline provider as a module or `{module, opts}` tuple
5671
5772 ## Examples
5873
59- # With configured default provider
6074 {:ok, sandbox} = Terrarium.create(image: "debian:12")
61-
62- # With explicit provider
75+ {:ok, sandbox} = Terrarium.create(:e2b, image: "debian:12")
6376 {:ok, sandbox} = Terrarium.create(MyProvider, image: "debian:12")
6477 """
65- @ spec create ( module ( ) | keyword ( ) , keyword ( ) ) :: { :ok , Sandbox . t ( ) } | { :error , term ( ) }
78+ @ spec create ( module ( ) | atom ( ) | keyword ( ) , keyword ( ) ) :: { :ok , Sandbox . t ( ) } | { :error , term ( ) }
6679 def create ( provider_or_opts \\ [ ] , opts \\ [ ] )
6780
68- def create ( provider , opts ) when is_atom ( provider ) do
69- provider . create ( opts )
81+ def create ( name , opts ) when is_atom ( name ) and name != nil do
82+ { provider , provider_opts } = resolve_named_or_module ( name )
83+ provider . create ( Keyword . merge ( provider_opts , opts ) )
7084 end
7185
7286 def create ( opts , [ ] ) when is_list ( opts ) do
73- { provider , opts } = resolve_provider ( opts )
74- provider . create ( opts )
87+ { provider , provider_opts , opts } = resolve_from_opts ( opts )
88+ provider . create ( Keyword . merge ( provider_opts , opts ) )
7589 end
7690
7791 @ doc """
7892 Creates a new sandbox, raising on error.
7993 """
80- @ spec create! ( module ( ) | keyword ( ) , keyword ( ) ) :: Sandbox . t ( )
94+ @ spec create! ( module ( ) | atom ( ) | keyword ( ) , keyword ( ) ) :: Sandbox . t ( )
8195 def create! ( provider_or_opts \\ [ ] , opts \\ [ ] )
8296
83- def create! ( provider , opts ) when is_atom ( provider ) do
84- case create ( provider , opts ) do
97+ def create! ( name , opts ) when is_atom ( name ) and name != nil do
98+ case create ( name , opts ) do
8599 { :ok , sandbox } -> sandbox
86100 { :error , reason } -> raise "Failed to create sandbox: #{ inspect ( reason ) } "
87101 end
@@ -191,21 +205,44 @@ defmodule Terrarium do
191205 provider . ls ( sandbox , path )
192206 end
193207
194- defp resolve_provider ( opts ) do
208+ # Resolves an atom that could be either a named provider from config
209+ # or a direct provider module.
210+ defp resolve_named_or_module ( name ) do
211+ providers = Application . get_env ( :terrarium , :providers , [ ] )
212+
213+ case Keyword . fetch ( providers , name ) do
214+ { :ok , { module , opts } } -> { module , opts }
215+ { :ok , module } when is_atom ( module ) -> { module , [ ] }
216+ :error -> { name , [ ] }
217+ end
218+ end
219+
220+ # Resolves the provider from opts (inline :provider key) or falls back
221+ # to the configured default.
222+ defp resolve_from_opts ( opts ) do
195223 case Keyword . pop ( opts , :provider ) do
196224 { nil , opts } ->
197- case Application . get_env ( :terrarium , :provider ) do
198- nil ->
199- raise ArgumentError ,
200- "no default provider configured. Either pass a provider module explicitly " <>
201- "or set one in your config: config :terrarium, provider: Terrarium.Providers.Local"
202-
203- provider ->
204- { provider , opts }
205- end
206-
207- { provider , opts } ->
208- { provider , opts }
225+ { provider , provider_opts } = resolve_default! ( )
226+ { provider , provider_opts , opts }
227+
228+ { { module , provider_opts } , opts } when is_atom ( module ) ->
229+ { module , provider_opts , opts }
230+
231+ { module , opts } when is_atom ( module ) ->
232+ { provider , provider_opts } = resolve_named_or_module ( module )
233+ { provider , provider_opts , opts }
234+ end
235+ end
236+
237+ defp resolve_default! do
238+ case Application . get_env ( :terrarium , :default ) do
239+ nil ->
240+ raise ArgumentError ,
241+ "no default provider configured. Either pass a provider explicitly " <>
242+ "or configure one: config :terrarium, default: :local, providers: [local: Terrarium.Providers.Local]"
243+
244+ name ->
245+ resolve_named_or_module ( name )
209246 end
210247 end
211248end
0 commit comments