Skip to content
Draft
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions configurations/ssh/.project.data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Name": "configurations/ssh",
"Kind": "Configuration",
"CopyFiles": {
"All": [
"ssh_subsystem.dsc.yaml"
]
}
}
32 changes: 32 additions & 0 deletions configurations/ssh/ssh_subsystem.dsc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Registers DSC as an SSH subsystem so that `dsc --target ssh://<host> config ...` can reach
# this machine. Apply it from an elevated session, then restart the SSH service:
#
# dsc config set --file ssh_subsystem.dsc.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
parameters:
dscPath:
type: string
defaultValue: "[tryWhich('dsc')]"
metadata:
description: Full path of the dsc executable that sshd starts.
traceLevel:
type: string
defaultValue: warn
allowedValues:
- error
- warn
- info
metadata:
description: Highest trace level the target sends to remote clients.
resources:
- name: DSC subsystem
type: Microsoft.OpenSSH.SSHD/Subsystem
properties:
subsystem:
name: dsc
# The path is quoted because sshd fails to start a subsystem whose unquoted path has a space.
value: >-
[concat('"', parameters('dscPath'), '" --trace-format json --trace-level ', parameters('traceLevel'), ' server')]
13 changes: 13 additions & 0 deletions data.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"sshd_config.dsc.resource.json",
"sshd-subsystem.dsc.resource.json",
"sshd-subsystemList.dsc.resource.json",
"ssh_subsystem.dsc.yaml",
"y2j"
],
"macOS": [
Expand Down Expand Up @@ -70,6 +71,7 @@
"sshd_config.dsc.resource.json",
"sshd-subsystem.dsc.resource.json",
"sshd-subsystemList.dsc.resource.json",
"ssh_subsystem.dsc.yaml",
"y2j"
],
"Windows": [
Expand Down Expand Up @@ -117,6 +119,7 @@
"sshd_config.dsc.resource.json",
"sshd-subsystem.dsc.resource.json",
"sshd-subsystemList.dsc.resource.json",
"ssh_subsystem.dsc.yaml",
"windowspowershell.dsc.resource.json",
"WindowsPowerShell_adapter.dsc.resource.json",
"windowsupdate.dsc.resource.json",
Expand Down Expand Up @@ -194,6 +197,16 @@
]
}
},
{
"Name": "configurations/ssh",
"Kind": "Configuration",
"RelativePath": "configurations/ssh",
"CopyFiles": {
"All": [
"ssh_subsystem.dsc.yaml"
]
}
},
{
"Name": "dism_dsc",
"Kind": "Resource",
Expand Down
3 changes: 3 additions & 0 deletions dsc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jsonschema = { workspace = true }
path-absolutize = { workspace = true }
regex = { workspace = true }
rmcp = { workspace = true, features = [
"client",
"server",
"macros",
"transport-io",
Expand All @@ -35,6 +36,8 @@ tokio-util = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-indicatif = { workspace = true }
url = { workspace = true }
urlencoding = { workspace = true }
# workspace crate dependencies
dsc-lib = { workspace = true }
dsc-lib-telemetry = { workspace = true }
31 changes: 31 additions & 0 deletions dsc/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ version = "The version requirement for the resource, like '^1.2' or '>1.2.3, <1.
serverAbout = "Use DSC as a server over JSON-RPC (useful as MCP server)"
bicepAbout = "Use DSC as a Bicep server over gRPC"
ignoreSettingsFile = "Ignore the settings file when running the command"
target = "Run the configuration command on another machine, given as 'ssh://[user@]host[:port]'"

[main]
failedToSpawnMain = "Failed to spawn dsc main thread: %{error}"
Expand All @@ -65,6 +66,36 @@ Press any key to close this window"""
failedToStartServer = "Failed to start server: %{error}"
failedToGetSystem = "Failed to get system information"

[remote]
targetNotUri = "The value is not a URI"
targetUnsupportedScheme = "The scheme '%{scheme}' is not supported, the supported scheme is '%{supported}'"
targetMissingHost = "The URI does not name a host"
targetInvalidHost = "The host '%{host}' is not a valid host name, address or SSH host alias"
targetInvalidUser = "The user '%{user}' is not a valid user name"
targetPasswordNotAllowed = "The URI must not contain a password"
targetUnexpectedComponent = "The URI must not contain a path, query or fragment"
unsupportedCommand = "The '--target' option is only supported with 'config get', 'config set', 'config test' and 'config export', and not together with '--system-root'"
invalidTarget = "Invalid target '%{target}': %{error}"
noDocument = "A configuration document is required, use '--input' or '--file'"
includeResolvesOnTarget = "The configuration uses '%{resource}'; file paths in it are resolved on '%{target}', not on this machine"
requestTooLarge = "The configuration and parameters are %{size} bytes, which is more than the %{limit} bytes a DSC server accepts"
failedToCreateRuntime = "Failed to create async runtime: %{error}"
connecting = "Connecting to '%{target}'"
startingSsh = "Starting '%{executable}' with arguments: %{args}"
failedToStartSsh = "Failed to start the SSH client '%{executable}': %{error}"
missingSshPipes = "The SSH client was started without piped standard streams"
sshOutput = "ssh: %{line}"
trace = "%{host}: %{message}"
invalidMinimumVersion = "The minimum server version is not a valid semantic version: %{error}"
serverNotIdentified = "the server did not identify itself"
serverTooOld = "the server reports '%{name} %{version}', but 'dsc %{minimum}' or newer is required"
serverVersion = "Remote DSC version is %{version}"
connectionFailed = "Could not connect to '%{target}': %{detail}"
handshakeTimeout = "no response to the initialize request within %{seconds} seconds"
incompatibleServer = "Cannot use the DSC server on '%{target}': %{reason}"
toolFailed = "The remote operation failed: %{detail}"
missingResult = "The remote server returned a response without a result"

[resolve]
processingInclude = "Processing Include input"
invalidInclude = "Failed to deserialize Include input"
Expand Down
2 changes: 2 additions & 0 deletions dsc/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ pub struct Args {
pub progress_format: Option<ProgressFormat>,
#[clap(long, help = t!("args.ignoreSettingsFile").to_string(), num_args=0..=1, default_missing_value="true", require_equals = true)]
pub ignore_settings_file: Option<bool>,
#[clap(long, value_name = "URI", help = t!("args.target").to_string())]
pub target: Option<String>,
}

impl From<&Args> for dsc_lib::settings::sources::CommandLineData {
Expand Down
14 changes: 13 additions & 1 deletion dsc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crossterm::event;
use std::env;

pub mod args;
pub mod remote;
pub mod resolve;
pub mod resource_command;
pub mod server;
Expand Down Expand Up @@ -112,6 +113,13 @@ fn dsc_main() -> Result<(), ExitCode> {

let progress_format = args.progress_format.unwrap_or( ProgressFormat::Default );

// Validate the remote target and the command before reading any input, so that an
// unsupported combination never opens a connection.
let remote_target = match args.target.as_deref() {
Some(target) => Some(remote::resolve_target(target, &args.subcommand)?),
None => None,
};

match args.subcommand {
SubCommand::Completer { shell } => {
info!("{} {:?}", t!("main.generatingCompleter"), shell);
Expand Down Expand Up @@ -144,7 +152,11 @@ fn dsc_main() -> Result<(), ExitCode> {
(None, None) => None,
};

subcommand::config(&subcommand, &merged_parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format)?;
if let Some(target) = &remote_target {
remote::config(target, &subcommand, merged_parameters.as_deref())?;
} else {
subcommand::config(&subcommand, &merged_parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format)?;
}
},
SubCommand::Action { subcommand } => {
subcommand::action(&subcommand, progress_format)?;
Expand Down
Loading
Loading