mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-03-24 21:42:42 +01:00
Compare commits
32 Commits
stenoclean
...
moresoup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddb26a9f42 | ||
|
|
4a89f7f26b | ||
|
|
12dec366e0 | ||
|
|
1713f6af76 | ||
|
|
7f4adb70bd | ||
|
|
e2483e4be0 | ||
|
|
322c0b8d56 | ||
|
|
81c1d8362d | ||
|
|
18f971954b | ||
|
|
c178eada22 | ||
|
|
92213e302f | ||
|
|
72193b0249 | ||
|
|
066d7106b0 | ||
|
|
589de8e361 | ||
|
|
914cd8b611 | ||
|
|
845290595e | ||
|
|
544b60d111 | ||
|
|
aa0787b0ff | ||
|
|
89f144df75 | ||
|
|
cfccbe2bed | ||
|
|
3dd9a06d67 | ||
|
|
4bfe9039ed | ||
|
|
75cddbf444 | ||
|
|
89b18341c5 | ||
|
|
90137f7093 | ||
|
|
480187b1f5 | ||
|
|
2bec5afcdd | ||
|
|
4539024280 | ||
|
|
91759587f5 | ||
|
|
bc9841ea8c | ||
|
|
685e22bd68 | ||
|
|
d78a5867b8 |
2
.github/DISCUSSION_TEMPLATE/2-4.yml
vendored
2
.github/DISCUSSION_TEMPLATE/2-4.yml
vendored
@@ -33,7 +33,7 @@ body:
|
|||||||
- 2.4.200
|
- 2.4.200
|
||||||
- 2.4.201
|
- 2.4.201
|
||||||
- 2.4.210
|
- 2.4.210
|
||||||
- 3.0.0
|
- 2.4.211
|
||||||
- Other (please provide detail below)
|
- Other (please provide detail below)
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
177
.github/DISCUSSION_TEMPLATE/3-0.yml
vendored
Normal file
177
.github/DISCUSSION_TEMPLATE/3-0.yml
vendored
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
If your organization needs more immediate, enterprise grade professional support, with one-on-one virtual meetings and screensharing, contact us via our website: https://securityonion.com/support
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Which version of Security Onion are you asking about?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- 3.0.0
|
||||||
|
- Other (please provide detail below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Installation Method
|
||||||
|
description: How did you install Security Onion?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- Security Onion ISO image
|
||||||
|
- Cloud image (Amazon, Azure, Google)
|
||||||
|
- Network installation on Oracle 9 (unsupported)
|
||||||
|
- Other (please provide detail below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: >
|
||||||
|
Is this discussion about installation, configuration, upgrading, or other?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- installation
|
||||||
|
- configuration
|
||||||
|
- upgrading
|
||||||
|
- other (please provide detail below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Installation Type
|
||||||
|
description: >
|
||||||
|
When you installed, did you choose Import, Eval, Standalone, Distributed, or something else?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- Import
|
||||||
|
- Eval
|
||||||
|
- Standalone
|
||||||
|
- Distributed
|
||||||
|
- other (please provide detail below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Location
|
||||||
|
description: >
|
||||||
|
Is this deployment in the cloud, on-prem with Internet access, or airgap?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- cloud
|
||||||
|
- on-prem with Internet access
|
||||||
|
- airgap
|
||||||
|
- other (please provide detail below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Hardware Specs
|
||||||
|
description: >
|
||||||
|
Does your hardware meet or exceed the minimum requirements for your installation type as shown at https://securityonion.net/docs/hardware?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- Meets minimum requirements
|
||||||
|
- Exceeds minimum requirements
|
||||||
|
- Does not meet minimum requirements
|
||||||
|
- other (please provide detail below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: CPU
|
||||||
|
description: How many CPU cores do you have?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: RAM
|
||||||
|
description: How much RAM do you have?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Storage for /
|
||||||
|
description: How much storage do you have for the / partition?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Storage for /nsm
|
||||||
|
description: How much storage do you have for the /nsm partition?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Network Traffic Collection
|
||||||
|
description: >
|
||||||
|
Are you collecting network traffic from a tap or span port?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- tap
|
||||||
|
- span port
|
||||||
|
- other (please provide detail below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Network Traffic Speeds
|
||||||
|
description: >
|
||||||
|
How much network traffic are you monitoring?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- Less than 1Gbps
|
||||||
|
- 1Gbps to 10Gbps
|
||||||
|
- more than 10Gbps
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Status
|
||||||
|
description: >
|
||||||
|
Does SOC Grid show all services on all nodes as running OK?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- Yes, all services on all nodes are running OK
|
||||||
|
- No, one or more services are failed (please provide detail below)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Salt Status
|
||||||
|
description: >
|
||||||
|
Do you get any failures when you run "sudo salt-call state.highstate"?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- Yes, there are salt failures (please provide detail below)
|
||||||
|
- No, there are no failures
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Logs
|
||||||
|
description: >
|
||||||
|
Are there any additional clues in /opt/so/log/?
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- Yes, there are additional clues in /opt/so/log/ (please provide detail below)
|
||||||
|
- No, there are no additional clues
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Detail
|
||||||
|
description: Please read our discussion guidelines at https://github.com/Security-Onion-Solutions/securityonion/discussions/1720 and then provide detailed information to help us help you.
|
||||||
|
placeholder: |-
|
||||||
|
STOP! Before typing, please read our discussion guidelines at https://github.com/Security-Onion-Solutions/securityonion/discussions/1720 in their entirety!
|
||||||
|
|
||||||
|
If your organization needs more immediate, enterprise grade professional support, with one-on-one virtual meetings and screensharing, contact us via our website: https://securityonion.com/support
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Guidelines
|
||||||
|
options:
|
||||||
|
- label: I have read the discussion guidelines at https://github.com/Security-Onion-Solutions/securityonion/discussions/1720 and assert that I have followed the guidelines.
|
||||||
|
required: true
|
||||||
@@ -8,5 +8,12 @@
|
|||||||
"base": "172.17.0.0/24",
|
"base": "172.17.0.0/24",
|
||||||
"size": 24
|
"size": 24
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"default-ulimits": {
|
||||||
|
"nofile": {
|
||||||
|
"Name": "nofile",
|
||||||
|
"Soft": 1048576,
|
||||||
|
"Hard": 1048576
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,19 +462,14 @@ function add_sensor_to_minion() {
|
|||||||
echo " lb_procs: '$CORECOUNT'"
|
echo " lb_procs: '$CORECOUNT'"
|
||||||
echo "suricata:"
|
echo "suricata:"
|
||||||
echo " enabled: True "
|
echo " enabled: True "
|
||||||
|
echo " pcap:"
|
||||||
|
echo " enabled: True"
|
||||||
if [[ $is_pcaplimit ]]; then
|
if [[ $is_pcaplimit ]]; then
|
||||||
echo " pcap:"
|
|
||||||
echo " maxsize: $MAX_PCAP_SPACE"
|
echo " maxsize: $MAX_PCAP_SPACE"
|
||||||
fi
|
fi
|
||||||
echo " config:"
|
echo " config:"
|
||||||
echo " af-packet:"
|
echo " af-packet:"
|
||||||
echo " threads: '$CORECOUNT'"
|
echo " threads: '$CORECOUNT'"
|
||||||
echo "pcap:"
|
|
||||||
echo " enabled: True"
|
|
||||||
if [[ $is_pcaplimit ]]; then
|
|
||||||
echo " config:"
|
|
||||||
echo " diskfreepercentage: $DFREEPERCENT"
|
|
||||||
fi
|
|
||||||
echo " "
|
echo " "
|
||||||
} >> $PILLARFILE
|
} >> $PILLARFILE
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def showUsage(args):
|
|||||||
print(' removelistitem - Remove a list item from a yaml key, if it exists and is a list. Requires KEY and LISTITEM args.', file=sys.stderr)
|
print(' removelistitem - Remove a list item from a yaml key, if it exists and is a list. Requires KEY and LISTITEM args.', file=sys.stderr)
|
||||||
print(' replacelistobject - Replace a list object based on a condition. Requires KEY, CONDITION_FIELD, CONDITION_VALUE, and JSON_OBJECT args.', file=sys.stderr)
|
print(' replacelistobject - Replace a list object based on a condition. Requires KEY, CONDITION_FIELD, CONDITION_VALUE, and JSON_OBJECT args.', file=sys.stderr)
|
||||||
print(' add - Add a new key and set its value. Fails if key already exists. Requires KEY and VALUE args.', file=sys.stderr)
|
print(' add - Add a new key and set its value. Fails if key already exists. Requires KEY and VALUE args.', file=sys.stderr)
|
||||||
print(' get - Displays (to stdout) the value stored in the given key. Requires KEY arg.', file=sys.stderr)
|
print(' get [-r] - Displays (to stdout) the value stored in the given key. Requires KEY arg. Use -r for raw output without YAML formatting.', file=sys.stderr)
|
||||||
print(' remove - Removes a yaml key, if it exists. Requires KEY arg.', file=sys.stderr)
|
print(' remove - Removes a yaml key, if it exists. Requires KEY arg.', file=sys.stderr)
|
||||||
print(' replace - Replaces (or adds) a new key and set its value. Requires KEY and VALUE args.', file=sys.stderr)
|
print(' replace - Replaces (or adds) a new key and set its value. Requires KEY and VALUE args.', file=sys.stderr)
|
||||||
print(' help - Prints this usage information.', file=sys.stderr)
|
print(' help - Prints this usage information.', file=sys.stderr)
|
||||||
@@ -256,7 +256,7 @@ def replacelistobject(args):
|
|||||||
def addKey(content, key, value):
|
def addKey(content, key, value):
|
||||||
pieces = key.split(".", 1)
|
pieces = key.split(".", 1)
|
||||||
if len(pieces) > 1:
|
if len(pieces) > 1:
|
||||||
if not pieces[0] in content:
|
if pieces[0] not in content or content[pieces[0]] is None:
|
||||||
content[pieces[0]] = {}
|
content[pieces[0]] = {}
|
||||||
addKey(content[pieces[0]], pieces[1], value)
|
addKey(content[pieces[0]], pieces[1], value)
|
||||||
elif key in content:
|
elif key in content:
|
||||||
@@ -332,6 +332,11 @@ def getKeyValue(content, key):
|
|||||||
|
|
||||||
|
|
||||||
def get(args):
|
def get(args):
|
||||||
|
raw = False
|
||||||
|
if len(args) > 0 and args[0] == '-r':
|
||||||
|
raw = True
|
||||||
|
args = args[1:]
|
||||||
|
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
print('Missing filename or key arg', file=sys.stderr)
|
print('Missing filename or key arg', file=sys.stderr)
|
||||||
showUsage(None)
|
showUsage(None)
|
||||||
@@ -346,7 +351,15 @@ def get(args):
|
|||||||
print(f"Key '{key}' not found by so-yaml.py", file=sys.stderr)
|
print(f"Key '{key}' not found by so-yaml.py", file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
print(yaml.safe_dump(output))
|
if raw:
|
||||||
|
if isinstance(output, bool):
|
||||||
|
print(str(output).lower())
|
||||||
|
elif isinstance(output, (dict, list)):
|
||||||
|
print(yaml.safe_dump(output).strip())
|
||||||
|
else:
|
||||||
|
print(output)
|
||||||
|
else:
|
||||||
|
print(yaml.safe_dump(output))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -395,6 +395,17 @@ class TestRemove(unittest.TestCase):
|
|||||||
self.assertEqual(result, 0)
|
self.assertEqual(result, 0)
|
||||||
self.assertIn("45\n...", mock_stdout.getvalue())
|
self.assertIn("45\n...", mock_stdout.getvalue())
|
||||||
|
|
||||||
|
def test_get_int_raw(self):
|
||||||
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
|
filename = "/tmp/so-yaml_test-get.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.write("{key1: { child1: 123, child2: { deep1: 45 } }, key2: false, key3: [e,f,g]}")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.get(["-r", filename, "key1.child2.deep1"])
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
self.assertEqual("45\n", mock_stdout.getvalue())
|
||||||
|
|
||||||
def test_get_str(self):
|
def test_get_str(self):
|
||||||
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
filename = "/tmp/so-yaml_test-get.yaml"
|
filename = "/tmp/so-yaml_test-get.yaml"
|
||||||
@@ -406,6 +417,51 @@ class TestRemove(unittest.TestCase):
|
|||||||
self.assertEqual(result, 0)
|
self.assertEqual(result, 0)
|
||||||
self.assertIn("hello\n...", mock_stdout.getvalue())
|
self.assertIn("hello\n...", mock_stdout.getvalue())
|
||||||
|
|
||||||
|
def test_get_str_raw(self):
|
||||||
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
|
filename = "/tmp/so-yaml_test-get.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.write("{key1: { child1: 123, child2: { deep1: \"hello\" } }, key2: false, key3: [e,f,g]}")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.get(["-r", filename, "key1.child2.deep1"])
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
self.assertEqual("hello\n", mock_stdout.getvalue())
|
||||||
|
|
||||||
|
def test_get_bool(self):
|
||||||
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
|
filename = "/tmp/so-yaml_test-get.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.write("{key1: { child1: 123, child2: { deep1: 45 } }, key2: false, key3: [e,f,g]}")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.get([filename, "key2"])
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
self.assertIn("false\n...", mock_stdout.getvalue())
|
||||||
|
|
||||||
|
def test_get_bool_raw(self):
|
||||||
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
|
filename = "/tmp/so-yaml_test-get.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.write("{key1: { child1: 123, child2: { deep1: 45 } }, key2: false, key3: [e,f,g]}")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.get(["-r", filename, "key2"])
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
self.assertEqual("false\n", mock_stdout.getvalue())
|
||||||
|
|
||||||
|
def test_get_dict_raw(self):
|
||||||
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
|
filename = "/tmp/so-yaml_test-get.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.write("{key1: { child1: 123, child2: { deep1: 45 } }, key2: false, key3: [e,f,g]}")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.get(["-r", filename, "key1"])
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
self.assertIn("child1: 123", mock_stdout.getvalue())
|
||||||
|
self.assertNotIn("...", mock_stdout.getvalue())
|
||||||
|
|
||||||
def test_get_list(self):
|
def test_get_list(self):
|
||||||
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
filename = "/tmp/so-yaml_test-get.yaml"
|
filename = "/tmp/so-yaml_test-get.yaml"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,11 @@ sool9_{{host}}:
|
|||||||
hypervisor_host: {{host ~ "_" ~ role}}
|
hypervisor_host: {{host ~ "_" ~ role}}
|
||||||
preflight_cmds:
|
preflight_cmds:
|
||||||
- |
|
- |
|
||||||
tee -a /etc/hosts <<< "{{ MANAGERIP }} {{ MANAGERHOSTNAME }}"
|
{%- set hostnames = [MANAGERHOSTNAME] %}
|
||||||
|
{%- if not (URL_BASE | ipaddr) and URL_BASE != MANAGERHOSTNAME %}
|
||||||
|
{%- do hostnames.append(URL_BASE) %}
|
||||||
|
{%- endif %}
|
||||||
|
tee -a /etc/hosts <<< "{{ MANAGERIP }} {{ hostnames | join(' ') }}"
|
||||||
- |
|
- |
|
||||||
timeout 600 bash -c 'trap "echo \"Preflight Check: Failed to establish repo connectivity\"; exit 1" TERM; \
|
timeout 600 bash -c 'trap "echo \"Preflight Check: Failed to establish repo connectivity\"; exit 1" TERM; \
|
||||||
while ! dnf makecache --repoid=securityonion >/dev/null 2>&1; do echo "Preflight Check: Waiting for repo connectivity..."; \
|
while ! dnf makecache --repoid=securityonion >/dev/null 2>&1; do echo "Preflight Check: Waiting for repo connectivity..."; \
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
{% if 'vrt' in salt['pillar.get']('features', []) %}
|
{% if 'vrt' in salt['pillar.get']('features', []) %}
|
||||||
{% set HYPERVISORS = salt['pillar.get']('hypervisor:nodes', {} ) %}
|
{% set HYPERVISORS = salt['pillar.get']('hypervisor:nodes', {} ) %}
|
||||||
{% from 'salt/map.jinja' import SALTVERSION %}
|
{% from 'salt/map.jinja' import SALTVERSION %}
|
||||||
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
|
|
||||||
{% if HYPERVISORS %}
|
{% if HYPERVISORS %}
|
||||||
cloud_providers:
|
cloud_providers:
|
||||||
@@ -34,6 +35,7 @@ cloud_profiles:
|
|||||||
MANAGERHOSTNAME: {{ grains.host }}
|
MANAGERHOSTNAME: {{ grains.host }}
|
||||||
MANAGERIP: {{ pillar.host.mainip }}
|
MANAGERIP: {{ pillar.host.mainip }}
|
||||||
SALTVERSION: {{ SALTVERSION }}
|
SALTVERSION: {{ SALTVERSION }}
|
||||||
|
URL_BASE: {{ GLOBALS.url_base }}
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- makedirs: True
|
- makedirs: True
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -805,11 +805,6 @@ def process_vm_creation(hypervisor_path: str, vm_config: dict) -> None:
|
|||||||
mark_invalid_hardware(hypervisor_path, vm_name, vm_config,
|
mark_invalid_hardware(hypervisor_path, vm_name, vm_config,
|
||||||
{'nsm_size': 'Invalid nsm_size: must be positive integer'})
|
{'nsm_size': 'Invalid nsm_size: must be positive integer'})
|
||||||
return
|
return
|
||||||
if size > 10000: # 10TB reasonable maximum
|
|
||||||
log.error("VM: %s - nsm_size %dGB exceeds reasonable maximum (10000GB)", vm_name, size)
|
|
||||||
mark_invalid_hardware(hypervisor_path, vm_name, vm_config,
|
|
||||||
{'nsm_size': f'Invalid nsm_size: {size}GB exceeds maximum (10000GB)'})
|
|
||||||
return
|
|
||||||
log.debug("VM: %s - nsm_size validated: %dGB", vm_name, size)
|
log.debug("VM: %s - nsm_size validated: %dGB", vm_name, size)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
log.error("VM: %s - nsm_size must be a valid integer, got: %s", vm_name, vm_config.get('nsm_size'))
|
log.error("VM: %s - nsm_size must be a valid integer, got: %s", vm_name, vm_config.get('nsm_size'))
|
||||||
|
|||||||
Reference in New Issue
Block a user