44from ansible .errors import AnsibleError
55from ansible .plugins .action import ActionBase
66from ansible .utils .vars import merge_hash
7+ from ansible .utils .display import Display
78from ansible_collections .netways .icinga .plugins .module_utils .parse import Icinga2Parser
89
910
@@ -13,111 +14,133 @@ def run(self, tmp=None, task_vars=None):
1314
1415 result = super (ActionModule , self ).run (tmp , task_vars )
1516
16- args = dict ()
17- args = self ._task .args .copy ()
18- args = merge_hash (args .pop ('args' , {}), args )
19- object_type = args .pop ('type' , None )
20-
21- if object_type not in task_vars ['icinga2_object_types' ]:
22- raise AnsibleError ('unknown Icinga object type: %s' % object_type )
23-
24- #
25- # distribute to object type as module (name: icinga2_type)
26- #
27- obj = dict ()
28- obj = self ._execute_module (
29- module_name = 'icinga2_' + object_type .lower (),
30- module_args = args ,
31- task_vars = task_vars ,
32- tmp = tmp
33- )
34-
35- if 'failed' in obj :
36- raise AnsibleError ('Call to module failed: %s' % obj ['msg' ])
37- if 'skipped' in obj and obj ['skipped' ]:
38- raise AnsibleError ('Call to module was skipped: %s' % obj ['msg' ])
39-
40- #
41- # file path handling for assemble
42- #
43- path = task_vars ['icinga2_fragments_path' ] + '/' + obj ['file' ] + '/'
44- file_fragment = path + obj ['order' ] + '_' + object_type .lower () + '-' + obj ['name' ]
45-
46- if obj ['state' ] != 'absent' :
47- file_args = dict ()
48- file_args ['state' ] = 'directory'
49- file_args ['path' ] = path
50- file_module = self ._execute_module (
51- module_name = 'file' ,
52- module_args = file_args ,
53- task_vars = task_vars ,
54- tmp = tmp
17+ arguments = dict ()
18+ arguments = self ._task .args .copy ()
19+
20+ # Create dict to bundle objects that will end up in the same file
21+ destinations = dict ()
22+
23+ # Deprecate order key
24+ display = Display ()
25+ if [x for x in arguments ['objects' ] if 'order' in x ]:
26+ display .deprecated (
27+ 'The \' order\' parameter for different object types is deprecated. It no longer has any effect.'
5528 )
56- result = merge_hash (result , file_module )
5729
58- varlist = list () # list of variables from 'apply for'
30+ for args in arguments ['objects' ]:
31+ args = merge_hash (args .pop ('args' , {}), args )
32+ object_type = args .pop ('type' , None )
33+
34+ if object_type not in task_vars ['icinga2_object_types' ]:
35+ raise AnsibleError ('unknown Icinga object type: %s' % object_type )
5936
6037 #
61- # quoting of object name?
38+ # distribute to object type as module ( name: icinga2_type)
6239 #
63- if obj ['name' ] not in task_vars ['icinga2_combined_constants' ]:
64- object_name = '"' + obj ['name' ] + '"'
65- else :
66- object_name = obj ['name' ]
40+ obj = dict ()
41+ obj = self ._execute_module (
42+ module_name = 'icinga2_' + object_type .lower (),
43+ module_args = args ,
44+ task_vars = task_vars ,
45+ tmp = tmp
46+ )
47+
48+ if 'failed' in obj :
49+ raise AnsibleError ('Call to module failed: %s' % obj ['msg' ])
50+ if 'skipped' in obj and obj ['skipped' ]:
51+ raise AnsibleError ('Call to module was skipped: %s' % obj ['msg' ])
6752
6853 #
69- # apply rule?
54+ # file path handling for assemble
7055 #
71- if 'apply' in obj and obj ['apply' ] and not obj ['args' ]['assign' ]:
72- raise AnsibleError ('Apply rule %s is missing the assign rule.' % obj ['name' ])
73- if 'apply' in obj and obj ['apply' ]:
74- object_content = 'apply ' + object_type
75- if 'apply_target' in obj and obj ['apply_target' ]:
76- object_content += ' ' + object_name + ' to ' + obj ['apply_target' ]
77- elif 'apply_for' in obj and obj ['apply_for' ]:
78- object_content += ' for (' + obj ['apply_for' ] + ') '
79- r = re .search (r'^(.+)\s+in\s+' , obj ['apply_for' ])
80- if r :
81- tmp = r .group (1 ).strip ()
82- r = re .search (r'^(.+)=>(.+)$' , tmp )
56+ path = task_vars ['icinga2_fragments_path' ] + '/' + obj ['file' ]
57+ if path not in destinations :
58+ destinations [path ] = list ()
59+
60+ if obj ['state' ] != 'absent' :
61+ varlist = list () # list of variables from 'apply for'
62+
63+ #
64+ # quoting of object name?
65+ #
66+ if obj ['name' ] not in task_vars ['icinga2_combined_constants' ]:
67+ object_name = '"' + obj ['name' ] + '"'
68+ else :
69+ object_name = obj ['name' ]
70+
71+ #
72+ # apply rule?
73+ #
74+ if 'apply' in obj and obj ['apply' ] and not obj ['args' ]['assign' ]:
75+ raise AnsibleError ('Apply rule %s is missing the assign rule.' % obj ['name' ])
76+ if 'apply' in obj and obj ['apply' ]:
77+ object_content = 'apply ' + object_type
78+ if 'apply_target' in obj and obj ['apply_target' ]:
79+ object_content += ' ' + object_name + ' to ' + obj ['apply_target' ]
80+ elif 'apply_for' in obj and obj ['apply_for' ]:
81+ object_content += ' for (' + obj ['apply_for' ] + ') '
82+ r = re .search (r'^(.+)\s+in\s+' , obj ['apply_for' ])
8383 if r :
84- varlist .extend ([r .group (1 ).strip (), r .group (2 ).strip ()])
85- else :
86- varlist .append (tmp )
84+ tmp = r .group (1 ).strip ()
85+ r = re .search (r'^(.+)=>(.+)$' , tmp )
86+ if r :
87+ varlist .extend ([r .group (1 ).strip (), r .group (2 ).strip ()])
88+ else :
89+ varlist .append (tmp )
90+ else :
91+ object_content += ' ' + object_name
92+ #
93+ # template?
94+ #
95+ elif 'template' in obj and obj ['template' ]:
96+ object_content = 'template ' + object_type + ' ' + object_name
97+ #
98+ # object
99+ #
87100 else :
88- object_content += ' ' + object_name
89- #
90- # template?
91- #
92- elif 'template' in obj and obj ['template' ]:
93- object_content = 'template ' + object_type + ' ' + object_name
94- #
95- # object
96- #
97- else :
98- object_content = 'object ' + object_type + ' ' + object_name
99- object_content += ' {\n '
101+ object_content = 'object ' + object_type + ' ' + object_name
102+ object_content += ' {\n '
100103
101- #
102- # imports?
103- #
104- if 'imports' in obj :
105- for item in obj ['imports' ]:
106- object_content += ' import "' + str (item ) + '"\n '
107- object_content += '\n '
104+ #
105+ # imports?
106+ #
107+ if 'imports' in obj :
108+ for item in obj ['imports' ]:
109+ object_content += ' import "' + str (item ) + '"\n '
110+ object_content += '\n '
111+
112+ #
113+ # parser
114+ #
115+ object_content += Icinga2Parser ().parse (obj ['args' ], list (task_vars ['icinga2_combined_constants' ].keys ())+ task_vars ['icinga2_reserved' ]+ varlist + list (obj ['args' ].keys ()), 2 ) + '}\n '
116+ destinations [path ] += [object_content ]
117+
118+
119+
120+ for destination , objects in destinations .items ():
121+ # Remove duplicate entries and sort list to ensure idempotency
122+ objects = list (set (objects ))
123+ objects .sort ()
124+
125+ config_string = '\n \n ' .join (objects )
126+
127+ file_args = dict ()
128+ file_args ['state' ] = 'directory'
129+ file_args ['path' ] = '/' .join (destination .split ('/' )[:- 1 ])
130+ file_module = self ._execute_module (
131+ module_name = 'file' ,
132+ module_args = file_args ,
133+ task_vars = task_vars ,
134+ tmp = tmp
135+ )
136+ result = merge_hash (result , file_module )
137+
138+ varlist = list () # list of variables from 'apply for'
108139
109- #
110- # parser
111- #
112- object_content += Icinga2Parser ().parse (
113- obj ['args' ],
114- list (task_vars ['icinga2_combined_constants' ].keys ()) + task_vars ['icinga2_reserved' ] + varlist + list (obj ['args' ].keys ()),
115- 2
116- ) + '}\n '
117140 copy_action = self ._task .copy ()
118141 copy_action .args = dict ()
119- copy_action .args ['dest' ] = file_fragment
120- copy_action .args ['content' ] = object_content
142+ copy_action .args ['dest' ] = destination
143+ copy_action .args ['content' ] = config_string
121144
122145 copy_action = self ._shared_loader_obj .action_loader .get (
123146 'copy' ,
@@ -130,19 +153,6 @@ def run(self, tmp=None, task_vars=None):
130153 )
131154
132155 result = merge_hash (result , copy_action .run (task_vars = task_vars ))
133- else :
134- # remove file if does not belong to a feature
135- if 'features-available' not in path :
136- file_args = dict ()
137- file_args ['state' ] = 'absent'
138- file_args ['path' ] = file_fragment
139- file_module = self ._execute_module (
140- module_name = 'file' ,
141- module_args = file_args ,
142- task_vars = task_vars ,
143- tmp = tmp
144- )
145- result = merge_hash (result , file_module )
146- result ['dest' ] = file_fragment
147156
157+ result ['destinations' ] = list (destinations .keys ())
148158 return result
0 commit comments