Class: Lemans::Environments::Daytona

Inherits:
Lemans::Environment show all
Defined in:
lib/lemans/environments/daytona.rb,
lib/lemans/environments/daytona/shell.rb,
lib/lemans/environments/daytona/retries.rb,
lib/lemans/environments/daytona/sdk_tweaks.rb,
lib/lemans/environments/daytona/snapshot_store.rb,
lib/lemans/environments/daytona/faraday_transfer.rb

Overview

Daytona sandboxes. Daytona builds images server-side into reusable content-named snapshots and enforces the network policy itself.

Defined Under Namespace

Modules: FaradayTransfer, Retries, SDKTweaks Classes: Shell, SnapshotStore

Constant Summary collapse

TTL_MINUTES =
120
DEFAULT_BUILD_TIMEOUT =
600
TRANSFER_TIMEOUT =

Workspace tarballs ride uploads/downloads, so transfers get their own budget through the SDK's streaming API instead of the global HTTP cap.

900
CLIENT =

Trials run concurrently, and an unsynchronised memo would build several SDK clients and leak all but one.

Concurrent::Delay.new { ::Daytona::Daytona.new(credentials) }

Constants inherited from Lemans::Environment

Lemans::Environment::DEFAULT_TIMEOUT

Instance Attribute Summary collapse

Attributes inherited from Lemans::Environment

#build_timeout, #env, #image, #labels, #network, #resources

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Lemans::Environment

#exec!

Constructor Details

#initialize(image:, resources:, network:, env: {}, labels: {}, logger: nil, build_timeout: nil) ⇒ Daytona

Returns a new instance of Daytona.



44
45
46
47
48
# File 'lib/lemans/environments/daytona.rb', line 44

def initialize(image:, resources:, network:, env: {}, labels: {}, logger: nil, build_timeout: nil)
  super(image:, resources:, network:, env:, labels:,
        build_timeout: build_timeout || DEFAULT_BUILD_TIMEOUT)
  @logger = logger
end

Instance Attribute Details

#sandboxObject (readonly)

Returns the value of attribute sandbox.



26
27
28
# File 'lib/lemans/environments/daytona.rb', line 26

def sandbox
  @sandbox
end

Class Method Details

.clientObject



32
# File 'lib/lemans/environments/daytona.rb', line 32

def self.client = CLIENT.value!

.credentialsObject

The CLI stores its key as DAYTONA_TOKEN, the SDK only reads DAYTONA_API_KEY; Config also resolves .env files, so go through it.

Raises:



36
37
38
39
40
41
42
# File 'lib/lemans/environments/daytona.rb', line 36

def self.credentials
  config = ::Daytona::Config.new
  config.api_key ||= config.read_env("DAYTONA_TOKEN")
  raise ConfigError, "no Daytona credentials: set DAYTONA_API_KEY or DAYTONA_TOKEN" unless config.api_key || config.jwt_token

  config
end

Instance Method Details

#download(remote_path, local_path) ⇒ Object



77
78
79
80
81
82
83
84
85
# File 'lib/lemans/environments/daytona.rb', line 77

def download(remote_path, local_path)
  local_path = Pathname(local_path)
  local_path.dirname.mkpath
  local_path.open("wb") do |file|
    sandbox.fs.download_file_stream(remote_path.to_s, timeout: TRANSFER_TIMEOUT) { file.write(it) }
  end
rescue *Retries::SDK_ERRORS, SystemCallError => e
  raise InfrastructureError, "daytona: could not download #{remote_path}: #{e.message}"
end

#exec(command, timeout: nil, env: {}) ⇒ Object



61
62
63
64
65
# File 'lib/lemans/environments/daytona.rb', line 61

def exec(command, timeout: nil, env: {})
  @shell.exec(command, timeout: timeout || DEFAULT_TIMEOUT, env: env)
rescue *Retries::SDK_ERRORS => e
  raise InfrastructureError, "daytona: exec failed: #{e.message}"
end

#startObject



50
51
52
53
54
55
56
57
58
59
# File 'lib/lemans/environments/daytona.rb', line 50

def start
  @sandbox = client.create(create_params, on_snapshot_create_logs: @logger)
  @shell = Shell.new(sandbox)
  self
rescue *Retries::SDK_ERRORS => e
  # A sandbox created but never handed over would bill until its TTL:
  # the caller's ensure can only stop an environment it received.
  stop
  raise InfrastructureError, "daytona: could not start sandbox: #{e.message}"
end

#stopObject

Deleting is the only cleanup that stops the meter. Never raises: cleanup must not replace an otherwise valid result with an exception.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/lemans/environments/daytona.rb', line 96

def stop
  return if @sandbox.nil?

  id = @sandbox.id
  wait = true
  begin
    @sandbox.delete(wait:)
    @sandbox = nil
  rescue StandardError => e
    # VM shutdown: confirming destruction needs threads Ruby no longer
    # grants, but the bare DELETE needs none — the meter still stops.
    if e.is_a?(ThreadError) && wait
      wait = false
      retry
    end
    warn "lemans: sandbox #{id} may still be running — delete failed: #{e.class}: #{e.message}"
  end
end

#switch_network_policy!(policy) ⇒ Object



87
88
89
90
91
92
# File 'lib/lemans/environments/daytona.rb', line 87

def switch_network_policy!(policy)
  sandbox.update_network_settings(**network_kwargs(policy, for_update: true))
  @network = policy
rescue *Retries::SDK_ERRORS => e
  raise InfrastructureError, "daytona: could not apply #{policy.mode} policy: #{e.message}"
end

#upload(local_path, remote_path) ⇒ Object



67
68
69
70
71
72
73
74
75
# File 'lib/lemans/environments/daytona.rb', line 67

def upload(local_path, remote_path)
  # An open handle, not a path string: the SDK uploads a non-existent
  # path AS ITS OWN BYTES, so a missing file must die here as ENOENT.
  Pathname(local_path).open("rb") do |file|
    sandbox.fs.upload_file_stream(file, remote_path.to_s, timeout: TRANSFER_TIMEOUT)
  end
rescue *Retries::SDK_ERRORS => e
  raise InfrastructureError, "daytona: could not upload #{local_path}: #{e.message}"
end