feat: update action.yml to use pwsh shell and enhance vm.py with start option; add credential management scripts
This commit is contained in:
@@ -398,6 +398,13 @@ def vm_cleanup(
|
||||
show_default=True,
|
||||
help="Informational; reserved for backend hints in Phase C.",
|
||||
)
|
||||
@click.option(
|
||||
"--start",
|
||||
"start_vm",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Start the clone headless immediately after creation.",
|
||||
)
|
||||
def vm_new(
|
||||
template: str,
|
||||
snapshot: str,
|
||||
@@ -405,6 +412,7 @@ def vm_new(
|
||||
job_id: str,
|
||||
vmrun_path: str | None,
|
||||
guest_os: str,
|
||||
start_vm: bool,
|
||||
) -> None:
|
||||
"""Create a linked clone of the template VM for an ephemeral CI build.
|
||||
|
||||
@@ -431,10 +439,10 @@ def vm_new(
|
||||
clone_dir = base / clone_name
|
||||
clone_vmx = clone_dir / f"{clone_name}.vmx"
|
||||
|
||||
click.echo("[vm new] creating linked clone...")
|
||||
click.echo(f" template : {template}")
|
||||
click.echo(f" snapshot : {snapshot}")
|
||||
click.echo(f" clone vmx: {clone_vmx}")
|
||||
click.echo("[vm new] creating linked clone...", err=True)
|
||||
click.echo(f" template : {template}", err=True)
|
||||
click.echo(f" snapshot : {snapshot}", err=True)
|
||||
click.echo(f" clone vmx: {clone_vmx}", err=True)
|
||||
|
||||
try:
|
||||
backend = _make_backend(vmrun_path)
|
||||
@@ -461,7 +469,16 @@ def vm_new(
|
||||
)
|
||||
|
||||
elapsed = time.monotonic() - start
|
||||
click.echo(f"[vm new] clone created in {elapsed:.1f}s")
|
||||
click.echo(f"[vm new] clone created in {elapsed:.1f}s", err=True)
|
||||
|
||||
if start_vm:
|
||||
click.echo("[vm new] starting VM headless...", err=True)
|
||||
try:
|
||||
backend.start(handle, headless=True)
|
||||
except BackendError as exc:
|
||||
raise click.ClickException(f"vmrun start failed: {exc}") from exc
|
||||
click.echo("[vm new] VM started.", err=True)
|
||||
|
||||
# Final stdout line: the identifier itself, so PS callers can capture it.
|
||||
click.echo(handle.identifier)
|
||||
|
||||
|
||||
@@ -45,15 +45,27 @@ class KeyringCredentialStore:
|
||||
"keyring is not installed; run `pip install keyring`."
|
||||
) from exc
|
||||
|
||||
# 1. Native lookup — works on Windows Credential Manager and backends
|
||||
# that implement get_credential(service, username=None).
|
||||
cred = keyring.get_credential(target, None)
|
||||
if cred is None:
|
||||
# Fallback: some backends only support get_password and need the
|
||||
# username to be the same as the target.
|
||||
password = keyring.get_password(self._service, target)
|
||||
if password is None:
|
||||
raise KeyError(f"Credential '{target}' not found in keyring.")
|
||||
if cred is not None:
|
||||
return Credential(username=cred.username, password=cred.password)
|
||||
|
||||
# 2. Two-entry scheme for file backends (PlaintextKeyring, etc.) where
|
||||
# get_credential(target, None) is a no-op. The username is stored
|
||||
# separately under service="{target}:meta", key="username".
|
||||
username = keyring.get_password(f"{target}:meta", "username")
|
||||
if username is not None:
|
||||
password = keyring.get_password(target, username)
|
||||
if password is not None:
|
||||
return Credential(username=username, password=password)
|
||||
|
||||
# 3. Legacy fallback: password stored under service=self._service.
|
||||
password = keyring.get_password(self._service, target)
|
||||
if password is not None:
|
||||
return Credential(username=target, password=password)
|
||||
return Credential(username=cred.username, password=cred.password)
|
||||
|
||||
raise KeyError(f"Credential '{target}' not found in keyring.")
|
||||
|
||||
|
||||
__all__ = ["Credential", "CredentialStore", "KeyringCredentialStore"]
|
||||
|
||||
Reference in New Issue
Block a user