mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 12:50:22 -07:00
win_dsc: improved parameter handling (#31556)
* win_dsc: improved parameter handling * removed uneeded try/catch leftover from testing * removed undeed return values * added custom DSC to fully test out casting * fix up codestyle issues * using new Requires ps version check * fixed up error message check on earlier ps version
This commit is contained in:
parent
55bc8291d6
commit
978a979566
14 changed files with 1127 additions and 313 deletions
|
@ -1,75 +1,169 @@
|
|||
#!powershell
|
||||
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# WANT_JSON
|
||||
# POWERSHELL_COMMON
|
||||
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Temporary fix
|
||||
#Set-StrictMode -Off
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#Requires -Version 5
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
#Check that we're on at least Powershell version 5
|
||||
if ($PSVersionTable.PSVersion.Major -lt 5)
|
||||
Function ConvertTo-HashtableFromPsCustomObject($psObject)
|
||||
{
|
||||
Fail-Json -obj $Result -message "This module only runs on Powershell version 5 or higher"
|
||||
$hashtable = @{}
|
||||
$psObject | Get-Member -MemberType *Property | ForEach-Object {
|
||||
$value = $psObject.($_.Name)
|
||||
if ($value -is [PSObject])
|
||||
{
|
||||
$value = ConvertTo-HashtableFromPsCustomObject -myPsObject $value
|
||||
}
|
||||
$hashtable.($_.Name) = $value
|
||||
}
|
||||
|
||||
return ,$hashtable
|
||||
}
|
||||
|
||||
Function Cast-ToCimInstance($name, $value, $className)
|
||||
{
|
||||
# this converts a hashtable to a CimInstance
|
||||
if ($value -is [PSObject])
|
||||
{
|
||||
# convert to hashtable
|
||||
$value = ConvertTo-HashtableFromPsCustomObject -psObject $value
|
||||
}
|
||||
|
||||
$valueType = $value.GetType()
|
||||
if ($valueType -ne [hashtable])
|
||||
{
|
||||
Fail-Json -obj $result -message "CimInstance value for property $name must be a hashtable, was $($valueType.FullName)"
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$cim = New-CimInstance -ClassName $className -Property $value -ClientOnly
|
||||
}
|
||||
catch
|
||||
{
|
||||
Fail-Json -obj $result -message "Failed to convert hashtable to CimInstance of $($className): $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
return ,$cim
|
||||
}
|
||||
|
||||
Function Cast-Value($value, $type, $typeString, $name)
|
||||
{
|
||||
if ($type -eq [CimInstance])
|
||||
{
|
||||
$newValue = Cast-ToCimInstance -name $name -value $value -className $typeString
|
||||
}
|
||||
ElseIf ($type -eq [CimInstance[]])
|
||||
{
|
||||
if ($value -isnot [array])
|
||||
{
|
||||
$value = @($value)
|
||||
}
|
||||
[CimInstance[]]$newValue = @()
|
||||
$baseTypeString = $typeString.Substring(0, $typeString.Length - 2)
|
||||
foreach ($cim in $value)
|
||||
{
|
||||
$newValue += Cast-ToCimInstance -name $name -value $cim -className $baseTypeString
|
||||
}
|
||||
}
|
||||
Else
|
||||
{
|
||||
$originalType = $value.GetType()
|
||||
if ($originalType -eq $type)
|
||||
{
|
||||
$newValue = $value
|
||||
}
|
||||
Else
|
||||
{
|
||||
$newValue = $value -as $type
|
||||
if ($newValue -eq $null)
|
||||
{
|
||||
Add-Warning -obj $result -message "failed to cast property $name from '$value' of type $($originalType.FullName) to type $($type.FullName), the DSC engine may ignore this property with an invalid cast"
|
||||
$newValue = $value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ,$newValue
|
||||
}
|
||||
|
||||
Function Parse-DscProperty($name, $value, $resourceProp)
|
||||
{
|
||||
$propertyTypeString = $resourceProp.PropertyType
|
||||
if ($propertyTypeString.StartsWith("["))
|
||||
{
|
||||
$propertyTypeString = $propertyTypeString.Substring(1, $propertyTypeString.Length - 2)
|
||||
}
|
||||
$propertyType = $propertyTypeString -as [type]
|
||||
|
||||
# CimInstance and CimInstance[] are reperesented as the actual Cim
|
||||
# ClassName and the above returns a $null. We need to manually set the
|
||||
# type in these cases
|
||||
if ($propertyType -eq $null)
|
||||
{
|
||||
if ($propertyTypeString.EndsWith("[]"))
|
||||
{
|
||||
$propertyType = [CimInstance[]]
|
||||
}
|
||||
Else
|
||||
{
|
||||
$propertyType = [CimInstance]
|
||||
}
|
||||
}
|
||||
|
||||
if ($propertyType.IsArray)
|
||||
{
|
||||
# convert the value to a list for later conversion
|
||||
if ($value -is [string])
|
||||
{
|
||||
$value = $value.Split(",").Trim()
|
||||
}
|
||||
ElseIf ($value -isnot [array])
|
||||
{
|
||||
$value = @($value)
|
||||
}
|
||||
}
|
||||
$newValue = Cast-Value -value $value -type $propertyType -typeString $propertyTypeString -name $name
|
||||
|
||||
return ,$newValue
|
||||
}
|
||||
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
$resourcename = Get-AnsibleParam -obj $params -name "resource_name" -type "str" -failifempty $true -resultobj $result
|
||||
$resourcename = Get-AnsibleParam -obj $params -name "resource_name" -type "str" -failifempty $true
|
||||
$module_version = Get-AnsibleParam -obj $params -name "module_version" -type "str" -default "latest"
|
||||
|
||||
#From Ansible 2.3 onwards, params is now a Hash Array
|
||||
$Attributes = $params.GetEnumerator() |
|
||||
Where-Object {
|
||||
$_.key -ne "resource_name" -and
|
||||
$_.key -ne "module_version" -and
|
||||
$_.key -notlike "_ansible_*"
|
||||
$Attributes = @{}
|
||||
foreach ($param in $params.GetEnumerator())
|
||||
{
|
||||
if ($param.Name -notin @("resource_name", "module_version") -and $param.Name -notlike "_ansible_*")
|
||||
{
|
||||
$Attributes[$param.Name] = $param.Value
|
||||
}
|
||||
}
|
||||
|
||||
if (!($Attributes))
|
||||
if ($Attributes.Count -eq 0)
|
||||
{
|
||||
Fail-Json -obj $result -message "No attributes specified"
|
||||
}
|
||||
|
||||
#Always return some basic info
|
||||
$result["resource_name"] = $resourcename
|
||||
$result["attributes"] = $Attributes
|
||||
$result["reboot_required"] = $null
|
||||
|
||||
|
||||
# Build Attributes Hashtable for DSC Resource Propertys
|
||||
$Attrib = @{}
|
||||
foreach ($key in $Attributes)
|
||||
{
|
||||
$result[$key.name] = $key.value
|
||||
$Attrib.Add($Key.Key,$Key.Value)
|
||||
}
|
||||
|
||||
$result["dsc_attributes"] = $attrib
|
||||
$result["reboot_required"] = $false
|
||||
|
||||
$Config = @{
|
||||
Name = ($resourcename)
|
||||
Property = @{
|
||||
}
|
||||
}
|
||||
Property = @{}
|
||||
}
|
||||
|
||||
#Get the latest version of the module
|
||||
if ($module_version -eq "latest")
|
||||
|
@ -115,24 +209,24 @@ try {
|
|||
}
|
||||
catch {}
|
||||
|
||||
|
||||
#Convert params to correct datatype and inject
|
||||
$attrib.Keys | foreach-object {
|
||||
$Key = $_.replace("item_name", "name")
|
||||
$prop = $resource.Properties | where {$_.Name -eq $key}
|
||||
foreach ($attribute in $Attributes.GetEnumerator())
|
||||
{
|
||||
$key = $attribute.Name.Replace("item_name", "name")
|
||||
$value = $attribute.Value
|
||||
$prop = $resource.Properties | Where-Object {$_.Name -eq $key}
|
||||
if (!$prop)
|
||||
{
|
||||
#If its a credential specified as "credential", Ansible will support credential_username and credential_password. Need to check for that
|
||||
$prop = $resource.Properties | where {$_.Name -eq $key.Replace("_username","")}
|
||||
$prop = $resource.Properties | Where-Object {$_.Name -eq $key.Replace("_username","")}
|
||||
if ($prop)
|
||||
{
|
||||
#We need to construct a cred object. At this point keyvalue is the username, so grab the password
|
||||
$PropUserNameValue = $attrib.Item($_)
|
||||
$PropUserNameValue = $value
|
||||
$PropPassword = $key.Replace("_username","_password")
|
||||
$PropPasswordValue = $attrib.$PropPassword
|
||||
$PropPasswordValue = $Attributes.$PropPassword
|
||||
|
||||
$cred = New-Object System.Management.Automation.PSCredential ($PropUserNameValue, ($PropPasswordValue | ConvertTo-SecureString -AsPlainText -Force))
|
||||
[System.Management.Automation.PSCredential]$KeyValue = $cred
|
||||
$KeyValue = New-Object System.Management.Automation.PSCredential ($PropUserNameValue, ($PropPasswordValue | ConvertTo-SecureString -AsPlainText -Force))
|
||||
$config.Property.Add($key.Replace("_username",""),$KeyValue)
|
||||
}
|
||||
ElseIf ($key.Contains("_password"))
|
||||
|
@ -143,91 +237,21 @@ $attrib.Keys | foreach-object {
|
|||
{
|
||||
Fail-Json -obj $result -message "Property $key in resource $resourcename is not a valid property"
|
||||
}
|
||||
|
||||
}
|
||||
ElseIf ($prop.PropertyType -eq "[string]")
|
||||
Else
|
||||
{
|
||||
[String]$KeyValue = $attrib.Item($_)
|
||||
$config.Property.Add($key,$KeyValue)
|
||||
}
|
||||
ElseIf ($prop.PropertyType -eq "[string[]]")
|
||||
{
|
||||
#KeyValue is an array of strings
|
||||
[String]$TempKeyValue = $attrib.Item($_)
|
||||
[String[]]$KeyValue = $TempKeyValue.Split(",").Trim()
|
||||
|
||||
$config.Property.Add($key,$KeyValue)
|
||||
}
|
||||
ElseIf ($prop.PropertyType -eq "[UInt32[]]")
|
||||
{
|
||||
#KeyValue is an array of integers
|
||||
[String]$TempKeyValue = $attrib.Item($_)
|
||||
[UInt32[]]$KeyValue = $attrib.Item($_.split(",").Trim())
|
||||
$config.Property.Add($key,$KeyValue)
|
||||
}
|
||||
ElseIf ($prop.PropertyType -eq "[bool]")
|
||||
{
|
||||
if ($attrib.Item($_) -like "true")
|
||||
if ($value -eq $null)
|
||||
{
|
||||
[bool]$KeyValue = $true
|
||||
$keyValue = $null
|
||||
}
|
||||
ElseIf ($attrib.Item($_) -like "false")
|
||||
Else
|
||||
{
|
||||
[bool]$KeyValue = $false
|
||||
$keyValue = Parse-DscProperty -name $key -value $value -resourceProp $prop
|
||||
}
|
||||
$config.Property.Add($key,$KeyValue)
|
||||
|
||||
$config.Property.Add($key, $keyValue)
|
||||
}
|
||||
ElseIf ($prop.PropertyType -eq "[int]")
|
||||
{
|
||||
[int]$KeyValue = $attrib.Item($_)
|
||||
$config.Property.Add($key,$KeyValue)
|
||||
}
|
||||
ElseIf ($prop.PropertyType -eq "[CimInstance[]]")
|
||||
{
|
||||
#KeyValue is an array of CimInstance
|
||||
[CimInstance[]]$KeyVal = @()
|
||||
[String]$TempKeyValue = $attrib.Item($_)
|
||||
#Need to split on the string }, because some property values have commas in them
|
||||
[String[]]$KeyValueStr = $TempKeyValue -split("},")
|
||||
#Go through each string of properties and create a hash of them
|
||||
foreach($str in $KeyValueStr)
|
||||
{
|
||||
[string[]]$properties = $str.Split("{")[1].Replace("}","").Trim().Split([environment]::NewLine).Trim()
|
||||
$prph = @{}
|
||||
foreach($p in $properties)
|
||||
{
|
||||
$pArr = $p -split "="
|
||||
#if the value can be an int we must convert it to an int
|
||||
if([bool]($pArr[1] -as [int] -is [int]))
|
||||
{
|
||||
$prph.Add($pArr[0].Trim(),$pArr[1].Trim() -as [int])
|
||||
}
|
||||
else
|
||||
{
|
||||
$prph.Add($pArr[0].Trim(),$pArr[1].Trim())
|
||||
}
|
||||
}
|
||||
#create the new CimInstance
|
||||
$cim = New-CimInstance -ClassName $str.Split("{")[0].Trim() -Property $prph -ClientOnly
|
||||
#add the new CimInstance to the array
|
||||
$KeyVal += $cim
|
||||
}
|
||||
$config.Property.Add($key,$KeyVal)
|
||||
}
|
||||
ElseIf ($prop.PropertyType -eq "[Int32]")
|
||||
{
|
||||
# Add Supoort for Int32
|
||||
[int]$KeyValue = $attrib.Item($_)
|
||||
$config.Property.Add($key,$KeyValue)
|
||||
}
|
||||
ElseIf ($prop.PropertyType -eq "[UInt32]")
|
||||
{
|
||||
# Add Support for [UInt32]
|
||||
[UInt32]$KeyValue = $attrib.Item($_)
|
||||
$config.Property.Add($key,$KeyValue)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -262,6 +286,4 @@ Catch
|
|||
Fail-Json -obj $result -message $_[0].Exception.Message
|
||||
}
|
||||
|
||||
|
||||
#set-attr -obj $result -name "property" -value $property
|
||||
Exit-Json -obj $result
|
||||
|
|
|
@ -1,25 +1,12 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
|
@ -31,70 +18,131 @@ module: win_dsc
|
|||
version_added: "2.4"
|
||||
short_description: Invokes a PowerShell DSC configuration
|
||||
description:
|
||||
- Invokes a PowerShell DSC Configuration. Requires PowerShell version 5 (February release or newer).
|
||||
- Most of the parameters for this module are dynamic and will vary depending on the DSC Resource.
|
||||
- See :doc:`windows_dsc` for more information on how to use this module.
|
||||
- Configures a resource using PowerShell DSC.
|
||||
- Requires PowerShell version 5.0 or newer.
|
||||
- Most of the options for this module are dynamic and will vary depending on
|
||||
the DSC Resource specified in I(resource_name).
|
||||
- See :doc:`windows_dsc` for more information on how to use this module.
|
||||
options:
|
||||
resource_name:
|
||||
description:
|
||||
- The DSC Resource to use. Must be accessible to PowerShell using any of the default paths.
|
||||
- The name of the DSC Resource to use.
|
||||
- Must be accessible to PowerShell using any of the default paths.
|
||||
required: true
|
||||
module_version:
|
||||
description:
|
||||
- Can be used to configure the exact version of the dsc resource to be invoked.
|
||||
- Useful if the target node has multiple versions installed of the module containing the DSC resource.
|
||||
- If not specified, the module will follow standard Powershell convention and use the highest version available.
|
||||
- Can be used to configure the exact version of the DSC resource to be
|
||||
invoked.
|
||||
- Useful if the target node has multiple versions installed of the module
|
||||
containing the DSC resource.
|
||||
- If not specified, the module will follow standard PowerShell convention
|
||||
and use the highest version available.
|
||||
default: latest
|
||||
author: Trond Hindenes
|
||||
free_form:
|
||||
description:
|
||||
- The M(win_dsc) module takes in multiple free form options based on the
|
||||
DSC resource being invoked by I(resource_name).
|
||||
- There is no option actually named C(free_form) so see the examples.
|
||||
- This module will try and convert the option to the correct type required
|
||||
by the DSC resource and throw a warning if it fails.
|
||||
- If the type of the DSC resource option is a C(CimInstance) or
|
||||
C(CimInstance[]), this means the value should be a dictionary or list
|
||||
of dictionaries based on the values required by that option.
|
||||
- If the type of the DSC resource option is a C(PSCredential) then there
|
||||
needs to be 2 options set in the Ansible task definition suffixed with
|
||||
C(_username) and C(_password).
|
||||
- If the type of the DSC resource option is an array, then a list should be
|
||||
provided but a comma separated string also work. Use a list where
|
||||
possible as no escaping is required and it works with more complex types
|
||||
list C(CimInstance[]).
|
||||
required: true
|
||||
notes:
|
||||
- By default there are a few builtin resources that come with PowerShell 5.0,
|
||||
see U(https://docs.microsoft.com/en-us/powershell/dsc/builtinresource) for
|
||||
more information on these resources.
|
||||
- Custom DSC resources can be installed with M(win_psmodule) using the I(name)
|
||||
option.
|
||||
- The DSC engine run's each task as the SYSTEM account, any resources that need
|
||||
to be accessed with a different account need to have C(PsDscRunAsCredential)
|
||||
set.
|
||||
author:
|
||||
- Trond Hindenes (@trondhindenes)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Playbook example
|
||||
- name: Extract zip file
|
||||
win_dsc:
|
||||
resource_name: archive
|
||||
ensure: Present
|
||||
path: "C:\\Temp\\zipfile.zip"
|
||||
destination: "C:\\Temp\\Temp2"
|
||||
- name: Extract zip file
|
||||
win_dsc:
|
||||
resource_name: Archive
|
||||
Ensure: Present
|
||||
Path: C:\Temp\zipfile.zip
|
||||
Destination: C:\Temp\Temp2
|
||||
|
||||
- name: Invoke DSC with check mode
|
||||
win_dsc:
|
||||
resource_name: windowsfeature
|
||||
name: telnet-client
|
||||
- name: Install a Windows feature with the WindowsFeature resource
|
||||
win_dsc:
|
||||
resource_name: WindowsFeature
|
||||
Name: telnet-client
|
||||
|
||||
- name: Edit HKCU reg key under specific user
|
||||
win_regedit:
|
||||
resource_name: Registry
|
||||
Ensure: Present
|
||||
Key: HKEY_CURRENT_USER\ExampleKey
|
||||
ValueName: TestValue
|
||||
ValueData: TestData
|
||||
PsDscRunAsCredential_username: '{{ansible_user}}'
|
||||
PsDscRunAsCredentual_password: '{{ansible_password}}'
|
||||
no_log: true
|
||||
|
||||
- name: Create file with multiple attributes
|
||||
win_dsc:
|
||||
resource_name: File
|
||||
DestinationPath: C:\ansible\dsc
|
||||
Attributes: # can also be a comma separated string, e.g. 'Hidden, System'
|
||||
- Hidden
|
||||
- System
|
||||
Ensure: Present
|
||||
Type: Directory
|
||||
|
||||
# more complex example using custom DSC resource and dict values
|
||||
- name: Setup the xWebAdministration module
|
||||
win_psmodule:
|
||||
name: xWebAdministration
|
||||
state: present
|
||||
|
||||
- name: Create IIS Website with Binding and Authentication options
|
||||
win_dsc:
|
||||
resource_name: xWebsite
|
||||
Ensure: Present
|
||||
Name: DSC Website
|
||||
State: Started
|
||||
PhysicalPath: C:\inetpub\wwwroot
|
||||
BindingInfo: # Example of a CimInstance[] DSC parameter (list of dicts)
|
||||
- Protocol: https
|
||||
Port: 1234
|
||||
CertificateStoreName: MY
|
||||
CertificateThumbprint: C676A89018C4D5902353545343634F35E6B3A659
|
||||
HostName: DSCTest
|
||||
IPAddress: '*'
|
||||
SSLFlags: '1'
|
||||
- Protocol: http
|
||||
Port: 4321
|
||||
IPAddress: '*'
|
||||
AuthenticationInfo: # Example of a CimInstance DSC parameter (dict)
|
||||
Anonymous: no
|
||||
Basic: true
|
||||
Digest: false
|
||||
Windows: yes
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
resource_name:
|
||||
description: The name of the invoked resource
|
||||
returned: always
|
||||
type: string
|
||||
sample: windowsfeature
|
||||
module_version:
|
||||
description: The version of the dsc resource/module used.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "1.0.1"
|
||||
attributes:
|
||||
description: The attributes/parameters passed in to the DSC resource as key/value pairs
|
||||
returned: always
|
||||
type: complex
|
||||
sample:
|
||||
contains:
|
||||
Key:
|
||||
description: Attribute key
|
||||
Value:
|
||||
description: Attribute value
|
||||
dsc_attributes:
|
||||
description: The attributes/parameters as returned from the DSC engine in dict format
|
||||
returned: always
|
||||
type: complex
|
||||
contains:
|
||||
Key:
|
||||
description: Attribute key
|
||||
Value:
|
||||
description: Attribute value
|
||||
reboot_required:
|
||||
description: flag returned from the DSC engine indicating whether or not the machine requires a reboot for the invoked changes to take effect
|
||||
description: Flag returned from the DSC engine indicating whether or not
|
||||
the machine requires a reboot for the invoked changes to take effect.
|
||||
returned: always
|
||||
type: boolean
|
||||
sample: True
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue