1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
|
from __future__ import annotations
from itertools import chain
from typing import Optional
import typer
from zabbix_cli.app import Example
from zabbix_cli.app import app
from zabbix_cli.commands.common.args import ARG_HOSTNAMES_OR_IDS
from zabbix_cli.commands.common.args import ARG_TEMPLATE_NAMES_OR_IDS
from zabbix_cli.output.console import info
from zabbix_cli.output.console import success
from zabbix_cli.output.formatting.grammar import pluralize as p
from zabbix_cli.output.render import render_result
from zabbix_cli.utils.args import parse_hosts_arg
from zabbix_cli.utils.args import parse_list_arg
from zabbix_cli.utils.args import parse_templates_arg
HELP_PANEL = "Template"
@app.command(
"link_template_to_host",
rich_help_panel=HELP_PANEL,
examples=[
Example(
"Link one template to one host",
"link_template_to_host 'Apache by HTTP' foo.example.com",
),
Example(
"Link many templates to many hosts",
"link_template_to_host 'Apache by HTTP,HAProxy by Zabbix agent' foo.example.com,bar.example.com",
),
Example(
"Link one template to all hosts",
"link_template_to_host 'Apache by HTTP' '*'",
),
Example(
"Link many templates to all hosts",
"link_template_to_host 'Apache by HTTP,HAProxy by Zabbix agent' '*'",
),
Example(
"Link all templates to all hosts [red](use with caution!)[/red]",
"link_template_to_host '*' '*'",
),
],
)
def link_template_to_host(
ctx: typer.Context,
template_names_or_ids: str = ARG_TEMPLATE_NAMES_OR_IDS,
hostnames_or_ids: str = ARG_HOSTNAMES_OR_IDS,
strict: bool = typer.Option(
False,
"--strict",
help="Fail if any hosts or templates aren't found. Should not be used in conjunction with wildcards.",
),
dryrun: bool = typer.Option(
False,
"--dryrun",
help="Preview changes.",
),
) -> None:
"""Link templates to hosts."""
from zabbix_cli.commands.results.template import LinkTemplateToHostResult
from zabbix_cli.models import AggregateResult
templates = parse_templates_arg(
app, template_names_or_ids, strict=strict, select_hosts=True
)
hosts = parse_hosts_arg(app, hostnames_or_ids, strict=strict)
if not dryrun:
with app.state.console.status("Linking templates..."):
app.state.client.link_templates_to_hosts(templates, hosts)
result: list[LinkTemplateToHostResult] = []
for host in hosts:
r = LinkTemplateToHostResult.from_result(templates, host, "Link")
if not r.templates:
continue
result.append(r)
total_templates = len(set(chain.from_iterable((r.templates) for r in result)))
total_hosts = len(result)
render_result(AggregateResult(result=result))
base_msg = f"{p('template', total_templates)} to {p('host', total_hosts)}"
if dryrun:
info(f"Would link {base_msg}.")
else:
success(f"Linked {base_msg}.")
@app.command(
"link_template_to_template",
rich_help_panel=HELP_PANEL,
examples=[
Example(
"Link one template to one template",
"link_template_to_template 'Apache by HTTP' foo_template",
),
Example(
"Link many templates to many templates",
"link_template_to_template 'Apache by HTTP,HAProxy by Zabbix agent' foo_template,bar_template",
),
Example(
"Link all templates starting with 'Apache' to a template",
"link_template_to_template 'Apache*' foo_template",
),
Example(
"Link all templates containing 'HTTP' to a subset of templates",
"link_template_to_template '*HTTP*' 'Webserver-*'",
),
],
)
def link_template_to_template(
ctx: typer.Context,
source: str = ARG_TEMPLATE_NAMES_OR_IDS,
dest: str = ARG_TEMPLATE_NAMES_OR_IDS,
strict: bool = typer.Option(
False,
"--strict",
help="Fail if any templates aren't found. Should not be used in conjunction with wildcards.",
),
dryrun: bool = typer.Option(
False,
"--dryrun",
help="Do not actually link templates, just show what would be done.",
),
) -> None:
"""Link templates to templates.
[b]NOTE:[/] Destination templates are the ones that are ultimately modified. Source templates remain unchanged.
"""
from zabbix_cli.commands.results.template import LinkTemplateResult
# TODO: add existing link checking just like in `link_template_to_host` & `unlink_template_from_host`
# so we only print the ones that are actually linked
source_templates = parse_templates_arg(app, source, strict=strict)
dest_templates = parse_templates_arg(app, dest, strict=strict)
if not dryrun:
with app.state.console.status("Linking templates..."):
app.state.client.link_templates(source_templates, dest_templates)
render_result(
LinkTemplateResult.from_result(source_templates, dest_templates, "Link")
)
base_msg = f"{p('template', len(source_templates))} to {p('template', len(dest_templates))}"
if dryrun:
info(f"Would link {base_msg}.")
else:
success(f"Linked {base_msg}.")
@app.command("show_template", rich_help_panel=HELP_PANEL)
def show_template(
ctx: typer.Context,
template_name: str = typer.Argument(
help="Template name or ID. Names support wildcards.",
show_default=False,
),
) -> None:
"""Show a template."""
template = app.state.client.get_template(
template_name,
select_hosts=True,
select_templates=True,
select_parent_templates=True,
)
render_result(template)
@app.command("show_templates", rich_help_panel=HELP_PANEL)
def show_templates(
ctx: typer.Context,
templates: Optional[str] = typer.Argument(
None,
help="Template name(s) or ID(s). Comma-separated. Supports wildcards.",
show_default=False,
),
) -> None:
"""Show all templates.
Shows all templates by default. The template name can be a pattern containing wildcards.
Names and IDs cannot be mixed.
"""
from zabbix_cli.models import AggregateResult
template_names_or_ids = parse_list_arg(templates)
tpls = app.state.client.get_templates(
*template_names_or_ids,
select_hosts=True,
select_templates=True,
select_parent_templates=True,
)
render_result(AggregateResult(result=tpls))
@app.command(
"show_items",
rich_help_panel=HELP_PANEL,
examples=[
Example(
"Show items for a template",
"show_items 'Apache by HTTP'",
),
],
)
def show_items(
ctx: typer.Context,
template_name: str = typer.Argument(
help="Template name or ID. Supports wildcards.",
show_default=False,
),
) -> None:
"""Show a template's items."""
from zabbix_cli.models import AggregateResult
template = app.state.client.get_template(template_name)
items = app.state.client.get_items(templates=[template])
# NOTE: __title__ is ignored by Pydantic when used as a kwarg
# when instantiating a model. We either need to subclass AggregateResult
# or set it after instantiation.
# Ideally, we would rename the field to `table_title` and make it an actual field
# but that clashes with our existing `__<option>__` pattern for overriding
res = AggregateResult(result=items)
res.__title__ = template.host
render_result(res)
@app.command(
"unlink_template_from_host",
rich_help_panel=HELP_PANEL,
examples=[
Example(
"Unlink a template from a host",
"unlink_template_from_host 'Apache by HTTP' foo.example.com",
),
Example(
"Unlink many templates from many hosts",
"unlink_template_from_host 'Apache by HTTP,HAProxy by Zabbix agent' foo.example.com,bar.example.com",
),
Example(
"Unlink one template from all hosts",
"unlink_template_from_host 'Apache by HTTP' '*'",
),
Example(
"Unlink templates starting with 'Apache' from hosts starting with 'Web'",
"unlink_template_from_host 'Apache*' 'Web-*'",
),
Example(
"Unlink template from host without clearing items and triggers",
"unlink_template_from_host --no-clear 'Apache by HTTP' foo.example.com",
),
],
)
def unlink_template_from_host(
ctx: typer.Context,
template_names_or_ids: str = ARG_TEMPLATE_NAMES_OR_IDS,
hostnames_or_ids: str = ARG_HOSTNAMES_OR_IDS,
strict: bool = typer.Option(
False,
"--strict",
help="Fail if any hosts or templates aren't found. Should not be used in conjunction with wildcards.",
),
dryrun: bool = typer.Option(
False,
"--dryrun",
help="Preview changes.",
),
clear: bool = typer.Option(
True, "--clear/--no-clear", help="Unlink and clear templates."
),
) -> None:
"""Unlink templates from hosts.
Unlinks and clears by default. Use `--no-clear` to unlink without clearing.
"""
from zabbix_cli.commands.results.template import UnlinkTemplateFromHostResult
from zabbix_cli.models import AggregateResult
templates = parse_templates_arg(
app, template_names_or_ids, strict=strict, select_hosts=True
)
hosts = parse_hosts_arg(app, hostnames_or_ids, strict=strict)
action = "Unlink and clear" if clear else "Unlink"
if not dryrun:
with app.state.console.status("Unlinking templates..."):
app.state.client.unlink_templates_from_hosts(templates, hosts)
# Only show hosts with matching templates to unlink
result: list[UnlinkTemplateFromHostResult] = []
for host in hosts:
r = UnlinkTemplateFromHostResult.from_result(templates, host, action)
if not r.templates:
continue
result.append(r)
total_templates = len(set(chain.from_iterable((r.templates) for r in result)))
total_hosts = len(result)
render_result(AggregateResult(result=result))
base_msg = f"{p('template', total_templates)} from {p('host', total_hosts)}"
if dryrun:
info(f"Would {action.lower()} {base_msg}.")
else:
action_success = "Unlinked and cleared" if clear else "Unlinked"
success(f"{action_success} {base_msg}.")
@app.command(
"unlink_template_from_template",
rich_help_panel=HELP_PANEL,
examples=[
Example(
"Unlink one template from one template",
"unlink_template_from_template 'Apache by HTTP' foo_template",
),
Example(
"Unlink many templates from many templates",
"unlink_template_from_template 'Apache by HTTP,HAProxy by Zabbix agent' foo_template,bar_template",
),
Example(
"Unlink all templates starting with 'Apache' from a template",
"unlink_template_from_template 'Apache*' foo_template",
),
Example(
"Unlink all templates containing 'HTTP' from a subset of templates",
"unlink_template_from_template '*HTTP*' 'Web-*'",
),
Example(
"Unlink a template without clearing items and triggers",
"unlink_template_from_template --no-clear foo_template bar_template",
),
],
)
def unlink_template_from_template(
ctx: typer.Context,
source: str = ARG_TEMPLATE_NAMES_OR_IDS,
dest: str = ARG_TEMPLATE_NAMES_OR_IDS,
strict: bool = typer.Option(
False,
"--strict",
help="Fail if any templates aren't found. Should not be used in conjunction with wildcards.",
),
clear: bool = typer.Option(
True,
"--clear/--no-clear",
help="Unlink and clear templates.",
),
dryrun: bool = typer.Option(
False,
"--dryrun",
help="Preview changes.",
),
) -> None:
"""Unlink templates from templates.
Unlinks and clears by default. Use `--no-clear` to unlink without clearing.
[b]NOTE:[/] Destination templates are the ones that are ultimately modified. Source templates remain unchanged.
"""
from zabbix_cli.commands.results.template import LinkTemplateResult
source_templates = parse_templates_arg(app, source, strict=strict)
dest_templates = parse_templates_arg(app, dest, strict=strict)
if not dryrun:
with app.state.console.status("Unlinking templates..."):
app.state.client.unlink_templates(
source_templates, dest_templates, clear=clear
)
action = "Unlink and clear" if clear else "Unlink"
render_result(
LinkTemplateResult.from_result(
source_templates,
dest_templates,
action,
)
)
base_msg = f"{p('template', len(source_templates))} from {p('template', len(dest_templates))}"
if dryrun:
info(f"Would {action.lower()} {base_msg}.")
else:
action_success = "Unlinked and cleared" if clear else "Unlinked"
success(f"{action_success} {base_msg}.")
|