Skip to content
Open
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
3 changes: 3 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"image": "mcr.microsoft.com/devcontainers/ruby:2-3.2-trixie"
}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
Here you can find the changelog for the project.

# [Unreleased]

- Make `tart.cpus`, `tart.memory`, `tart.disk`, `tart.display` optional, defaults to image values.
- Make `tart.name` optional, defaults to `<vagrantfile_folder>_<vm_name>_<timestamp>`.
- Default SSH communicator credentials to `admin` / `admin` when not explicitly configured.

# [1.0.0] - 2025-09-20

- Bump version to 1.0.0, indicating the first stable release.
Expand Down
26 changes: 3 additions & 23 deletions docs/configuration.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,18 @@ Vagrant.configure("2") do |config|
config.vm.provider "tart" do |tart|
# The image (it can be a local or remote image)
tart.image = "ghcr.io/cirruslabs/macos-sonoma-vanilla:latest"
# The name of the virtual machine
tart.name = "hello-tart"
end
# These are the default values for a Tart VM
config.ssh.username = "admin"
config.ssh.password = "admin"
end
```

{: .info }
The `image` and `name` are required fields for the Tart provider.
The `name` is used to identify the virtual machine in the Tart command line tool, so it must be unique.
The `username` and `password` are required fields for the SSH connection.
The `image` is a required field for the Tart provider.
By default, the Tart provider sets SSH communicator credentials to `admin` / `admin` when not explicitly configured.

## Customization

Some characteristics of the virtual machine can be customized:
- `name` (string): Tart VM identity; if not set, defaults to `<vagrantfile_folder>_<vm_name>_<timestamp>`; must be unique
- `gui` (boolean): Whether to show the GUI
- `audio` (boolean): Whether to enable audio
- `clipboard` (boolean): Whether to enable clipboard sharing
Expand All @@ -50,7 +45,6 @@ Some characteristics of the virtual machine can be customized:
Vagrant.configure("2") do |config|
config.vm.provider "tart" do |tart|
tart.image = "ghcr.io/cirruslabs/macos-sonoma-vanilla:latest"
tart.name = "hello-tart"
# Customize the VM
tart.gui = true
tart.audio = true
Expand All @@ -64,8 +58,6 @@ Vagrant.configure("2") do |config|
tart.vnc_experimental = true
tart.extra_run_args = ["--capture-system-keys"]
end
config.ssh.username = "admin"
config.ssh.password = "admin"
end
```

Expand Down Expand Up @@ -100,13 +92,10 @@ It is also possible to configure explicitly the host IP address for the SSH conn
Vagrant.configure("2") do |config|
config.vm.provider "tart" do |tart|
tart.image = "ghcr.io/cirruslabs/macos-sonoma-vanilla:latest"
tart.name = "hello-tart"

tart.ip_resolver = "dhcp"
end
config.ssh.host = "192.168.0.42"
config.ssh.username = "admin"
config.ssh.password = "admin"
end
```

Expand All @@ -120,10 +109,7 @@ See [Vagrant Provisioning](https://www.vagrantup.com/docs/provisioning) for more
Vagrant.configure("2") do |config|
config.vm.provider "tart" do |tart|
tart.image = "ghcr.io/cirruslabs/macos-sonoma-vanilla:latest"
tart.name = "hello-tart"
end
config.ssh.username = "admin"
config.ssh.password = "admin"

# Provision the VM by running a shell script
config.vm.provision "shell", inline: <<-SHELL
Expand All @@ -146,10 +132,7 @@ See [Vagrant Synced Folders](https://www.vagrantup.com/docs/synced-folders) for
Vagrant.configure("2") do |config|
config.vm.provider "tart" do |tart|
tart.image = "ghcr.io/cirruslabs/macos-sonoma-vanilla:latest"
tart.name = "hello-tart"
end
config.ssh.username = "admin"
config.ssh.password = "admin"

# Disable the default synced folder
config.vm.synced_folder ".", "/vagrant", disabled: true
Expand All @@ -174,15 +157,12 @@ The Tart provider supports custom registries, either authenticated or unauthenti
Vagrant.configure("2") do |config|
config.vm.provider "tart" do |tart|
tart.image = "ghcr.io/cirruslabs/macos-sonoma-vanilla:latest"
tart.name = "hello-tart"
# Specify custom registry
tart.registry = "https://ghcr.io"
# Optional username and password for authenticated registry
tart.username = "username"
tart.password = "password"
end
config.ssh.username = "admin"
config.ssh.password = "admin"
end
```

Expand Down
4 changes: 1 addition & 3 deletions docs/molecule.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ nav_order: 5
[Ansible Molecule](https://ansible.readthedocs.io/projects/molecule/) is a testing framework for Ansible roles and playbooks.

You can use Tart virtual machines in Molecule by setting the `provider` to `tart` in the `molecule.yml` file.
By default, the Tart provider uses SSH communicator credentials `admin` / `admin`.
Here is an example of a `molecule.yml` file that uses a Tart virtual machine:

```yaml
Expand All @@ -24,9 +25,6 @@ platforms:
- name: instance
cpus: 4
memory: 4096
instance_raw_config_args:
- "ssh.username = 'admin'"
- "ssh.password = 'admin'"
config_options:
synced_folder: false
provider_options:
Expand Down
12 changes: 6 additions & 6 deletions docs/remote-access.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ Take the following configuration for a Tart virtual machine:
Vagrant.configure("2") do |config|
config.vm.provider "tart" do |tart|
tart.image = "ghcr.io/cirruslabs/macos-sonoma-vanilla:latest"
tart.name = "hello-tart"
end
config.ssh.username = "admin"
config.ssh.password = "admin"
end
```

{: .info }
The Tart provider automatically defaults SSH communicator credentials to `admin` / `admin` when not explicitly configured.

{: .info }
If your image uses different credentials, set `config.ssh.username` and `config.ssh.password` in the Vagrantfile.

The virtual machine can be accessed via SSH using the `vagrant ssh` command.

```shell
Expand All @@ -42,14 +45,11 @@ Take the following configuration for a Tart virtual machine:
Vagrant.configure("2") do |config|
config.vm.provider "tart" do |tart|
tart.image = "ghcr.io/cirruslabs/macos-sonoma-vanilla:latest"
tart.name = "hello-tart"
# Disable the GUI
tart.gui = false
# Enable the VNC server
tart.vnc = true
end
config.ssh.username = "admin"
config.ssh.password = "admin"
end
```

Expand Down
10 changes: 9 additions & 1 deletion lib/vagrant-tart/action/create_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def call(env)
config = machine.provider_config
driver = machine.provider.driver
image = config.image
name = config.name
name = machine.id || config.name
name = default_name(env, machine) if name.nil? || name.empty?

list = driver.list
return @app.call(env) if list.any?(name)
Expand All @@ -36,6 +37,13 @@ def call(env)

@app.call(env)
end

private

def default_name(env, machine)
folder_name = File.basename(env[:root_path].to_s)
[folder_name, machine.name, Time.now.to_i].join("_")
end
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/vagrant-tart/action/delete_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def initialize(app, _env)

def call(env)
machine = env[:machine]
config = machine.provider_config
driver = machine.provider.driver
name = config.name
name = machine.id
return @app.call(env) if name.nil?

list = driver.list
return @app.call(env) unless list.any?(name)
Expand Down
3 changes: 1 addition & 2 deletions lib/vagrant-tart/action/forward_ports.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ def initialize(app, _env)

def call(env)
machine = env[:machine]
config = machine.provider_config
machine.provider.driver
config.name
machine.id

@app.call(env)
end
Expand Down
8 changes: 6 additions & 2 deletions lib/vagrant-tart/action/get_state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ def initialize(app, _env)

def call(env)
machine = env[:machine]
config = machine.provider_config
driver = machine.provider.driver
name = config.name
name = machine.id

if name.nil?
env[:machine_state_id] = :not_created
return @app.call(env)
end

list = driver.list
instance = list.find(name)
Expand Down
3 changes: 2 additions & 1 deletion lib/vagrant-tart/action/start_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def call(env)
machine = env[:machine]
config = machine.provider_config
driver = machine.provider.driver
name = config.name
name = machine.id
return @app.call(env) if name.nil?

list = driver.list
return @app.call(env) unless list.any?(name)
Expand Down
4 changes: 2 additions & 2 deletions lib/vagrant-tart/action/stop_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def initialize(app, _env)

def call(env)
machine = env[:machine]
config = machine.provider_config
driver = machine.provider.driver
name = config.name
name = machine.id
return @app.call(env) if name.nil?

list = driver.list
return @app.call(env) unless list.any?(name)
Expand Down
4 changes: 3 additions & 1 deletion lib/vagrant-tart/action/suspend_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ def call(env)
machine = env[:machine]
config = machine.provider_config
driver = machine.provider.driver
name = config.name
name = machine.id
return @app.call(env) if name.nil?

suspendable = config.suspendable?

list = driver.list
Expand Down
4 changes: 2 additions & 2 deletions lib/vagrant-tart/action/vnc_connect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def initialize(app, _env)

def call(env)
machine = env[:machine]
config = machine.provider_config
driver = machine.provider.driver
name = config.name
name = machine.id
return @app.call(env) if name.nil?

list = driver.list
return @app.call(env) unless list.any?(name)
Expand Down
17 changes: 8 additions & 9 deletions lib/vagrant-tart/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ def finalize!
@gui = false if @gui == UNSET_VALUE
@audio = false if @audio == UNSET_VALUE
@clipboard = false if @clipboard == UNSET_VALUE
@cpus = 1 if @cpus == UNSET_VALUE
@memory = 1024 if @memory == UNSET_VALUE
@disk = 10 if @disk == UNSET_VALUE
@display = "1024x768" if @display == UNSET_VALUE
@cpus = nil if @cpus == UNSET_VALUE
@memory = nil if @memory == UNSET_VALUE
@disk = nil if @disk == UNSET_VALUE
@display = nil if @display == UNSET_VALUE
@suspendable = false if @suspendable == UNSET_VALUE
@vnc = false if @vnc == UNSET_VALUE
@vnc_experimental = false if @vnc_experimental == UNSET_VALUE
Expand All @@ -105,7 +105,6 @@ def validate(_machine)

# Sanity checks for the image and the name
errors << I18n.t("vagrant_tart.config.image_required") if @image.nil? || @image.empty?
errors << I18n.t("vagrant_tart.config.name_required") if @name.nil? || @name.empty?

# Check that the GUI flag is a valid boolean
errors << I18n.t("vagrant_tart.config.gui_invalid") unless @gui == true || @gui == false
Expand All @@ -118,21 +117,21 @@ def validate(_machine)

# Check that CPUs is a valid number and between 1 and the maximum available CPUs
max_cpus = Etc.nprocessors
unless (@cpus.is_a? Integer) && @cpus >= 1 && @cpus <= max_cpus
unless @cpus.nil? || ((@cpus.is_a? Integer) && @cpus >= 1 && @cpus <= max_cpus)
errors << I18n.t("vagrant_tart.config.cpus_invalid", max_cpus: max_cpus)
end

# Check that memory is a valid number and between 1 and the maximum available memory
max_memory = `sysctl -n hw.memsize`.to_i / 1024 / 1024
unless (@memory.is_a? Integer) && @memory >= 1 && @memory <= max_memory
unless @memory.nil? || ((@memory.is_a? Integer) && @memory >= 1 && @memory <= max_memory)
errors << I18n.t("vagrant_tart.config.memory_invalid", max_memory: max_memory)
end

# Check that disk is a valid number and greater than 1
errors << I18n.t("vagrant_tart.config.disk_invalid") unless (@disk.is_a? Integer) && @disk >= 1
errors << I18n.t("vagrant_tart.config.disk_invalid") unless @disk.nil? || ((@disk.is_a? Integer) && @disk >= 1)

# Check that the display resolution is a valid string conforming to the format "WIDTHxHEIGHT"
errors << I18n.t("vagrant_tart.config.display_invalid") unless @display.match?(/^\d+x\d+$/)
errors << I18n.t("vagrant_tart.config.display_invalid") unless @display.nil? || @display.match?(/^\d+x\d+$/)

# Check that the suspendable flag is a valid boolean
errors << I18n.t("vagrant_tart.config.suspendable_invalid") unless @suspendable == true || @suspendable == false
Expand Down
15 changes: 12 additions & 3 deletions lib/vagrant-tart/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def ssh_info
instance_ip = nil
begin
ip_resolver = @machine.provider_config.ip_resolver
instance_ip = @machine.config.ssh.host || @driver.ip(@machine.provider_config.name, ip_resolver)
instance_ip = @machine.config.ssh.host || @driver.ip(@machine.id, ip_resolver)
rescue Errors::CommandError
@logger.warn("Failed to read guest IP #{$ERROR_INFO}")
end
Expand All @@ -82,11 +82,20 @@ def ssh_info

@logger.info("IP: #{instance_ip}")

ssh_config = @machine.config.ssh
username = ssh_config.username || "admin"
password = ssh_config.password
password = "admin" if password.nil? && ssh_config.private_key_path.nil?

# Return the information
{
info = {
host: instance_ip,
port: 22
port: 22,
username: username
}

info[:password] = password unless password.nil?
info
end

def to_s
Expand Down
2 changes: 1 addition & 1 deletion lib/vagrant-tart/util/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def suspend(name)
# @param key [String] The key to set
# @param value [String] The value to set
def set(name, key, value)
return if value == Config::UNSET_VALUE
return if value.nil? || value == Config::UNSET_VALUE

# Map the key to the correct switch
switch = nil
Expand Down
Loading