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
25 changes: 23 additions & 2 deletions architecture/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,29 @@ revocation and the gateway's reauthorization-required recovery state.
Tmachine environments define the guest machine and runtime setup, while named
installers define how OpenShell is installed. This keeps the runtime mode
independent from binary or package installation and lets multiple installers
reuse the same prepared setup disk. The test command is
`tmachine test <environment> <installer> <testsuite>`.
reuse the same prepared setup disk. The `none` installer skips OpenShell
installation and boots the prepared environment directly.

### Interactive tmachine shell

The test command is `tmachine test <environment> <installer> <testsuite>`. The
`shell` testsuite prepares the selected environment and installer, then opens an
interactive SSH session in the disposable guest for manual debugging.

Start an Ubuntu Docker guest without installing OpenShell:

```shell
nix run .#tmachine -- test ubuntu-docker-rootful none shell
```

Replace `none` with `deb` to install the locally staged Debian package before
opening the shell:

```shell
nix run .#tmachine -- test ubuntu-docker-rootful deb shell
```

Exit the SSH session to shut down and discard the disposable guest.

The `tests/tmachine` setup and install caches include a digest of the
entire directory containing `ANSIBLE_CONFIG`, including local roles, task
Expand Down
18 changes: 18 additions & 0 deletions tests/ansible/playbooks/shell.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

---
- name: Prepare an interactive tmachine shell
hosts: all
gather_facts: false
tasks:
- name: Wait for SSH
ansible.builtin.wait_for_connection:

- name: Set the tmachine SSH password
become: true
ansible.builtin.command:
argv: [chpasswd]
stdin: tmachine:tmachine
no_log: true
changed_when: false
12 changes: 12 additions & 0 deletions tests/config.nix
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ let
];

installers = [
{
name = "none";
use_galaxy = false;
playbooks = [ ];
inputs = { };
}
{
name = "binaries";
use_galaxy = false;
Expand Down Expand Up @@ -99,6 +105,12 @@ let
];

testsuites = [
{
name = "shell";
playbooks = [ "ansible/playbooks/shell.yaml" ];
inputs = { };
interactive = true;
}
{
name = "conformance";
playbooks = [ "ansible/playbooks/conformance/cli.yaml" ];
Expand Down
2 changes: 2 additions & 0 deletions tests/tmachine/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub struct Testsuite {
pub name: String,
pub playbooks: Vec<PathBuf>,
pub inputs: BTreeMap<String, PathBuf>,
#[serde(default)]
pub interactive: bool,
}

impl Config {
Expand Down
36 changes: 35 additions & 1 deletion tests/tmachine/src/qemu/test.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use std::process::Stdio;

use anyhow::{Context, Result};
use tempfile::tempdir;
use tokio::process::Command;

use crate::config::{Environment, Installer, Machine, Testsuite};
use anyhow::Result;

use super::img::QemuImage;
use super::install::install;
Expand All @@ -25,6 +28,37 @@ pub async fn test(

run_playbooks(&testsuite.playbooks, &testsuite.inputs).await?;

if testsuite.interactive {
println!("Opening an SSH shell in the tmachine VM.");
let ssh_status = Command::new("sshpass")
.env("SSHPASS", "tmachine")
.args([
"-e",
"ssh",
"-tt",
"-p",
"2222",
"-o",
"StrictHostKeyChecking=no",
"-o",
"UserKnownHostsFile=/dev/null",
"-o",
"LogLevel=ERROR",
"tmachine@127.0.0.1",
])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.await
.context("open SSH shell in tmachine VM")?;

vm.shutdown().await;
vm.wait().await;
anyhow::ensure!(ssh_status.success(), "SSH shell exited with {ssh_status}");
return Ok(());
}

vm.shutdown().await;
vm.wait().await;
Ok(())
Expand Down
Loading