Merge PR #1641: Reconcile Docker members dynamically and add weekly Joanna review

Reconcile Docker monitored members dynamically and add weekly Joanna drift check
master
Carlo Costanzo 10 hours ago committed by GitHub
commit fefe9e83a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -199,5 +199,8 @@
type: custom:button-card
template: bearstone_infra_container_row
icon: mdi:docker
exclude:
- state: unavailable
- state: unknown
sort:
method: name

@ -614,7 +614,7 @@ bearstone_infra_container_row:
}
const switchEntity = key ? `switch.${key}_container` : '';
const switchEntityAlt = key ? `switch.${key}_container_2` : '';
const monitored = states['switch.docker_monitored_containers']?.attributes?.entity_id;
const monitored = states['sensor.docker_monitored_switch_inventory']?.attributes?.entity_id;
const restartCandidates = key ? [
`button.${key}_restart_container`,
`button.${key}_restart_container_2`,

@ -11,6 +11,7 @@
# Notes: Reboots are handled directly on each host by apt_weekly.sh.
# Notes: Reboot staggering: docker_14 first, docker_69 second, docker_10 third.
# Notes: Container monitoring is dynamic with binary_sensor status preferred over switch state.
# Notes: Weekly Joanna reconcile checks discovered container switches vs configured group members.
# Notes: Includes Portainer stack status repairs, 20-minute Joanna dispatch for persistent container outages, and scheduled image prune.
######################################################################
@ -203,18 +204,117 @@ template:
{% endif %}
- sensor:
- name: "Docker Monitored Switch Inventory"
unique_id: docker_monitored_switch_inventory
icon: mdi:docker
state: >-
{% set ns = namespace(items=[]) %}
{% for item in states.switch %}
{% set ent = item.entity_id %}
{% if ent is search('^switch\\..*_container(?:_2)?$') %}
{% set key = ent | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% set has_companion = expand('binary_sensor.' ~ key ~ '_status') | count > 0
or expand('binary_sensor.' ~ key ~ '_status_2') | count > 0
or expand('sensor.' ~ key ~ '_state') | count > 0
or expand('sensor.' ~ key ~ '_state_2') | count > 0 %}
{% set switch_state = states(ent) | lower %}
{% if has_companion or switch_state not in ['unknown', 'unavailable', ''] %}
{% if ent not in ns.items %}
{% set ns.items = ns.items + [ent] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.items | sort | count }}
attributes:
entity_id: >-
{% set ns = namespace(items=[]) %}
{% for item in states.switch %}
{% set ent = item.entity_id %}
{% if ent is search('^switch\\..*_container(?:_2)?$') %}
{% set key = ent | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% set has_companion = expand('binary_sensor.' ~ key ~ '_status') | count > 0
or expand('binary_sensor.' ~ key ~ '_status_2') | count > 0
or expand('sensor.' ~ key ~ '_state') | count > 0
or expand('sensor.' ~ key ~ '_state_2') | count > 0 %}
{% set switch_state = states(ent) | lower %}
{% if has_companion or switch_state not in ['unknown', 'unavailable', ''] %}
{% if ent not in ns.items %}
{% set ns.items = ns.items + [ent] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.items | sort }}
configured_group_members: >-
{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list | sort }}
missing_from_group: >-
{% set discovered_ns = namespace(items=[]) %}
{% for item in states.switch %}
{% set ent = item.entity_id %}
{% if ent is search('^switch\\..*_container(?:_2)?$') %}
{% set key = ent | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% set has_companion = expand('binary_sensor.' ~ key ~ '_status') | count > 0
or expand('binary_sensor.' ~ key ~ '_status_2') | count > 0
or expand('sensor.' ~ key ~ '_state') | count > 0
or expand('sensor.' ~ key ~ '_state_2') | count > 0 %}
{% set switch_state = states(ent) | lower %}
{% if has_companion or switch_state not in ['unknown', 'unavailable', ''] %}
{% if ent not in discovered_ns.items %}
{% set discovered_ns.items = discovered_ns.items + [ent] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% set discovered = discovered_ns.items | list %}
{% set configured = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list %}
{% set ns = namespace(items=[]) %}
{% for ent in discovered %}
{% if ent not in configured %}
{% set ns.items = ns.items + [ent] %}
{% endif %}
{% endfor %}
{{ ns.items | sort }}
stale_group_members: >-
{% set discovered_ns = namespace(items=[]) %}
{% for item in states.switch %}
{% set ent = item.entity_id %}
{% if ent is search('^switch\\..*_container(?:_2)?$') %}
{% set key = ent | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% set has_companion = expand('binary_sensor.' ~ key ~ '_status') | count > 0
or expand('binary_sensor.' ~ key ~ '_status_2') | count > 0
or expand('sensor.' ~ key ~ '_state') | count > 0
or expand('sensor.' ~ key ~ '_state_2') | count > 0 %}
{% set switch_state = states(ent) | lower %}
{% if has_companion or switch_state not in ['unknown', 'unavailable', ''] %}
{% if ent not in discovered_ns.items %}
{% set discovered_ns.items = discovered_ns.items + [ent] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% set discovered = discovered_ns.items | list %}
{% set configured = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list %}
{% set ns = namespace(items=[]) %}
{% for ent in configured %}
{% if ent not in discovered %}
{% set ns.items = ns.items + [ent] %}
{% endif %}
{% endfor %}
{{ ns.items | sort }}
- name: "Docker Monitored Container Count"
unique_id: docker_monitored_container_count
icon: mdi:format-list-numbered
state: >-
{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | count }}
{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | count }}
- name: "Docker Monitored Unavailable Count"
unique_id: docker_monitored_unavailable_count
icon: mdi:lan-disconnect
state: >-
{% set ns = namespace(keys=[], unavailable=0) %}
{% set monitored = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) %}
{% set monitored = state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) %}
{% for switch_entity in monitored %}
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% if key not in ns.keys %}
@ -258,7 +358,7 @@ template:
icon: mdi:docker
state: >-
{% set ns = namespace(keys=[], down=[]) %}
{% set monitored = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list %}
{% set monitored = state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list %}
{% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %}
{% for switch_entity in monitored %}
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
@ -302,7 +402,7 @@ template:
attributes:
down_containers: >-
{% set ns = namespace(keys=[], down=[]) %}
{% set monitored = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list %}
{% set monitored = state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list %}
{% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %}
{% for switch_entity in monitored %}
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
@ -438,7 +538,7 @@ script:
status_entity_alt: "binary_sensor.{{ container_key }}_status_2"
state_entity: "sensor.{{ container_key }}_state"
state_entity_alt: "sensor.{{ container_key }}_state_2"
monitored_switches: "{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) }}"
monitored_switches: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) }}"
tracked_container: "{{ switch_entity in monitored_switches or switch_entity_alt in monitored_switches }}"
effective_entity: >-
{% if expand(status_entity) | count > 0 %}
@ -799,7 +899,7 @@ automation:
entity_id: "{{ trigger.event.data.entity_id | default('') }}"
old_state: "{{ (trigger.event.data.old_state.state if trigger.event.data.old_state is not none else '') | lower }}"
new_state: "{{ (trigger.event.data.new_state.state if trigger.event.data.new_state is not none else '') | lower }}"
monitored_switches: "{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list }}"
monitored_switches: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list }}"
is_monitored_container_event: >-
{% set ent = entity_id %}
{% if ent.startswith('switch.') and (ent.endswith('_container') or ent.endswith('_container_2')) %}
@ -889,7 +989,7 @@ automation:
minutes: "/55"
action:
- variables:
monitored_switches: "{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list }}"
monitored_switches: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list }}"
- repeat:
for_each: "{{ monitored_switches }}"
sequence:
@ -1010,11 +1110,64 @@ automation:
- service: homeassistant.update_entity
target:
entity_id:
- sensor.docker_monitored_switch_inventory
- sensor.docker_monitored_unavailable_count
- sensor.docker_containers_down_list
- sensor.docker_containers_down_count
- binary_sensor.docker_container_telemetry_degraded
- alias: "Docker Group Reconcile - Weekly Joanna Review"
id: docker_group_reconcile_weekly_joanna_review
description: "Weekly reconciliation of discovered Docker container entities vs configured group members."
mode: single
trigger:
- platform: time
at: "08:45:00"
condition:
- condition: time
weekday:
- sun
action:
- variables:
discovered_members: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list }}"
configured_members: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'configured_group_members') | default([], true) | list }}"
missing_members: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'missing_from_group') | default([], true) | list }}"
stale_members: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'stale_group_members') | default([], true) | list }}"
- service: script.send_to_logbook
data:
topic: "DOCKER"
message: >-
Weekly reconcile check: discovered={{ discovered_members | count }},
configured={{ configured_members | count }},
missing={{ missing_members | count }},
stale={{ stale_members | count }}.
- choose:
- conditions:
- condition: template
value_template: "{{ (missing_members | count > 0) or (stale_members | count > 0) }}"
sequence:
- service: script.joanna_dispatch
data:
trigger_context: >-
HA automation docker_group_reconcile_weekly_joanna_review
(Docker Group Reconcile - Weekly Joanna Review)
source: "home_assistant_automation.docker_group_reconcile_weekly_joanna_review"
summary: >-
Docker group membership drift detected (missing={{ missing_members | count }},
stale={{ stale_members | count }}).
entity_ids:
- sensor.docker_monitored_switch_inventory
- switch.docker_monitored_containers
diagnostics: >-
discovered={{ discovered_members | join(', ') if (discovered_members | count > 0) else 'none' }};
configured={{ configured_members | join(', ') if (configured_members | count > 0) else 'none' }};
missing={{ missing_members | join(', ') if (missing_members | count > 0) else 'none' }};
stale={{ stale_members | join(', ') if (stale_members | count > 0) else 'none' }}.
request: >-
Reconcile Docker monitored group members in
config/packages/docker_infrastructure.yaml so configured monitoring
matches currently discovered container entities.
- alias: "Docker Weekly Prune Unused Images"
id: docker_weekly_prune_unused_images
description: "Run weekly unguarded prune actions across Docker hosts."

Loading…
Cancel
Save

Powered by TurnKey Linux.