Skip to content

Commit 21fc3fa

Browse files
committed
fix: adding progress role and functionality
1 parent 31e3452 commit 21fc3fa

File tree

8 files changed

+271
-15
lines changed

8 files changed

+271
-15
lines changed
Submodule core updated 1 file
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
use_default_rules: true
2+
exclude_paths:
3+
- ../ansible_collections/ # Relative to this .ansible-lint file's location

hcl_domino_additional_provisioner/provisioners/ansible/ansible.cfg

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ localhost_warning=False
2929
ansible_winrm_server_cert_validation=ignore
3030

3131
# change the default callback, you can only have one 'stdout' type enabled at a time.
32-
stdout_callback = yaml
32+
stdout_callback = default
33+
#stdout_callback = yaml
3334
#stdout_callback = actionable
3435
#stdout_callback = log_plays
3536
bin_ansible_callbacks = True
@@ -136,4 +137,4 @@ command_timeout = 30
136137
#accelerate_timeout = 30
137138
#accelerate_connect_timeout = 5.0
138139
#accelerate_daemon_timeout = 30
139-
#accelerate_multi_key = yes
140+
#accelerate_multi_key = yes
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import os
2+
import yaml
3+
from ansible.errors import AnsibleFilterError
4+
from ansible.utils.display import Display
5+
6+
display = Display()
7+
8+
class FilterModule(object):
9+
def filters(self):
10+
return {
11+
'resolve_progress_dependencies': self.resolve_progress_dependencies
12+
}
13+
14+
def _get_role_fqcn_parts(self, role_name_str):
15+
"""Splits a role name (potentially FQCN) into (namespace, collection, simple_name)"""
16+
parts = role_name_str.split('.')
17+
if len(parts) >= 3:
18+
return parts[0], parts[1], '.'.join(parts[2:]) # Handles simple_name containing dots
19+
elif len(parts) == 1: # Simple name
20+
return None, None, parts[0]
21+
else: # Ambiguous, treat as simple for now or could be an error
22+
display.vvv(f"Ambiguous role name for FQCN parsing: {role_name_str}")
23+
return None, None, role_name_str
24+
25+
26+
def _get_role_meta(self, role_path):
27+
meta_file = os.path.join(role_path, 'meta', 'main.yml')
28+
if not os.path.exists(meta_file):
29+
display.vvv(f"Meta file not found: {meta_file}")
30+
return None
31+
try:
32+
with open(meta_file, 'r') as f:
33+
content = yaml.safe_load(f)
34+
display.vvv(f"Meta content for {role_path}: {content}")
35+
return content
36+
except Exception as e:
37+
display.warning(f"Error parsing meta file {meta_file}: {e}")
38+
return None
39+
40+
def _get_role_defaults(self, role_path):
41+
defaults_file = os.path.join(role_path, 'defaults', 'main.yml')
42+
if not os.path.exists(defaults_file):
43+
display.vvv(f"Defaults file not found: {defaults_file}")
44+
return {}
45+
try:
46+
with open(defaults_file, 'r') as f:
47+
content = yaml.safe_load(f) or {}
48+
display.vvv(f"Defaults content for {role_path}: {content}")
49+
return content
50+
except Exception as e:
51+
display.warning(f"Error parsing defaults file {defaults_file}: {e}")
52+
return {}
53+
54+
def resolve_progress_dependencies(self, initial_roles_config, playbook_dir, ansible_collections_base_relative_path):
55+
"""
56+
:param initial_roles_config: List of role dicts from provision_roles
57+
:param playbook_dir: The directory of the playbook being run.
58+
:param ansible_collections_base_relative_path: Relative path from playbook_dir to the root of ansible_collections (e.g., '../ansible_collections')
59+
"""
60+
display.v(f"Starting progress dependency resolution. Playbook dir: {playbook_dir}")
61+
display.v(f"Received initial_roles_config: {initial_roles_config}")
62+
display.v(f"Ansible collections base relative path: {ansible_collections_base_relative_path}")
63+
64+
all_roles_data = {} # Keyed by FQCN
65+
abs_ansible_collections_base = os.path.normpath(os.path.join(playbook_dir, ansible_collections_base_relative_path))
66+
display.v(f"Absolute ansible_collections base path: {abs_ansible_collections_base}")
67+
68+
if not os.path.isdir(abs_ansible_collections_base):
69+
display.error(f"Ansible collections base directory not found: {abs_ansible_collections_base}")
70+
return [] # Return empty if base collections dir is not found
71+
72+
# Phase 1: Discover all roles by scanning the ansible_collections_base_path
73+
for namespace_name in os.listdir(abs_ansible_collections_base):
74+
namespace_path = os.path.join(abs_ansible_collections_base, namespace_name)
75+
if not os.path.isdir(namespace_path):
76+
continue
77+
78+
for collection_name in os.listdir(namespace_path):
79+
collection_path = os.path.join(namespace_path, collection_name)
80+
if not os.path.isdir(collection_path):
81+
continue
82+
83+
abs_collection_roles_dir = os.path.join(collection_path, 'roles')
84+
display.v(f"Scanning for roles in: {abs_collection_roles_dir} (collection: {namespace_name}.{collection_name})")
85+
86+
if not os.path.isdir(abs_collection_roles_dir):
87+
display.vvv(f"No 'roles' directory in collection {namespace_name}.{collection_name} at {collection_path}")
88+
continue
89+
90+
for simple_role_name in os.listdir(abs_collection_roles_dir):
91+
role_path = os.path.join(abs_collection_roles_dir, simple_role_name)
92+
if os.path.isdir(role_path):
93+
fqcn = f"{namespace_name}.{collection_name}.{simple_role_name}"
94+
# This block should be nested to ensure fqcn is defined
95+
if fqcn not in all_roles_data:
96+
display.vv(f"Found role '{simple_role_name}' (FQCN: {fqcn}) at {role_path}")
97+
meta_content = self._get_role_meta(role_path)
98+
defaults_content = self._get_role_defaults(role_path)
99+
all_roles_data[fqcn] = {
100+
'path': role_path,
101+
'meta': meta_content,
102+
'defaults': defaults_content,
103+
'name': simple_role_name, # Simple name
104+
'fqcn': fqcn,
105+
'namespace': namespace_name,
106+
'collection': collection_name
107+
}
108+
display.vvv(f"All discovered roles data (keyed by FQCN): {list(all_roles_data.keys())}")
109+
110+
# Phase 2: Recursive resolution
111+
final_progress_definitions = []
112+
roles_to_process_queue = []
113+
for r_conf in initial_roles_config:
114+
if r_conf.get('enabled', True): # Default to enabled
115+
# r_conf['name'] is expected to be an FQCN from Hosts.yml
116+
roles_to_process_queue.append({'fqcn': r_conf['name'], 'source_vars': r_conf.get('vars', {})})
117+
118+
counted_role_fqcns = set()
119+
explored_for_dependencies_fqcns = set()
120+
121+
display.v(f"Initial processing queue: {roles_to_process_queue}")
122+
123+
idx = 0
124+
while idx < len(roles_to_process_queue):
125+
current_task = roles_to_process_queue[idx]
126+
idx += 1
127+
128+
current_fqcn = current_task['fqcn']
129+
source_vars = current_task['source_vars']
130+
131+
display.vv(f"Processing '{current_fqcn}' from queue. Source vars: {source_vars}")
132+
133+
if current_fqcn in counted_role_fqcns and current_fqcn in explored_for_dependencies_fqcns:
134+
display.vvv(f"'{current_fqcn}' already counted and explored. Skipping.")
135+
continue
136+
137+
role_data = all_roles_data.get(current_fqcn)
138+
if not role_data:
139+
display.warning(f"Role '{current_fqcn}' (from provision_roles/dependency) not found in scanned collections. Skipping.")
140+
continue
141+
142+
role_defaults = role_data.get('defaults', {})
143+
count_this_role_progress = source_vars.get('count_progress', role_defaults.get('count_progress', False))
144+
if isinstance(count_this_role_progress, str):
145+
count_this_role_progress = count_this_role_progress.lower() == 'true'
146+
147+
display.vvv(f"Role '{current_fqcn}': count_progress={count_this_role_progress} (source: {source_vars.get('count_progress')}, default: {role_defaults.get('count_progress')})")
148+
149+
if count_this_role_progress and current_fqcn not in counted_role_fqcns:
150+
progress_units = source_vars.get('progress_units', role_defaults.get('progress_units', 1))
151+
try:
152+
progress_units = int(progress_units)
153+
except ValueError:
154+
display.warning(f"Invalid progress_units '{progress_units}' for role '{current_fqcn}'. Defaulting to 1.")
155+
progress_units = 1
156+
157+
final_progress_definitions.append({'name': current_fqcn, 'progress_units': progress_units})
158+
counted_role_fqcns.add(current_fqcn)
159+
display.vv(f"Added '{current_fqcn}' to progress count with {progress_units} units.")
160+
161+
if current_fqcn not in explored_for_dependencies_fqcns:
162+
explored_for_dependencies_fqcns.add(current_fqcn)
163+
meta = role_data.get('meta')
164+
if meta and 'dependencies' in meta and isinstance(meta['dependencies'], list):
165+
for dep in meta['dependencies']:
166+
dep_name_str = None
167+
dep_vars = {}
168+
if isinstance(dep, dict):
169+
dep_name_str = dep.get('role')
170+
dep_vars = {k: v for k, v in dep.items() if k != 'role'}
171+
elif isinstance(dep, str):
172+
dep_name_str = dep
173+
174+
if not dep_name_str:
175+
continue
176+
177+
dep_fqcn_to_queue = None
178+
# Check if dep_name_str is already an FQCN
179+
if '.' in dep_name_str and len(dep_name_str.split('.')) >= 3 : # Heuristic for FQCN
180+
if dep_name_str in all_roles_data:
181+
dep_fqcn_to_queue = dep_name_str
182+
else:
183+
display.vvv(f"Dependency '{dep_name_str}' looks like FQCN but not found in all_roles_data.")
184+
else: # Simple name, try to resolve within current role's collection
185+
parent_namespace = role_data.get('namespace')
186+
parent_collection = role_data.get('collection')
187+
if parent_namespace and parent_collection:
188+
potential_fqcn = f"{parent_namespace}.{parent_collection}.{dep_name_str}"
189+
if potential_fqcn in all_roles_data:
190+
dep_fqcn_to_queue = potential_fqcn
191+
else:
192+
display.vvv(f"Simple dependency '{dep_name_str}' not found as '{potential_fqcn}' in same collection as '{current_fqcn}'.")
193+
else:
194+
display.vvv(f"Cannot resolve simple dependency '{dep_name_str}' for '{current_fqcn}' due to missing parent N/C info.")
195+
196+
if dep_fqcn_to_queue:
197+
is_in_queue = any(item['fqcn'] == dep_fqcn_to_queue for item in roles_to_process_queue[idx:])
198+
if dep_fqcn_to_queue not in explored_for_dependencies_fqcns and not is_in_queue:
199+
display.vvv(f"Queueing dependency '{dep_fqcn_to_queue}' of '{current_fqcn}' with vars {dep_vars}")
200+
roles_to_process_queue.append({'fqcn': dep_fqcn_to_queue, 'source_vars': dep_vars})
201+
else:
202+
display.vvv(f"Dependency '{dep_fqcn_to_queue}' of '{current_fqcn}' already explored or in queue. Skipping queue add.")
203+
else:
204+
display.warning(f"Could not resolve dependency '{dep_name_str}' for role '{current_fqcn}'. Skipping.")
205+
else:
206+
display.vvv(f"No dependencies found or meta missing for '{current_fqcn}'.")
207+
else:
208+
display.vvv(f"'{current_fqcn}' already explored for dependencies.")
209+
210+
display.v(f"Final progress definitions: {final_progress_definitions}")
211+
return final_progress_definitions
Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,47 @@
11
---
22
-
3-
name: "Generating hcl_domino_additional_provisioner Playbook Locally"
3+
name: "Generating switchboard_provisioner Playbook Locally"
44
become: true
55
gather_facts: true
66
hosts: all
7+
vars:
8+
# provision_roles is expected to be available as an extra_var from Hosts.rb
9+
_ansible_collections_base_relative_path: '../ansible_collections'
710
tasks:
11+
- name: "Resolve all progress role definitions including dependencies using Python filter"
12+
ansible.builtin.set_fact:
13+
_progress_setup_role_definitions: >
14+
{{
15+
provision_roles |
16+
resolve_progress_dependencies(
17+
playbook_dir,
18+
_ansible_collections_base_relative_path
19+
)
20+
}}
21+
22+
-
23+
name: "Debugging: _progress_setup_role_definitions content"
24+
ansible.builtin.debug:
25+
var: _progress_setup_role_definitions
26+
verbosity: 1 # Show only if -v is used
27+
828
-
929
name: "Dynamically generating template playbook for SHI"
1030
ansible.builtin.template:
11-
dest: "Hosts.template.yml.SHI"
31+
dest: "Hosts.template.yml"
1232
mode: a+x
1333
src: "Hosts.template.yml.j2"
34+
1435
-
1536
name: "Dynamically generating playbook"
1637
ansible.builtin.template:
1738
dest: "/vagrant/ansible/playbook.yml"
1839
mode: a+x
1940
src: "playbook.yml.j2"
41+
2042
-
2143
name: "Dynamically generating playbook"
2244
ansible.builtin.template:
2345
dest: "/vagrant/ansible/always-playbook.yml"
2446
mode: a+x
25-
src: "always-playbook.yml.j2"
47+
src: "always-playbook.yml.j2"

hcl_domino_additional_provisioner/provisioners/ansible/templates/playbook.yml.j2

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,23 @@
2222
{% for role in provision_roles %}
2323
- {{ role.name }}
2424
{% endfor %}
25-
enabled_roles:
25+
enabled_roles: # This list can be used by the progress role if needed
2626
{% for role in provision_roles %}
27-
{% set run_tasks = role.vars.run_tasks if role.vars is defined and 'run_tasks' in role.vars else true %}
27+
{% set vars_for_role = role.vars | default({}) %}
28+
{% set run_tasks = vars_for_role.run_tasks if 'run_tasks' in vars_for_role else true %}
2829
{% if run_tasks %}
29-
- {{ role.name }}
30+
- name: {{ role.name }} # Keep as a list of names or dicts as per your preference
31+
# Storing progress related info directly here if useful for other purposes or direct use by progress role
32+
# count_progress: {{ vars_for_role.count_progress | default(false) }}
33+
# progress_units: {{ vars_for_role.progress_units | default(1) | int }}
3034
{% endif %}
3135
{% endfor %}
36+
37+
# Initialize global_current_progress_step here
38+
global_current_progress_step: 0
39+
40+
# _progress_setup_role_definitions is now expected to be passed in by generate-playbook.yml
41+
# when it renders this template. It will be used below when calling the progress role.
3242
{% endif %}
3343
{% if playbook_collections is defined and playbook_collections and playbook_collections != "" %}
3444
collections:
@@ -38,12 +48,21 @@
3848
{% endif %}
3949
{% if provision_pre_tasks is defined and provision_pre_tasks and provision_pre_tasks != "" %}
4050
pre_tasks:
41-
{% for pre_task in provision_pre_tasks %}
42-
-
43-
{{ pre_task }}
51+
{# Append user-defined pre_tasks from Hosts.template.yml's 'pre_tasks' key #}
52+
{% for user_pre_task_content_block in provision_pre_tasks %}
53+
- # This creates the list item for the user-defined pre-task
54+
{{ user_pre_task_content_block | indent(2) }} # Indents the YAML string content of the task
4455
{% endfor %}
4556
{% endif %}
57+
4658
roles:
59+
# Explicitly include the progress role first for setup
60+
- role: startcloud.startcloud_roles.progress
61+
vars:
62+
_progress_role_is_setup_run: true
63+
_progress_role_definitions_to_calculate: "{{ _progress_setup_role_definitions }}"
64+
65+
# Original roles loop
4766
{% for role in provision_roles %}
4867
-
4968
role: {{ role.name }}
@@ -84,10 +103,11 @@
84103
{% endif %}
85104
{% endif %}
86105
{% endfor %}
106+
87107
{% if provision_post_tasks is defined and provision_post_tasks and provision_post_tasks != "" %}
88108
post_tasks:
89109
{% for post_task in provision_post_tasks %}
90110
-
91111
{{ post_task }}
92112
{% endfor %}
93-
{% endif %}
113+
{% endif %}
Submodule hcl_roles updated 38 files

0 commit comments

Comments
 (0)