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