Skip to content

Commit 62d96eb

Browse files
Merge pull request #542 from netscaler/migrationtool_NSNETAUTO-1024
migrationtool for citrix.adc to netscaler.adc
2 parents f12bda0 + 8ef656e commit 62d96eb

File tree

4 files changed

+350
-0
lines changed

4 files changed

+350
-0
lines changed

migrationtool/README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# NetScaler ADC Ansible Collection Migration Tool
2+
3+
This tool helps migrate existing Ansible playbooks from the legacy `citrix.adc` collection to the new `netscaler.adc` collection format.
4+
5+
## Overview
6+
7+
The migration tool converts YAML playbooks that use:
8+
- Legacy `citrix.adc` modules
9+
- `citrix_adc_nitro_request` generic module
10+
11+
Into playbooks that use the new `netscaler.adc` collection modules.
12+
13+
## Features
14+
15+
- **Module Mapping**: Automatically maps legacy module names to new collection modules
16+
- **State Conversion**: Converts NITRO request operations to appropriate state values
17+
- **Credential Handling**: Preserves and converts authentication parameters
18+
- **YAML Structure Preservation**: Maintains playbook structure, variables, and task organization
19+
20+
## Usage
21+
22+
### Basic Usage
23+
24+
```bash
25+
python3 convert_yaml.py -i input_playbook.yaml -o output_playbook.yaml
26+
```
27+
28+
### Arguments
29+
30+
- `-i, --input`: (Required) Path to the input YAML playbook
31+
- `-o, --output`: (Optional) Path for the output file. Defaults to `output.yaml`
32+
- `-v, --verbose`: (Optional) Enable verbose mode
33+
34+
### Example
35+
36+
```bash
37+
python convert_yaml.py -i legacy_playbook.yml -o migrated_playbook.yml
38+
```
39+
40+
## Supported Conversions
41+
42+
### Legacy Module Mappings
43+
The tool uses `resource_map` to convert legacy module names to new collection modules:
44+
- `citrix.adc.lbvserver``netscaler.adc.lbvserver`
45+
- `lbvserver``netscaler.adc.lbvserver`
46+
47+
### NITRO Request Conversion
48+
Converts `citrix_adc_nitro_request` tasks to specific resource modules:
49+
50+
**Before:**
51+
```yaml
52+
- name: Configure LB vserver
53+
citrix_adc_nitro_request:
54+
nsip: "{{ nsip }}"
55+
nitro_user: "{{ nitro_user }}"
56+
nitro_pass: "{{ nitro_pass }}"
57+
operation: present
58+
resource: lbvserver
59+
name: my_lb_vserver
60+
attributes:
61+
servicetype: HTTP
62+
ipv46: 10.10.10.10
63+
port: 80
64+
```
65+
66+
**After:**
67+
```yaml
68+
- name: Configure LB vserver
69+
netscaler.adc.lbvserver:
70+
nsip: "{{ nsip }}"
71+
nitro_user: "{{ nitro_user }}"
72+
nitro_pass: "{{ nitro_pass }}"
73+
state: present
74+
name: my_lb_vserver
75+
servicetype: HTTP
76+
ipv46: 10.10.10.10
77+
port: 80
78+
```
79+
80+
### State Mapping
81+
- `add` → `present`
82+
- `update` → `present`
83+
- `delete` → `absent`
84+
- `present` → `present`
85+
- `absent` → `absent`
86+
- `action` → Uses the action value from the task
87+
88+
## Files
89+
- `convert_yaml.py`: Main conversion script
90+
- `resourcelist.py`: Contains `resource_map` and `state_map` mappings
91+
92+
## Requirements
93+
94+
- Python 3.x
95+
- PyYAML
96+
97+
## Installation
98+
99+
```bash
100+
pip install pyyaml
101+
```
102+
103+
## Input Format Support
104+
105+
The tool supports various YAML input formats:
106+
- Single playbook dictionary
107+
- List of plays
108+
- List of tasks (automatically wrapped in a play structure)
109+
110+
## Output
111+
112+
The tool generates a properly formatted YAML playbook with:
113+
- Converted module names
114+
- Updated authentication parameters
115+
- Preserved task names and structure
116+
- Proper indentation and formatting
117+
118+
## Troubleshooting
119+
120+
### Common Issues
121+
122+
1. **Resource not found**: Check if the resource type exists in `resource_map`
123+
2. **Missing name field**: Ensure the original task has a `name` parameter for NITRO requests
124+
3. **Authentication errors**: Verify credential parameters are correctly set
125+
126+
### Debug Output
127+
128+
The tool provides console output showing:
129+
- Module mappings being applied
130+
- NITRO request processing details
131+
- Tasks being converted
132+
133+
## Contributing
134+
135+
To add support for new modules:
136+
1. Update `resource_map` in `resourcelist.py`
137+
2. Add appropriate state mappings if needed
138+
3. Test with sample playbooks

migrationtool/__init__.py

Whitespace-only changes.

migrationtool/convert_yaml.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import yaml
2+
import argparse
3+
from resourcelist import (
4+
resource_map,
5+
state_map
6+
)
7+
8+
class CustomDumper(yaml.SafeDumper):
9+
def ignore_aliases(self, data):
10+
return True
11+
12+
def increase_indent(self, flow=False, indentless=False):
13+
return super(CustomDumper, self).increase_indent(flow, False)
14+
15+
def represent_custom_object(dumper, data):
16+
try:
17+
return dumper.represent_dict(data.__dict__)
18+
except AttributeError:
19+
raise yaml.representer.RepresenterError("cannot represent an object", data)
20+
21+
# Register the custom representer
22+
CustomDumper.add_representer(object, represent_custom_object)
23+
24+
def get_state_attributes(plugin_key):
25+
logincreds ={}
26+
logincreds["nsip"] = plugin_key.get("nsip", None)
27+
logincreds["nitro_user"] = plugin_key.get("nitro_user", None)
28+
logincreds["nitro_pass"] = plugin_key.get("nitro_pass", None)
29+
logincreds["validate_certs"] = plugin_key.get("validate_certs", "no")
30+
logincreds["nitro_protocol"] = plugin_key.get("nitro_protocol", "http")
31+
32+
return logincreds
33+
34+
def convert_yaml_file(input_file, output_file, template_file, verbose):
35+
36+
# Convert input file (citrix.adc) to output file (citrix.adc.yaml) using template
37+
with open(input_file, 'r') as infile:
38+
data = yaml.safe_load(infile)
39+
task_only = False
40+
# Handle both list and dict formats
41+
if isinstance(data, list):
42+
# If data is a list, assume it's a list of plays/tasks
43+
if len(data) == 1 and isinstance(data[0], dict):
44+
# Take the first item if it's a dictionary
45+
play_data = data[0]
46+
else:
47+
# If it's a list of tasks, wrap it in a play structure
48+
task_only = True
49+
play_data = {"tasks": data}
50+
elif isinstance(data, dict):
51+
play_data = data
52+
else:
53+
raise ValueError(f"Unsupported YAML structure. Expected dict or list, got {type(data)}")
54+
55+
hosts = play_data.get("hosts", "localhost")
56+
vars_data = play_data.get("vars", {})
57+
tasks = play_data.get("tasks", [])
58+
name = play_data.get("name", "sample converted playbook")
59+
gather_facts = play_data.get("gather_facts", False)
60+
if verbose:
61+
print("tasks:", tasks)
62+
new_tasks = []
63+
for task in tasks:
64+
taskname = task.get("name", "")
65+
delegate_to = task.get("delegate_to", "localhost")
66+
register = task.get("register", None)
67+
if isinstance(task, dict):
68+
for pluginkey, pluginvalue in task.items():
69+
# Skip non-module keys like 'name' and 'delegate_to'
70+
if pluginkey in ['name', 'delegate_to']:
71+
continue
72+
if verbose:
73+
print(f"Module: {pluginkey}, Parameters: {pluginvalue}")
74+
if pluginkey.split('.')[-1] in resource_map:
75+
new_resource_name = resource_map[pluginkey.split('.')[-1]]
76+
if verbose:
77+
print(f"Remapped {pluginkey} to {new_resource_name}")
78+
new_task = {
79+
"name": taskname,
80+
"delegate_to": delegate_to,
81+
new_resource_name: pluginvalue,
82+
}
83+
elif pluginkey == "citrix_adc_nitro_request":
84+
newplugin = {}
85+
if verbose:
86+
print(f"Processing citrix_adc_nitro_request for {taskname}")
87+
operation = pluginvalue.get("operation", "present")
88+
if operation == "action":
89+
operation = pluginkey.get("action", "")
90+
state = state_map[operation]
91+
resource = pluginvalue.get("resource", None)
92+
entityname = pluginvalue.get("name", "")
93+
if resource is None:
94+
print(f"Resource not found for {pluginkey}, skipping")
95+
continue
96+
attributeslist = pluginvalue.get("attributes", [])
97+
98+
login_attributes = get_state_attributes(pluginvalue)
99+
newplugin["state"] = state
100+
newplugin.update(login_attributes)
101+
102+
# Handle attributes first
103+
if attributeslist != []:
104+
if verbose:
105+
print(f'attribute list: {attributeslist}')
106+
newplugin.update(attributeslist)
107+
else:
108+
newplugin["name"] = entityname
109+
110+
new_task = {
111+
"name": taskname,
112+
"delegate_to": delegate_to,
113+
f'netscaler.adc.{resource}': newplugin,
114+
}
115+
116+
else:
117+
# Keep original name if no mapping found
118+
new_resource_name = pluginkey
119+
print(f"No mapping found for {pluginkey}, keeping original name")
120+
121+
122+
new_tasks.append(new_task)
123+
124+
# Playbook struct
125+
if task_only:
126+
playbook = new_tasks
127+
else:
128+
playbook = [{
129+
"name": name,
130+
"hosts": hosts,
131+
"gather_facts": gather_facts,
132+
"vars": vars_data,
133+
"tasks": new_tasks
134+
}]
135+
136+
# Write the playbook directly as YAML without template
137+
with open(output_file, 'w') as outfile:
138+
outfile.write("---\n")
139+
yaml.dump(playbook, outfile, default_flow_style=False, sort_keys=False, Dumper=CustomDumper, indent=2)
140+
141+
print(f"Output written to: {output_file}")
142+
143+
def main():
144+
parser = argparse.ArgumentParser(description="Convert YAML files for migration")
145+
parser.add_argument("-i", "--input", required=True, help="Input YAML file")
146+
parser.add_argument("-o", "--output", required=False, help="Output YAML file")
147+
parser.add_argument("-v", "--verbose", action="store_true", help="verbose mode")
148+
args = parser.parse_args()
149+
150+
input_file = args.input
151+
print(f"Input file: {input_file}")
152+
output_file = args.output if args.output else "output.yaml"
153+
template_file = "./template.j2"
154+
verbose = args.verbose
155+
156+
print("Starting YAML conversion process...")
157+
convert_yaml_file(input_file, output_file, template_file, verbose)
158+
print("Conversion completed successfully!")
159+
160+
if __name__ == "__main__":
161+
main()

migrationtool/resourcelist.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
resource_map = {
2+
"citrix_adc_appfw_confidfield" : "netscaler.adc.appfwconfidfield",
3+
"citrix_adc_appfw_fieldtype" : "netscaler.adc.appfwfieldtype",
4+
"citrix_adc_appfw_global_bindings" : "netscaler.adc.appfwglobal_bindings",
5+
"citrix_adc_appfw_htmlerrorpage" : "netscaler.adc.appfwhtmlerrorpage",
6+
"citrix_adc_appfw_jsoncontenttype" : "netscaler.adc.appfwjsoncontenttype",
7+
"citrix_adc_appfw_learningsettings" : "netscaler.adc.appfwlearningsettings",
8+
"citrix_adc_appfw_policylabel": "netscaler.adc.appfwpolicylabel",
9+
"citrix_adc_appfw_policy": "netscaler.adc.appfwpolicy",
10+
"citrix_adc_appfw_profile": "netscaler.adc.appfwprofile",
11+
"citrix_adc_appfw_settings": "netscaler.adc.appfwsettings",
12+
"citrix_adc_appfw_signatures": "netscaler.adc.appfwsignatures",
13+
"citrix_adc_appfw_wsdl": "netscaler.adc.appfwwsdl",
14+
"citrix_adc_appfw_xmlcontenttype": "netscaler.adc.appfwxmlcontenttype",
15+
"citrix_adc_appfw_xmlerrorpage": "netscaler.adc.appfwxmlerrorpage",
16+
"citrix_adc_appfw_xmlschema": "netscaler.adc.appfwxmlschema",
17+
"citrix_adc_cs_action": "netscaler.adc.csaction",
18+
"citrix_adc_cs_policy": "netscaler.adc.cspolicy",
19+
"citrix_adc_cs_vserver": "netscaler.adc.csvserver",
20+
"citrix_adc_dnsnsrec": "netscaler.adc.dnsnsrec",
21+
"citrix_adc_get_bearer_token": "netscaler.adc.get_bearer_token",
22+
"citrix_adc_gslb_service": "netscaler.adc.gslbservice",
23+
"citrix_adc_gslb_site": "netscaler.adc.gslbsite",
24+
"citrix_adc_gslb_vserver": "netscaler.adc.gslbvserver",
25+
"citrix_adc_lb_monitor": "netscaler.adc.lbmonitor",
26+
"citrix_adc_lb_vserver": "netscaler.adc.lbvserver",
27+
"citrix_adc_nsip": "netscaler.adc.nsip",
28+
"citrix_adc_nspartition": "netscaler.adc.nspartition",
29+
"citrix_adc_password_reset": "netscaler.adc.password_reset",
30+
"citrix_adc_save_config": "netscaler.adc.save_config",
31+
"citrix_adc_server": "netscaler.adc.server",
32+
"citrix_adc_servicegroup": "netscaler.adc.servicegroup",
33+
"citrix_adc_service": "netscaler.adc.service",
34+
"citrix_adc_ssl_certkey": "netscaler.adc.sslcertkey",
35+
"citrix_adc_sslcipher": "netscaler.adc.sslcipher",
36+
"citrix_adc_sslcipher_sslciphersuite_binding": "netscaler.adc.sslcipher_sslciphersuite_binding",
37+
"citrix_adc_sslprofile_sslcipher_binding": "netscaler.adc.sslprofile_sslcipher_binding",
38+
"citrix_adc_system_file": "netscaler.adc.systemfile",
39+
}
40+
41+
state_map = {
42+
"add": "present",
43+
"update": "present",
44+
"delete": "absent",
45+
"enable": "enable",
46+
"disable": "disable",
47+
"present": "present",
48+
"absent": "absent",
49+
}
50+
if __name__ == "__main__":
51+
pass

0 commit comments

Comments
 (0)