ducted 2.1__tar.gz → 2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {ducted-2.1 → ducted-2.2}/PKG-INFO +1 -1
  2. {ducted-2.1 → ducted-2.2}/README.md +13 -0
  3. {ducted-2.1 → ducted-2.2}/duct/outputs/nats.py +18 -4
  4. {ducted-2.1 → ducted-2.2}/duct/sources/nats.py +6 -0
  5. {ducted-2.1 → ducted-2.2}/duct/tests/test_outputs.py +25 -0
  6. {ducted-2.1 → ducted-2.2}/ducted.egg-info/PKG-INFO +1 -1
  7. {ducted-2.1 → ducted-2.2}/ducted.egg-info/top_level.txt +1 -0
  8. {ducted-2.1 → ducted-2.2}/pyproject.toml +1 -1
  9. {ducted-2.1 → ducted-2.2}/LICENSE +0 -0
  10. {ducted-2.1 → ducted-2.2}/docs/conf.py +0 -0
  11. {ducted-2.1 → ducted-2.2}/docs/update_modules.py +0 -0
  12. {ducted-2.1 → ducted-2.2}/duct/__init__.py +0 -0
  13. {ducted-2.1 → ducted-2.2}/duct/__main__.py +0 -0
  14. {ducted-2.1 → ducted-2.2}/duct/aggregators.py +0 -0
  15. {ducted-2.1 → ducted-2.2}/duct/configuration.py +0 -0
  16. {ducted-2.1 → ducted-2.2}/duct/ihateprotobuf/__init__.py +0 -0
  17. {ducted-2.1 → ducted-2.2}/duct/ihateprotobuf/proto_pb2.py +0 -0
  18. {ducted-2.1 → ducted-2.2}/duct/interfaces.py +0 -0
  19. {ducted-2.1 → ducted-2.2}/duct/logs/__init__.py +0 -0
  20. {ducted-2.1 → ducted-2.2}/duct/logs/follower.py +0 -0
  21. {ducted-2.1 → ducted-2.2}/duct/logs/parsers.py +0 -0
  22. {ducted-2.1 → ducted-2.2}/duct/objects.py +0 -0
  23. {ducted-2.1 → ducted-2.2}/duct/outputs/__init__.py +0 -0
  24. {ducted-2.1 → ducted-2.2}/duct/outputs/bosun.py +0 -0
  25. {ducted-2.1 → ducted-2.2}/duct/outputs/elasticsearch.py +0 -0
  26. {ducted-2.1 → ducted-2.2}/duct/outputs/graphite.py +0 -0
  27. {ducted-2.1 → ducted-2.2}/duct/outputs/influxdb3.py +0 -0
  28. {ducted-2.1 → ducted-2.2}/duct/outputs/logger.py +0 -0
  29. {ducted-2.1 → ducted-2.2}/duct/outputs/null.py +0 -0
  30. {ducted-2.1 → ducted-2.2}/duct/outputs/opentsdb.py +0 -0
  31. {ducted-2.1 → ducted-2.2}/duct/outputs/prometheus.py +0 -0
  32. {ducted-2.1 → ducted-2.2}/duct/outputs/riemann.py +0 -0
  33. {ducted-2.1 → ducted-2.2}/duct/protocol/__init__.py +0 -0
  34. {ducted-2.1 → ducted-2.2}/duct/protocol/elasticsearch.py +0 -0
  35. {ducted-2.1 → ducted-2.2}/duct/protocol/i2c.py +0 -0
  36. {ducted-2.1 → ducted-2.2}/duct/protocol/icmp.py +0 -0
  37. {ducted-2.1 → ducted-2.2}/duct/protocol/influxdb3.py +0 -0
  38. {ducted-2.1 → ducted-2.2}/duct/protocol/opentsdb.py +0 -0
  39. {ducted-2.1 → ducted-2.2}/duct/protocol/riemann.py +0 -0
  40. {ducted-2.1 → ducted-2.2}/duct/protocol/senml.py +0 -0
  41. {ducted-2.1 → ducted-2.2}/duct/protocol/sflow/__init__.py +0 -0
  42. {ducted-2.1 → ducted-2.2}/duct/protocol/sflow/protocol/__init__.py +0 -0
  43. {ducted-2.1 → ducted-2.2}/duct/protocol/sflow/protocol/counters.py +0 -0
  44. {ducted-2.1 → ducted-2.2}/duct/protocol/sflow/protocol/flows.py +0 -0
  45. {ducted-2.1 → ducted-2.2}/duct/protocol/sflow/protocol/protocol.py +0 -0
  46. {ducted-2.1 → ducted-2.2}/duct/protocol/sflow/protocol/utils.py +0 -0
  47. {ducted-2.1 → ducted-2.2}/duct/protocol/sflow/protocol/xdr.py +0 -0
  48. {ducted-2.1 → ducted-2.2}/duct/protocol/sflow/server.py +0 -0
  49. {ducted-2.1 → ducted-2.2}/duct/protocol/ssh.py +0 -0
  50. {ducted-2.1 → ducted-2.2}/duct/service.py +0 -0
  51. {ducted-2.1 → ducted-2.2}/duct/sources/__init__.py +0 -0
  52. {ducted-2.1 → ducted-2.2}/duct/sources/apache.py +0 -0
  53. {ducted-2.1 → ducted-2.2}/duct/sources/database/__init__.py +0 -0
  54. {ducted-2.1 → ducted-2.2}/duct/sources/database/elasticsearch.py +0 -0
  55. {ducted-2.1 → ducted-2.2}/duct/sources/database/memcache.py +0 -0
  56. {ducted-2.1 → ducted-2.2}/duct/sources/database/postgresql.py +0 -0
  57. {ducted-2.1 → ducted-2.2}/duct/sources/docker.py +0 -0
  58. {ducted-2.1 → ducted-2.2}/duct/sources/generator.py +0 -0
  59. {ducted-2.1 → ducted-2.2}/duct/sources/haproxy.py +0 -0
  60. {ducted-2.1 → ducted-2.2}/duct/sources/linux/__init__.py +0 -0
  61. {ducted-2.1 → ducted-2.2}/duct/sources/linux/basic.py +0 -0
  62. {ducted-2.1 → ducted-2.2}/duct/sources/linux/ipsec.py +0 -0
  63. {ducted-2.1 → ducted-2.2}/duct/sources/linux/process.py +0 -0
  64. {ducted-2.1 → ducted-2.2}/duct/sources/linux/sensors.py +0 -0
  65. {ducted-2.1 → ducted-2.2}/duct/sources/mail.py +0 -0
  66. {ducted-2.1 → ducted-2.2}/duct/sources/media/__init__.py +0 -0
  67. {ducted-2.1 → ducted-2.2}/duct/sources/media/libav.py +0 -0
  68. {ducted-2.1 → ducted-2.2}/duct/sources/munin.py +0 -0
  69. {ducted-2.1 → ducted-2.2}/duct/sources/network.py +0 -0
  70. {ducted-2.1 → ducted-2.2}/duct/sources/nginx.py +0 -0
  71. {ducted-2.1 → ducted-2.2}/duct/sources/postfix.py +0 -0
  72. {ducted-2.1 → ducted-2.2}/duct/sources/power/__init__.py +0 -0
  73. {ducted-2.1 → ducted-2.2}/duct/sources/power/nut.py +0 -0
  74. {ducted-2.1 → ducted-2.2}/duct/sources/power/raritan_px.py +0 -0
  75. {ducted-2.1 → ducted-2.2}/duct/sources/python/__init__.py +0 -0
  76. {ducted-2.1 → ducted-2.2}/duct/sources/python/uwsgi.py +0 -0
  77. {ducted-2.1 → ducted-2.2}/duct/sources/rabbitmq.py +0 -0
  78. {ducted-2.1 → ducted-2.2}/duct/sources/redis.py +0 -0
  79. {ducted-2.1 → ducted-2.2}/duct/sources/riak.py +0 -0
  80. {ducted-2.1 → ducted-2.2}/duct/sources/riemann.py +0 -0
  81. {ducted-2.1 → ducted-2.2}/duct/sources/sensors/__init__.py +0 -0
  82. {ducted-2.1 → ducted-2.2}/duct/sources/sensors/environment/__init__.py +0 -0
  83. {ducted-2.1 → ducted-2.2}/duct/sources/sensors/environment/ds18b20.py +0 -0
  84. {ducted-2.1 → ducted-2.2}/duct/sources/sensors/environment/mpl115.py +0 -0
  85. {ducted-2.1 → ducted-2.2}/duct/sources/sflow.py +0 -0
  86. {ducted-2.1 → ducted-2.2}/duct/sources/snmp.py +0 -0
  87. {ducted-2.1 → ducted-2.2}/duct/sources/unbound.py +0 -0
  88. {ducted-2.1 → ducted-2.2}/duct/tests/__init__.py +0 -0
  89. {ducted-2.1 → ducted-2.2}/duct/tests/globs.py +0 -0
  90. {ducted-2.1 → ducted-2.2}/duct/tests/helpers.py +0 -0
  91. {ducted-2.1 → ducted-2.2}/duct/tests/test_duct.py +0 -0
  92. {ducted-2.1 → ducted-2.2}/duct/tests/test_logs.py +0 -0
  93. {ducted-2.1 → ducted-2.2}/duct/tests/test_protocol.py +0 -0
  94. {ducted-2.1 → ducted-2.2}/duct/tests/test_service.py +0 -0
  95. {ducted-2.1 → ducted-2.2}/duct/tests/test_sflow.py +0 -0
  96. {ducted-2.1 → ducted-2.2}/duct/tests/test_sources.py +0 -0
  97. {ducted-2.1 → ducted-2.2}/duct/tests/test_ssh.py +0 -0
  98. {ducted-2.1 → ducted-2.2}/duct/tests/test_utils.py +0 -0
  99. {ducted-2.1 → ducted-2.2}/duct/utils.py +0 -0
  100. {ducted-2.1 → ducted-2.2}/ducted.egg-info/SOURCES.txt +0 -0
  101. {ducted-2.1 → ducted-2.2}/ducted.egg-info/dependency_links.txt +0 -0
  102. {ducted-2.1 → ducted-2.2}/ducted.egg-info/entry_points.txt +0 -0
  103. {ducted-2.1 → ducted-2.2}/ducted.egg-info/requires.txt +0 -0
  104. {ducted-2.1 → ducted-2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ducted
3
- Version: 2.1
3
+ Version: 2.2
4
4
  Summary: A monitoring agent and event processor
5
5
  Author-email: Colin Alston <colin@tamvera.com>
6
6
  License: MIT
@@ -1,5 +1,7 @@
1
1
  # Duct
2
2
 
3
+ I want to talk to you about ducts. Do your ducts seem old-fashioned? Out of date?
4
+
3
5
  [![CI](https://github.com/Tamvera/ducted/actions/workflows/ci.yml/badge.svg)](https://github.com/Tamvera/ducted/actions/workflows/ci.yml)
4
6
  [![Latest Docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://tamvera.github.io/ducted/)
5
7
 
@@ -31,6 +33,17 @@ Duct is an asyncio daemon that polls *sources* on configurable intervals and rou
31
33
 
32
34
  ## Installation
33
35
 
36
+ ### System wide on Linux, with a systemd script
37
+ ```bash
38
+ curl -o - https://raw.githubusercontent.com/Tamvera/ducted/refs/heads/master/scripts/install.sh | sh
39
+ ```
40
+
41
+ ### With docker
42
+ ```bash
43
+ docker run -i -v ./duct.yml:/duct/duct.yml ghcr.io/tamvera/ducted:latest
44
+ ```
45
+
46
+ ### With pip
34
47
  ```bash
35
48
  pip install ducted
36
49
  ```
@@ -27,6 +27,11 @@ class Nats(Output):
27
27
  :param prefix: Prefix added to topics (default: "")
28
28
  :type prefix: str
29
29
 
30
+ :param topic: Topic template, formatted with the event's fields, e.g.
31
+ "{hostname}.{service}" or "metrics.{attributes[region]}.{service}"
32
+ (default: "{hostname}.{service}")
33
+ :type topic: str
34
+
30
35
  :param format: Serialisation format - json, senml-json, senml-cbor (default: senml-json)
31
36
  :type format: str
32
37
 
@@ -59,6 +64,7 @@ class Nats(Output):
59
64
 
60
65
  self.servers = self.config.get("servers", ["nats://localhost:4222"])
61
66
  self.prefix = self.config.get("prefix", "")
67
+ self.topic = self.config.get("topic", "{hostname}.{service}")
62
68
  self.format = self.config.get("format", "senml-json")
63
69
  self.inter = float(self.config.get("interval", 1.0))
64
70
  self.use_jetstream = bool(self.config.get("jetstream", False))
@@ -117,13 +123,21 @@ class Nats(Output):
117
123
  def _transform_event(self, event: Event) -> bytes:
118
124
  return self.transformers[self.format](event)
119
125
 
126
+ def _build_topic(self, event: Event) -> str:
127
+ topic = self.topic.format_map(vars(event))
128
+ if self.prefix:
129
+ topic = f"{self.prefix}.{topic}"
130
+ return topic
131
+
120
132
  async def sendEvents(self, events: list[Event]):
121
133
  "Send batches of events to NATS or JetStream"
122
134
  for ev in events:
123
- if self.prefix:
124
- topic = f"{self.prefix}.{ev.hostname}.{ev.service}"
125
- else:
126
- topic = f"{ev.hostname}.{ev.service}"
135
+ try:
136
+ topic = self._build_topic(ev)
137
+ except (KeyError, IndexError, AttributeError, TypeError):
138
+ log.exception("Failed to build topic from %r for event %s",
139
+ self.topic, ev.service)
140
+ continue
127
141
  payload = self._transform_event(ev)
128
142
  try:
129
143
  if self.use_jetstream:
@@ -41,6 +41,9 @@ class Nats(Source):
41
41
  :param durable: Durable consumer name for JetStream subscriptions (default: "ducted")
42
42
  :type durable: str
43
43
 
44
+ :param append_topic: Append the topic to the service name for events
45
+ :type append_topic: bool
46
+
44
47
  :param credentials_file: Path to NATS credentials (.creds) file
45
48
  :type credentials_file: str
46
49
 
@@ -66,6 +69,7 @@ class Nats(Source):
66
69
  self.format = self.config.get("format", "senml-json")
67
70
  self.use_jetstream = bool(self.config.get("jetstream", False))
68
71
  self.durable = self.config.get("durable", "ducted")
72
+ self.append_topic = self.config.get("append_topic", False)
69
73
 
70
74
  self.credentials_file = self.config.get("credentials_file")
71
75
  self.nkey_seed_file = self.config.get("nkey_seed_file")
@@ -147,6 +151,8 @@ class Nats(Source):
147
151
  async def _get_event(self, message):
148
152
  try:
149
153
  ev = self.transformers[self.format](message.data)
154
+ if self.append_topic:
155
+ ev.service = f"{message.subject}.{ev.service}"
150
156
  self.queueBack([ev])
151
157
  except Exception:
152
158
  log.exception(
@@ -255,6 +255,31 @@ class TestNATSOutput:
255
255
  assert decoded.tags == ["prod", "eu"]
256
256
  assert decoded.attributes["chicken"] == "little"
257
257
 
258
+ @pytest.mark.asyncio
259
+ async def test_nats_output_topic_template(self, service):
260
+ ev = Event('ok', 'sky', 'Chicken alert', 2.0, 30.0,
261
+ tags=['prod'], attributes={"region": "eu"},
262
+ hostname='testhost')
263
+ nats_mod.nats = FakeNATS()
264
+ out = nats_mod.Nats({"topic": "{attributes[region]}.{state}.{service}",
265
+ "prefix": "duct"}, service)
266
+ await out.createClient()
267
+ await out.eventsReceived([ev])
268
+ await out._tick()
269
+
270
+ topic, _ = out.nc.messages[0]
271
+ assert topic == "duct.eu.ok.sky"
272
+
273
+ @pytest.mark.asyncio
274
+ async def test_nats_output_topic_template_missing_key(self, service, event):
275
+ nats_mod.nats = FakeNATS()
276
+ out = nats_mod.Nats({"topic": "{attributes[nope]}.{service}"}, service)
277
+ await out.createClient()
278
+ await out.eventsReceived([event])
279
+ await out._tick()
280
+
281
+ assert out.nc.messages == []
282
+
258
283
  @pytest.mark.asyncio
259
284
  async def test_nats_output_cbor(self, service, event):
260
285
  nats_mod.nats = FakeNATS()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ducted
3
- Version: 2.1
3
+ Version: 2.2
4
4
  Summary: A monitoring agent and event processor
5
5
  Author-email: Colin Alston <colin@tamvera.com>
6
6
  License: MIT
@@ -2,3 +2,4 @@ dist
2
2
  docker
3
3
  docs
4
4
  duct
5
+ scripts
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ducted"
7
- version = "2.1"
7
+ version = "2.2"
8
8
  description = "A monitoring agent and event processor"
9
9
  license = { text = "MIT" }
10
10
  authors = [{ name = "Colin Alston", email = "colin@tamvera.com" }]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes