Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
42 changes: 42 additions & 0 deletions docs/reference/schemas/config/functions/cidrHost.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,48 @@ messages: []
hadErrors: false
```

### Example 4 - IPv6 host address allocation

This configuration demonstrates calculating host addresses within an IPv6
network, showing that the function supports both IPv4 and IPv6 address families.

```yaml
# cidrHost.example.4.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
parameters:
ipv6Network:
type: string
defaultValue: 2001:db8::/64
resources:
- name: IPv6 host addresses
type: Microsoft.DSC.Debug/Echo
properties:
output:
network: "[parameters('ipv6Network')]"
router: "[cidrHost(parameters('ipv6Network'), 1)]"
server1: "[cidrHost(parameters('ipv6Network'), 10)]"
server2: "[cidrHost(parameters('ipv6Network'), 11)]"
```

```bash
dsc config get --file cidrHost.example.4.dsc.config.yaml
```

```yaml
results:
- name: IPv6 host addresses
type: Microsoft.DSC.Debug/Echo
result:
actualState:
output:
network: 2001:db8::/64
router: 2001:db8::1
server1: 2001:db8::a
server2: 2001:db8::b
messages: []
hadErrors: false
```

## Parameters

### cidrNotation
Expand Down
45 changes: 45 additions & 0 deletions docs/reference/schemas/config/functions/cidrSubnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,51 @@ messages: []
hadErrors: false
```

### Example 4 - IPv6 subnet allocation

This configuration demonstrates creating IPv6 subnets from a larger IPv6 address
block, showing support for both IPv4 and IPv6 address families.

```yaml
# cidrSubnet.example.4.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
parameters:
ipv6BaseNetwork:
type: string
defaultValue: 2001:db8::/32
subnetPrefix:
type: int
defaultValue: 48
resources:
- name: IPv6 subnets
type: Microsoft.DSC.Debug/Echo
properties:
output:
baseNetwork: "[parameters('ipv6BaseNetwork')]"
subnet0: "[cidrSubnet(parameters('ipv6BaseNetwork'), parameters('subnetPrefix'), 0)]"
subnet1: "[cidrSubnet(parameters('ipv6BaseNetwork'), parameters('subnetPrefix'), 1)]"
subnet10: "[cidrSubnet(parameters('ipv6BaseNetwork'), parameters('subnetPrefix'), 10)]"
```

```bash
dsc config get --file cidrSubnet.example.4.dsc.config.yaml
```

```yaml
results:
- name: IPv6 subnets
type: Microsoft.DSC.Debug/Echo
result:
actualState:
output:
baseNetwork: 2001:db8::/32
subnet0: 2001:db8::/48
subnet1: 2001:db8:1::/48
subnet10: 2001:db8:a::/48
messages: []
hadErrors: false
```

## Parameters

### cidrNotation
Expand Down
38 changes: 36 additions & 2 deletions docs/reference/schemas/config/functions/parseCidr.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,41 @@ messages: []
hadErrors: false
```

### Example 4 - Parse IPv6 CIDR notation

This configuration demonstrates parsing IPv6 CIDR notation, showing that the
function supports both IPv4 and IPv6 address families.

```yaml
# parseCidr.example.4.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Parse IPv6 network
type: Microsoft.DSC.Debug/Echo
properties:
output: "[parseCidr('2001:db8::/32')]"
```

```bash
dsc config get --file parseCidr.example.4.dsc.config.yaml
```

```yaml
results:
- name: Parse IPv6 network
type: Microsoft.DSC.Debug/Echo
result:
actualState:
output:
network: 2001:db8::
netmask: ffff:ffff::
firstUsable: 2001:db8::
lastUsable: 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff
cidr: 32
messages: []
hadErrors: false
```

## Parameters

### cidrNotation
Expand Down Expand Up @@ -195,9 +230,8 @@ For **IPv6** addresses:

- `network`: The network address (string)
- `netmask`: The network mask (string)
- `broadcast`: The broadcast address (string)
- `firstUsable`: The first usable address (same as network for IPv6) (string)
- `lastUsable`: The last usable address (same as broadcast for IPv6) (string)
- `lastUsable`: The last address in the network (string)
- `cidr`: The prefix length (integer)

**Note**: For `/32` IPv4 networks (single host), `firstUsable` and `lastUsable`
Expand Down
27 changes: 10 additions & 17 deletions dsc/tests/dsc_functions.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1365,7 +1365,7 @@ Describe 'tests for function expressions' {
}

It 'parseCidr fails with invalid CIDR: <cidr>' -TestCases @(
@{ cidr = 'invalid'; errorMatch = 'Invalid CIDR notation' }
@{ cidr = '192.168.1/24'; errorMatch = 'Invalid CIDR notation' }
@{ cidr = '192.168.1.0/33'; errorMatch = 'Invalid CIDR notation' }
@{ cidr = '192.168.1.256/24'; errorMatch = 'Invalid CIDR notation' }
) {
Expand Down Expand Up @@ -1444,7 +1444,7 @@ Describe 'tests for function expressions' {
@{ testName = 'new CIDR too small'; network = '10.144.0.0/20'; newCidr = 16; index = 0; errorMatch = 'equal to or larger' }
@{ testName = 'invalid IPv4 prefix'; network = '10.144.0.0/20'; newCidr = 33; index = 0; errorMatch = 'Invalid IPv4 prefix' }
@{ testName = 'invalid IPv6 prefix'; network = '2001:db8::/32'; newCidr = 129; index = 0; errorMatch = 'Invalid IPv6 prefix' }
@{ testName = 'invalid CIDR format'; network = 'invalid'; newCidr = 24; index = 0; errorMatch = 'Invalid CIDR notation' }
@{ testName = 'invalid CIDR format'; network = '10.0.0/16'; newCidr = 24; index = 0; errorMatch = 'Invalid CIDR notation' }
) {
param($testName, $network, $newCidr, $index, $errorMatch)

Expand Down Expand Up @@ -1501,30 +1501,23 @@ Describe 'tests for function expressions' {
$out.results[0].result.actualState.output | Should -BeExactly $expected
}

It 'cidrHost handles /31 point-to-point' {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
output: "[cidrHost('192.168.1.0/31', 0)]"
"@
$out = $config_yaml | dsc config get -f - | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$out.results[0].result.actualState.output | Should -BeExactly '192.168.1.0'
It 'cidrHost handles /31 point-to-point: index <index>' -TestCases @(
@{ network = '192.168.1.0/31'; index = 0; expected = '192.168.1.0' }
@{ network = '192.168.1.0/31'; index = 1; expected = '192.168.1.1' }
) {
param($network, $index, $expected)

$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
output: "[cidrHost('192.168.1.0/31', 1)]"
output: "[cidrHost('$network', $index)]"
"@
$out = $config_yaml | dsc config get -f - | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$out.results[0].result.actualState.output | Should -BeExactly '192.168.1.1'
$out.results[0].result.actualState.output | Should -BeExactly $expected
}

It 'cidrHost works with IPv6' {
Expand All @@ -1546,7 +1539,7 @@ Describe 'tests for function expressions' {
@{ testName = '/128 has no usable hosts'; network = '2001:db8::1/128'; index = 0; errorMatch = 'no usable host' }
@{ testName = 'index out of range'; network = '192.168.1.0/24'; index = 254; errorMatch = 'out of range' }
@{ testName = 'negative index'; network = '192.168.1.0/24'; index = -1; errorMatch = 'negative' }
@{ testName = 'invalid CIDR'; network = 'invalid'; index = 0; errorMatch = 'Invalid CIDR notation' }
@{ testName = 'invalid CIDR'; network = '192.168.1.0.0/24'; index = 0; errorMatch = 'Invalid CIDR notation' }
) {
param($testName, $network, $index, $errorMatch)

Expand Down
5 changes: 0 additions & 5 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,6 @@ invalidCidr = "Invalid CIDR notation: '%{cidr}'. Expected format: IP/prefix (e.g
[functions.cidrHost]
description = "Calculates the usable IP address of the host with the specified index on the specified IP address range in CIDR notation"
invoked = "cidrHost function"
invalidNetwork = "Network parameter must be a string"
invalidHostIndex = "HostIndex parameter must be an integer"
negativeHostIndex = "HostIndex cannot be negative"
invalidCidr = "Invalid CIDR notation: '%{cidr}'. Expected format: IP/prefix (e.g., '192.168.1.0/24' or '2001:db8::/32')"
noUsableHosts = "Network has no usable host addresses (single IP address)"
Expand All @@ -482,9 +480,6 @@ hostCalculationFailed = "Failed to calculate host address"
[functions.cidrSubnet]
description = "Splits the specified IP address range in CIDR notation into subnets with a new CIDR value and returns the IP address range of the subnet with the specified index"
invoked = "cidrSubnet function"
invalidNetwork = "Network parameter must be a string"
invalidNewCidr = "NewCIDR parameter must be an integer"
invalidSubnetIndex = "SubnetIndex parameter must be an integer"
negativeSubnetIndex = "SubnetIndex cannot be negative"
invalidCidr = "Invalid CIDR notation: '%{cidr}'. Expected format: IP/prefix (e.g., '192.168.1.0/24' or '2001:db8::/32')"
invalidPrefixV4 = "Invalid IPv4 prefix: %{prefix}. Must be between 0 and 32"
Expand Down
25 changes: 7 additions & 18 deletions lib/dsc-lib/src/functions/cidr_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl Function for CidrHost {
FunctionMetadata {
name: "cidrHost".to_string(),
description: t!("functions.cidrHost.description").to_string(),
category: vec![FunctionCategory::String],
category: vec![FunctionCategory::Cidr],
min_args: 2,
max_args: 2,
accepted_arg_ordered_types: vec![
Expand All @@ -33,19 +33,8 @@ impl Function for CidrHost {
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
debug!("{}", t!("functions.cidrHost.invoked"));

let cidr_string = args[0].as_str().ok_or_else(|| {
DscError::FunctionArg(
"cidrHost".to_string(),
t!("functions.cidrHost.invalidNetwork").to_string(),
)
})?;

let host_index = args[1].as_i64().ok_or_else(|| {
DscError::FunctionArg(
"cidrHost".to_string(),
t!("functions.cidrHost.invalidHostIndex").to_string(),
)
})?;
let cidr_string = args[0].as_str().unwrap();
let host_index = args[1].as_i64().unwrap();

if host_index < 0 {
return Err(DscError::FunctionArg(
Expand All @@ -62,15 +51,15 @@ impl Function for CidrHost {
})?;

let result = match network {
IpNetwork::V4(net) => calculate_ipv4_host(net, host_index as u32)?,
IpNetwork::V6(net) => calculate_ipv6_host(net, host_index as u128)?,
IpNetwork::V4(net) => calculate_ipv4_host(&net, host_index as u32)?,
IpNetwork::V6(net) => calculate_ipv6_host(&net, host_index as u128)?,
};

Ok(Value::String(result))
}
}

fn calculate_ipv4_host(net: Ipv4Network, host_index: u32) -> Result<String, DscError> {
fn calculate_ipv4_host(net: &Ipv4Network, host_index: u32) -> Result<String, DscError> {
let prefix = net.prefix();

// Special case: /32 has no usable hosts
Expand Down Expand Up @@ -125,7 +114,7 @@ fn calculate_ipv4_host(net: Ipv4Network, host_index: u32) -> Result<String, DscE
Ok(host_ip.to_string())
}

fn calculate_ipv6_host(net: Ipv6Network, host_index: u128) -> Result<String, DscError> {
fn calculate_ipv6_host(net: &Ipv6Network, host_index: u128) -> Result<String, DscError> {
let prefix = net.prefix();

if prefix == 128 {
Expand Down
25 changes: 4 additions & 21 deletions lib/dsc-lib/src/functions/cidr_subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl Function for CidrSubnet {
FunctionMetadata {
name: "cidrSubnet".to_string(),
description: t!("functions.cidrSubnet.description").to_string(),
category: vec![FunctionCategory::String],
category: vec![FunctionCategory::Cidr],
min_args: 3,
max_args: 3,
accepted_arg_ordered_types: vec![
Expand All @@ -35,26 +35,9 @@ impl Function for CidrSubnet {
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
debug!("{}", t!("functions.cidrSubnet.invoked"));

let cidr_string = args[0].as_str().ok_or_else(|| {
DscError::FunctionArg(
"cidrSubnet".to_string(),
t!("functions.cidrSubnet.invalidNetwork").to_string(),
)
})?;

let new_cidr = args[1].as_i64().ok_or_else(|| {
DscError::FunctionArg(
"cidrSubnet".to_string(),
t!("functions.cidrSubnet.invalidNewCidr").to_string(),
)
})? as u8;

let subnet_index = args[2].as_i64().ok_or_else(|| {
DscError::FunctionArg(
"cidrSubnet".to_string(),
t!("functions.cidrSubnet.invalidSubnetIndex").to_string(),
)
})?;
let cidr_string = args[0].as_str().unwrap();
let new_cidr = args[1].as_i64().unwrap() as u8;
let subnet_index = args[2].as_i64().unwrap();

if subnet_index < 0 {
return Err(DscError::FunctionArg(
Expand Down
2 changes: 2 additions & 0 deletions lib/dsc-lib/src/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ pub struct FunctionDefinition {
#[serde(deny_unknown_fields)]
pub enum FunctionCategory {
Array,
Cidr,
Comparison,
Date,
Deployment,
Expand All @@ -362,6 +363,7 @@ impl Display for FunctionCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FunctionCategory::Array => write!(f, "Array"),
FunctionCategory::Cidr => write!(f, "Cidr"),
FunctionCategory::Comparison => write!(f, "Comparison"),
FunctionCategory::Date => write!(f, "Date"),
FunctionCategory::Deployment => write!(f, "Deployment"),
Expand Down
7 changes: 3 additions & 4 deletions lib/dsc-lib/src/functions/parse_cidr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl Function for ParseCidr {
FunctionMetadata {
name: "parseCidr".to_string(),
description: t!("functions.parseCidr.description").to_string(),
category: vec![FunctionCategory::Object],
category: vec![FunctionCategory::Cidr],
min_args: 1,
max_args: 1,
accepted_arg_ordered_types: vec![vec![FunctionArgKind::String]],
Expand Down Expand Up @@ -80,7 +80,6 @@ impl Function for ParseCidr {
json!({
"network": network_addr.to_string(),
"netmask": net.mask().to_string(),
"broadcast": broadcast_addr.to_string(),
"firstUsable": network_addr.to_string(),
"lastUsable": broadcast_addr.to_string(),
"cidr": net.prefix()
Expand Down Expand Up @@ -216,7 +215,7 @@ mod tests {
assert_eq!(obj.get("network").unwrap().as_str().unwrap(), "2001:db8::");
assert_eq!(obj.get("cidr").unwrap().as_u64().unwrap(), 32);
assert!(obj.get("netmask").is_some());
assert!(obj.get("broadcast").is_some());
assert!(obj.get("broadcast").is_none());
assert!(obj.get("firstUsable").is_some());
assert!(obj.get("lastUsable").is_some());
}
Expand All @@ -232,7 +231,7 @@ mod tests {
assert_eq!(obj.get("network").unwrap().as_str().unwrap(), "fe80::");
assert_eq!(obj.get("cidr").unwrap().as_u64().unwrap(), 64);
assert!(obj.get("netmask").is_some());
assert!(obj.get("broadcast").is_some());
assert!(obj.get("broadcast").is_none());
}

#[test]
Expand Down
Loading