Skip to content

Commit ea7304c

Browse files
authored
Merge branch 'main' into dependabot/github_actions/github/codeql-action-4
2 parents e76b643 + f3d1c59 commit ea7304c

16 files changed

Lines changed: 710 additions & 429 deletions

File tree

.github/workflows/trivy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
uses: actions/checkout@v5
1414

1515
- name: Run Trivy vulnerability scanner in repo mode
16-
uses: aquasecurity/trivy-action@0.33.1
16+
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
1717
with:
1818
scan-type: "fs"
1919
ignore-unfixed: true

CHANGELOG.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [2.14.0] - 2026-03-05
11+
12+
# Added
13+
14+
- Supporting routerdynamicrouting_info in netscaler.adc collections
15+
16+
## [2.13.0] - 2025-12-14
17+
18+
# Fixed
19+
20+
- Issue with running cli commands through ansible using ssh connection against 13.1 ADC.
21+
22+
## [2.12.0] - 2025-12-8
23+
24+
# Added
25+
26+
- supporting reboot action
27+
28+
# Fixed
29+
30+
- Issue with running cli commands through ansible using ssh connection.
31+
- Resolving url builder issue when filterargs has bool datatype.
32+
- Issue with get_args keys in HAnode module.
33+
1034
## [2.11.0] - 2025-10-31
1135

1236
# Added
@@ -212,7 +236,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
212236

213237
- Initial Release
214238

215-
[unreleased]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.11.0...HEAD
239+
[unreleased]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.14.0...HEAD
240+
[2.14.0]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.13.0...2.14.0
241+
[2.13.0]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.12.0...2.13.0
242+
[2.12.0]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.11.0...2.12.0
216243
[2.11.0]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.10.1...2.11.0
217244
[2.10.1]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.10.0...2.10.1
218245
[2.10.0]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.9.2...2.10.0

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ build:
4242
ansible-galaxy collection build --force
4343

4444
galaxy_importer: build
45-
python3 -m galaxy_importer.main netscaler-adc-2.11.0.tar.gz
45+
python3 -m galaxy_importer.main netscaler-adc-2.14.0.tar.gz
4646

4747
# build_docs:
4848
# rm -rf _built_docs

README.md

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,128 @@ Refer to the [NetScaler ADM as an API proxy server](https://docs.netscaler.com/e
132132
Refer to the [sample_playboook](https://github.com/netscaler/ansible-collection-netscaleradc/tree/main/examples) and [playbook_anatomy.md](https://github.com/netscaler/ansible-collection-netscaleradc/blob/main/playbook_anatomy.md).
133133
134134
135-
### SSH_connections
135+
### SSH Connection Plugin
136136
137-
Refer to [SSH_connections examples](https://github.com/netscaler/ansible-collection-netscaleradc/tree/main/examples/ssh_connections) to know how `ansible.builtins.` plugins can be used to configure the NetScaler ADC.
137+
The collection provides an SSH connection plugin (`netscaler.adc.ssh_netscaler_adc`) that enables direct SSH connectivity to NetScaler ADC devices for executing shell commands and CLI operations.
138+
The collection supports both SSH key-based authentication and SSH password based authentication.
139+
140+
#### Prerequisites
141+
142+
- SSH access to the NetScaler ADC device.
143+
- Private SSH key file available on the control machine (required when using SSH key-based authentication). Refer [Configure SSH key-based authentication](https://docs.netscaler.com/en-us/citrix-adc/current-release/system/authentication-and-authorization-for-system-user/ssh-key-based-authentication-for-system-users.html#configure-ssh-key-based-authentication-for-the-netscaler-local-system-users-by-using-cli)
144+
- SSH username and password (required when using SSH password based authentication).
145+
146+
#### Inventory Configuration
147+
148+
Configure your inventory file with the required SSH parameters:
149+
150+
**Example 1: Group-based inventory**
151+
```ini
152+
[demo_netscaler1]
153+
demo_netscalers
154+
155+
[demo_netscaler1:vars]
156+
ansible_host=10.10.10.10
157+
ansible_user=nsroot
158+
ansible_ssh_private_key_file=~/.ssh/id_rsa
159+
nitro_pass=YourPassword
160+
```
161+
162+
**Example 2: Inline inventory**
163+
```ini
164+
[demo_netscalers]
165+
netscaler_adc ansible_host=10.10.10.10 ansible_user=nsroot ansible_ssh_private_key_file=~/.ssh/id_rsa
166+
```
167+
168+
**Required inventory parameters:**
169+
- `ansible_host` - IP address or hostname of NetScaler ADC
170+
- `ansible_user` - SSH username (typically `nsroot`)
171+
- `ansible_ssh_private_key_file` - Path to SSH private key (required when using SSH key-based authentication)
172+
- `nitro_pass` - Password for nscli commands (required when using CLI commands)
173+
174+
#### Playbook Structure for Shell Commands
175+
176+
To execute FreeBSD shell commands on the NetScaler appliance:
177+
178+
```yaml
179+
---
180+
- name: Execute shell commands on NetScaler
181+
hosts: demo_netscalers
182+
connection: netscaler.adc.ssh_netscaler_adc
183+
remote_user: nsroot
184+
gather_facts: false
185+
186+
vars:
187+
ansible_python_interpreter: /var/python/bin/python
188+
189+
tasks:
190+
- name: List files in /var/tmp
191+
ansible.builtin.command: "ls -lhrt /var/tmp/"
192+
register: result
193+
changed_when: false
194+
195+
- name: Display output
196+
ansible.builtin.debug:
197+
msg: "{{ result.stdout_lines }}"
198+
```
199+
200+
**Required settings for shell access:**
201+
- `connection: netscaler.adc.ssh_netscaler_adc` - Use the SSH connection plugin
202+
- `ansible_python_interpreter: /var/python/bin/python` - Required for shell command execution
203+
- `gather_facts: false` - Fact gathering is not supported
204+
205+
#### Playbook Structure for NetScaler CLI Commands
206+
207+
To execute NetScaler CLI commands using nscli:
208+
209+
```yaml
210+
---
211+
- name: Execute NetScaler CLI commands
212+
hosts: demo_netscalers
213+
connection: netscaler.adc.ssh_netscaler_adc
214+
remote_user: nsroot
215+
gather_facts: false
216+
217+
vars:
218+
ansible_python_interpreter: /var/python/bin/python
219+
nscli_command: "show ns version"
220+
221+
tasks:
222+
- name: Run NetScaler CLI command
223+
ansible.builtin.command: "nscli -s -U :nsroot:{{ nitro_pass }} {{ nscli_command }}"
224+
register: cli_result
225+
changed_when: false
226+
227+
- name: Display CLI output
228+
ansible.builtin.debug:
229+
msg: "{{ cli_result.stdout_lines }}"
230+
```
231+
232+
**CLI command format:**
233+
- Use `nscli -s -U :nsroot:{{ nitro_pass }} <command>` to execute NetScaler CLI commands
234+
- The `-s` flag runs in non-interactive mode
235+
- The `-U :nsroot:{{ nitro_pass }}` provides authentication credentials
236+
- Replace `<command>` with any valid NetScaler CLI command (e.g., `show ns ip`, `show lb vserver`)
237+
238+
#### Running the Playbook
239+
240+
```bash
241+
ansible-playbook playbook.yaml -i inventory.ini
242+
```
243+
244+
Use --ask-pass to key in SSH password on demand in case of SSH password based authentication.
245+
```bash
246+
ansible-playbook playbook.yaml -i inventory.ini --ask-pass
247+
```
248+
249+
Add `-vvvv` for verbose debugging output if needed:
250+
```bash
251+
ansible-playbook playbook.yaml -i inventory.ini -vvvv
252+
```
253+
254+
#### Additional Examples
255+
256+
For more complete examples and use cases, refer to the [SSH connections examples](https://github.com/netscaler/ansible-collection-netscaleradc/tree/main/examples/ssh_connections) directory.
138257

139258
### Authentication
140259

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
- name: Sample routerdynamicrouting_info playbook
3+
hosts: demo_netscalers
4+
gather_facts: false
5+
tasks:
6+
- name: Get BGP routing summary
7+
delegate_to: localhost
8+
netscaler.adc.routerdynamicrouting_info:
9+
nsip: "{{ nsip }}"
10+
nitro_user: "{{ nitro_user }}"
11+
nitro_pass: "{{ nitro_pass }}"
12+
nitro_protocol: "{{ nitro_protocol }}"
13+
validate_certs: false
14+
commandstring: "show ip bgp summary"
15+
register: bgp_summary
16+
17+
- name: Display BGP summary
18+
ansible.builtin.debug:
19+
var: bgp_summary.info
20+
21+
- name: Get IP routing table
22+
delegate_to: localhost
23+
netscaler.adc.routerdynamicrouting_info:
24+
nsip: "{{ nsip }}"
25+
nitro_user: "{{ nitro_user }}"
26+
nitro_pass: "{{ nitro_pass }}"
27+
nitro_protocol: "{{ nitro_protocol }}"
28+
validate_certs: false
29+
commandstring: "sh ip route"
30+
register: ip_route
31+
32+
- name: Display routing table
33+
ansible.builtin.debug:
34+
var: ip_route.info
35+
36+
- name: Get OSPF neighbor information
37+
delegate_to: localhost
38+
netscaler.adc.routerdynamicrouting_info:
39+
nsip: "{{ nsip }}"
40+
nitro_user: "{{ nitro_user }}"
41+
nitro_pass: "{{ nitro_pass }}"
42+
nitro_protocol: "{{ nitro_protocol }}"
43+
validate_certs: false
44+
commandstring: "show ip ospf neighbor"
45+
register: ospf_neighbors
46+
47+
- name: Display OSPF neighbors
48+
ansible.builtin.debug:
49+
var: ospf_neighbors.info

galaxy.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ namespace: netscaler
77
# The name of the collection. Has the same character restrictions as 'namespace'
88
name: adc
99
# The version of the collection. Must be compatible with semantic versioning
10-
version: 2.11.0
10+
version: 2.14.0
1111
# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
1212
readme: README.md
1313
# A list of the collection's content authors. Can be just the name or in the format 'Full Name <email> (url)
1414
# @nicks:irc/im.site#channel'
1515
authors:
16-
- Sumanth Lingappa <[email protected]>
17-
- Shiva Shankar Vaddepally <[email protected]>
16+
- Sumanth Lingappa <[email protected]>
17+
- Shiva Shankar Vaddepally <[email protected]>
18+
- Lakshman M J <[email protected]>
1819
### OPTIONAL but strongly recommended
1920
# A short summary description of the collection
2021
description: Ansible Collection Modules for NetScaler ADC

plugins/connection/ssh_netscaler_adc.py

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -391,25 +391,49 @@ def _return_tuple_manipulate(func):
391391
def wrapped(self, *args, **kwargs):
392392
return_tuple = func(self, *args, **kwargs)
393393
return_tuple = list(return_tuple)
394-
subst = ""
394+
395+
# Decode stdout
396+
stdout = codecs.decode(return_tuple[1], errors='ignore')
397+
398+
# Remove "Done" messages with surrounding newlines
395399
regex = r"(\r\n|\r|\n|)( Done)(\r\n|\r|\n)+"
396-
return_tuple[1] = re.sub(regex, subst, codecs.decode(return_tuple[1]), 0, re.UNICODE)
400+
stdout = re.sub(regex, "", stdout, 0, re.UNICODE)
401+
402+
# Remove Warning messages (multi-line)
403+
warning_regex = r"Warning: \[[\s\S]*?\]\s*"
404+
stdout = re.sub(warning_regex, "", stdout, 0, re.UNICODE)
405+
406+
# Remove RPC node password warnings that contaminate stdout
407+
rpc_warning_regex = (
408+
r"Warning: One or more RPC nodes are configured with default "
409+
r"passwords. For enhanced security, you must change the default "
410+
r"RPC node password."
411+
)
412+
stdout = re.sub(rpc_warning_regex, "", stdout, 0, re.UNICODE)
413+
414+
# Remove leading "]" characters that might remain
415+
stdout = re.sub(r"^\]\s*", "", stdout, re.MULTILINE)
416+
417+
# Remove extra blank lines
418+
stdout = re.sub(r"\n\s*\n", "\n", stdout)
397419

398-
# Ansible needs some data from return_tuple[1](or stdout). So, we are returning the same to ansible
420+
# Try to extract JSON if present
399421
regex2 = r'{.*}'
400422
try:
401-
return_tuple[1] = re.findall(regex2, str(return_tuple[1]))[0]
402-
except IndexError:
423+
json_match = re.findall(regex2, stdout)
424+
if json_match:
425+
stdout = json_match[0]
426+
except (IndexError, AttributeError):
403427
pass
404-
# If no match, return the old `return_tuple[1]` (i.e., stdout)
428+
# If no match, use the cleaned stdout
405429

430+
return_tuple[1] = stdout.encode() if isinstance(return_tuple[1], bytes) else stdout
406431
return_tuple = tuple(return_tuple)
407432
return return_tuple
408433
return wrapped
409434

410435

411436
def _manipulate_cmd(func):
412-
413437
@wraps(func)
414438
def wrapped(self, cmd, *args, **kwargs):
415439
# Adding the 'shell' command for the citrix adc cli
@@ -455,3 +479,48 @@ def exec_command(self, cmd, in_data=None, sudoable=True):
455479
''' run a command on the remote host '''
456480

457481
return super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
482+
483+
@_return_tuple_manipulate
484+
def _bare_run(self, cmd, in_data, sudoable=True, checkrc=True):
485+
"""
486+
Wrapper around parent _bare_run to clean NetScaler output
487+
"""
488+
return super(Connection, self)._bare_run(cmd, in_data, sudoable=sudoable, checkrc=checkrc)
489+
490+
def put_file(self, in_path, out_path):
491+
"""
492+
Transfer a file from local to remote using piped method due to NetScaler limitations
493+
"""
494+
display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self.host)
495+
496+
# Force piped transfer for NetScaler
497+
# Save original transfer method
498+
original_method = self.get_option('ssh_transfer_method')
499+
500+
try:
501+
# Temporarily set to piped
502+
self._options['ssh_transfer_method'] = 'piped'
503+
return super(Connection, self).put_file(in_path, out_path)
504+
finally:
505+
# Restore original method
506+
if original_method:
507+
self._options['ssh_transfer_method'] = original_method
508+
509+
def fetch_file(self, in_path, out_path):
510+
"""
511+
Fetch a file from remote to local using piped method due to NetScaler limitations
512+
"""
513+
display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self.host)
514+
515+
# Force piped transfer for NetScaler
516+
# Save original transfer method
517+
original_method = self.get_option('ssh_transfer_method')
518+
519+
try:
520+
# Temporarily set to piped
521+
self._options['ssh_transfer_method'] = 'piped'
522+
return super(Connection, self).fetch_file(in_path, out_path)
523+
finally:
524+
# Restore original method
525+
if original_method:
526+
self._options['ssh_transfer_method'] = original_method

plugins/module_utils/client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ def format_value(val):
137137
return str(val)
138138

139139
args_val = ",".join(
140-
["%s:%s" % (k, quote(codecs.encode(format_value(args[k])), safe="")) for k in args]
140+
[
141+
"%s:%s" % (k, quote(codecs.encode(format_value(args[k])), safe=""))
142+
for k in args
143+
]
141144
)
142145
args_val = ("args=%s" % args_val) if args_val != "" else ""
143146

plugins/module_utils/module_executor.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,10 @@ def filter_values(value):
325325

326326
log("DEBUG: self.module.params: %s" % self.module.params)
327327
for k, v in self.module.params.items():
328-
if not k.endswith("_binding") and k in NITRO_RESOURCE_MAP[self.resource_name]["readwrite_arguments"]:
328+
if (
329+
not k.endswith("_binding")
330+
and k in NITRO_RESOURCE_MAP[self.resource_name]["readwrite_arguments"]
331+
):
329332
cleaned_value = filter_values(v)
330333
if cleaned_value is not None:
331334
self.resource_module_params[k] = cleaned_value

0 commit comments

Comments
 (0)