From eff9c22b3cae6d397bbb29b944e0aeeb391c4f1f Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 27 Feb 2021 14:27:22 +0100 Subject: [PATCH 01/90] feat(auth): add HMAC authentication as config option (#248) --- alertaclient/api.py | 4 ++-- alertaclient/cli.py | 1 + alertaclient/config.py | 1 + examples/alerta.conf | 5 +++++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index ea0f9e6..3bd8ada 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -32,8 +32,8 @@ class Client: DEFAULT_ENDPOINT = 'http://localhost:8080' - def __init__(self, endpoint=None, key=None, secret=None, token=None, username=None, password=None, timeout=5.0, ssl_verify=True, - ssl_cert=None, ssl_key=None, headers=None, debug=False): + def __init__(self, endpoint=None, key=None, secret=None, token=None, username=None, password=None, timeout=5.0, + ssl_verify=True, ssl_cert=None, ssl_key=None, headers=None, debug=False): self.endpoint = endpoint or os.environ.get('ALERTA_ENDPOINT', self.DEFAULT_ENDPOINT) if debug: diff --git a/alertaclient/cli.py b/alertaclient/cli.py index 663f2e7..9d03530 100644 --- a/alertaclient/cli.py +++ b/alertaclient/cli.py @@ -63,6 +63,7 @@ def cli(ctx, config_file, profile, endpoint_url, output, color, debug): ctx.obj['client'] = Client( endpoint=endpoint, key=config.options['key'], + secret=config.options['secret'], token=get_token(endpoint), username=config.options.get('username', None), password=config.options.get('password', None), diff --git a/alertaclient/config.py b/alertaclient/config.py index d88f5d1..9c46f38 100644 --- a/alertaclient/config.py +++ b/alertaclient/config.py @@ -11,6 +11,7 @@ 'profile': None, 'endpoint': 'http://localhost:8080', 'key': '', + 'secret': None, 'client_id': None, 'username': None, 'password': None, diff --git a/examples/alerta.conf b/examples/alerta.conf index 30257fa..2f75a0b 100644 --- a/examples/alerta.conf +++ b/examples/alerta.conf @@ -19,3 +19,8 @@ key = demo-key [profile development] endpoint = http://localhost:8080 key = demo-key + +[profile hmac-auth] +endpoint = http://localhost:9080/api +key = access-key +secret = secret-key From 7dbb6de9fb8b5e65ce592e33aad506ac65470034 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 27 Feb 2021 22:49:06 +0100 Subject: [PATCH 02/90] Bump version 8.3.3 -> 8.4.0 --- VERSION | 2 +- alertaclient/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 2bf50aa..a2f28f4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.3.0 +8.4.0 diff --git a/alertaclient/version.py b/alertaclient/version.py index e93ed63..b0193b9 100644 --- a/alertaclient/version.py +++ b/alertaclient/version.py @@ -1 +1 @@ -__version__ = '8.3.0' +__version__ = '8.4.0' From 629bad665c471fe6c9055c8de10dbfea83d7d945 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Wed, 14 Apr 2021 23:58:39 +0200 Subject: [PATCH 03/90] Add alert origin blackout support (#252) * Add alert origin blackout support * Add some tests for blackout origin --- Makefile | 1 + alertaclient/api.py | 5 ++- alertaclient/commands/cmd_blackout.py | 4 ++- alertaclient/commands/cmd_blackouts.py | 6 ++-- alertaclient/models/blackout.py | 7 +++++ docker-compose.ci.yaml | 2 +- tests/integration/test_blackouts.py | 10 ++++-- tests/unit/test_blackouts.py | 43 ++++++++++++++------------ 8 files changed, 50 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 07b8395..9c71260 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,7 @@ test.unit: $(TOX) $(PYTEST) ## test.integration - Run integration tests. test.integration: docker-compose -f docker-compose.ci.yaml rm --stop --force + docker-compose -f docker-compose.ci.yaml pull docker-compose -f docker-compose.ci.yaml build sut docker-compose -f docker-compose.ci.yaml up --exit-code-from sut docker-compose -f docker-compose.ci.yaml rm --stop --force diff --git a/alertaclient/api.py b/alertaclient/api.py index 3bd8ada..092dfbc 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -168,7 +168,8 @@ def delete_alert_note(self, id, note_id): return self.http.delete('/alert/{}/note/{}'.format(id, note_id)) # Blackouts - def create_blackout(self, environment, service=None, resource=None, event=None, group=None, tags=None, customer=None, start=None, duration=None, text=None): + def create_blackout(self, environment, service=None, resource=None, event=None, group=None, tags=None, + origin=None, customer=None, start=None, duration=None, text=None): data = { 'environment': environment, 'service': service or list(), @@ -176,6 +177,7 @@ def create_blackout(self, environment, service=None, resource=None, event=None, 'event': event, 'group': group, 'tags': tags or list(), + 'origin': origin, 'customer': customer, 'startTime': start, 'duration': duration, @@ -200,6 +202,7 @@ def update_blackout(self, id, **kwargs): 'event': kwargs.get('event'), 'group': kwargs.get('group'), 'tags': kwargs.get('tags'), + 'origin': kwargs.get('origin'), 'startTime': kwargs.get('startTime'), 'endTime': kwargs.get('endTime'), 'text': kwargs.get('text'), diff --git a/alertaclient/commands/cmd_blackout.py b/alertaclient/commands/cmd_blackout.py index c083b71..c51ed06 100644 --- a/alertaclient/commands/cmd_blackout.py +++ b/alertaclient/commands/cmd_blackout.py @@ -10,13 +10,14 @@ @click.option('--event', '-e', metavar='EVENT', help='Event name') @click.option('--group', '-g', metavar='GROUP', help='Group event by type eg. OS, Performance') @click.option('--tag', '-T', 'tags', multiple=True, metavar='TAG', help='List of tags eg. London, os:linux, AWS/EC2') +@click.option('--origin', '-O', metavar='ORIGIN', help='Origin of alert in form app/host') @click.option('--customer', metavar='STRING', help='Customer (Admin only)') @click.option('--start', metavar='DATETIME', help='Start time in ISO8601 eg. 2018-02-01T12:00:00.000Z') @click.option('--duration', metavar='SECONDS', type=int, help='Blackout period in seconds') @click.option('--text', help='Reason for blackout') @click.option('--delete', '-D', help='Delete blackout using ID') @click.pass_obj -def cli(obj, environment, service, resource, event, group, tags, customer, start, duration, text, delete): +def cli(obj, environment, service, resource, event, group, tags, origin, customer, start, duration, text, delete): """Suppress alerts for specified duration based on alert attributes.""" client = obj['client'] if delete: @@ -32,6 +33,7 @@ def cli(obj, environment, service, resource, event, group, tags, customer, start event=event, group=group, tags=tags, + origin=origin, customer=customer, start=start, duration=duration, diff --git a/alertaclient/commands/cmd_blackouts.py b/alertaclient/commands/cmd_blackouts.py index 70c2af2..583dd33 100644 --- a/alertaclient/commands/cmd_blackouts.py +++ b/alertaclient/commands/cmd_blackouts.py @@ -18,9 +18,9 @@ def cli(obj, purge): timezone = obj['timezone'] headers = { 'id': 'ID', 'priority': 'P', 'environment': 'ENVIRONMENT', 'service': 'SERVICE', 'resource': 'RESOURCE', - 'event': 'EVENT', 'group': 'GROUP', 'tags': 'TAGS', 'customer': 'CUSTOMER', 'startTime': 'START', 'endTime': 'END', - 'duration': 'DURATION', 'user': 'USER', 'createTime': 'CREATED', 'text': 'COMMENT', - 'status': 'STATUS', 'remaining': 'REMAINING' + 'event': 'EVENT', 'group': 'GROUP', 'tags': 'TAGS', 'origin': 'ORIGIN', 'customer': 'CUSTOMER', + 'startTime': 'START', 'endTime': 'END', 'duration': 'DURATION', 'user': 'USER', + 'createTime': 'CREATED', 'text': 'COMMENT', 'status': 'STATUS', 'remaining': 'REMAINING' } blackouts = client.get_blackouts() click.echo(tabulate([b.tabular(timezone) for b in blackouts], headers=headers, tablefmt=obj['output'])) diff --git a/alertaclient/models/blackout.py b/alertaclient/models/blackout.py index d1b3716..8323dea 100644 --- a/alertaclient/models/blackout.py +++ b/alertaclient/models/blackout.py @@ -24,6 +24,7 @@ def __init__(self, environment, **kwargs): self.event = kwargs.get('event', None) self.group = kwargs.get('group', None) self.tags = kwargs.get('tags', None) or list() + self.origin = kwargs.get('origin', None) self.customer = kwargs.get('customer', None) self.start_time = start_time self.end_time = end_time @@ -47,6 +48,8 @@ def __init__(self, environment, **kwargs): self.priority = 6 elif self.tags: self.priority = 7 + if self.origin: + self.priority = 8 now = datetime.utcnow() if self.start_time <= now and self.end_time > now: @@ -71,6 +74,8 @@ def __repr__(self): more += 'group=%r, ' % self.group if self.tags: more += 'tags=%r, ' % self.tags + if self.origin: + more += 'origin=%r, ' % self.origin if self.customer: more += 'customer=%r, ' % self.customer @@ -100,6 +105,7 @@ def parse(cls, json): event=json.get('event', None), group=json.get('group', None), tags=json.get('tags', list()), + origin=json.get('origin', None), customer=json.get('customer', None), start_time=DateTime.parse(json.get('startTime')), end_time=DateTime.parse(json.get('endTime')), @@ -119,6 +125,7 @@ def tabular(self, timezone=None): 'event': self.event, 'group': self.group, 'tags': ','.join(self.tags), + 'origin': self.origin, 'customer': self.customer, 'startTime': DateTime.localtime(self.start_time, timezone), 'endTime': DateTime.localtime(self.end_time, timezone), diff --git a/docker-compose.ci.yaml b/docker-compose.ci.yaml index 077fd0f..f0ddd12 100644 --- a/docker-compose.ci.yaml +++ b/docker-compose.ci.yaml @@ -2,7 +2,7 @@ version: '3.7' services: api: - image: docker.pkg.github.com/alerta/alerta/alerta-api:latest + image: ghcr.io/alerta/alerta-api:latest ports: - "8080:8080" depends_on: diff --git a/tests/integration/test_blackouts.py b/tests/integration/test_blackouts.py index 5414b94..e6d2264 100644 --- a/tests/integration/test_blackouts.py +++ b/tests/integration/test_blackouts.py @@ -10,7 +10,8 @@ def setUp(self): def test_blackout(self): blackout = self.client.create_blackout( - environment='Production', service=['Web', 'App'], resource='web01', event='node_down', group='Network', tags=['london', 'linux'] + environment='Production', service=['Web', 'App'], resource='web01', event='node_down', group='Network', + tags=['london', 'linux'], origin='foo/bar' ) blackout_id = blackout.id @@ -18,14 +19,17 @@ def test_blackout(self): self.assertEqual(blackout.service, ['Web', 'App']) self.assertIn('london', blackout.tags) self.assertIn('linux', blackout.tags) + self.assertEqual(blackout.origin, 'foo/bar') - blackout = self.client.update_blackout(blackout_id, environment='Development', group='Network', text='updated blackout') + blackout = self.client.update_blackout(blackout_id, environment='Development', group='Network', + origin='foo/quux', text='updated blackout') self.assertEqual(blackout.environment, 'Development') self.assertEqual(blackout.group, 'Network') + self.assertEqual(blackout.origin, 'foo/quux') self.assertEqual(blackout.text, 'updated blackout') blackout = self.client.create_blackout( - environment='Production', service=['Core'], group='Network' + environment='Production', service=['Core'], group='Network', origin='foo/baz' ) blackouts = self.client.get_blackouts() diff --git a/tests/unit/test_blackouts.py b/tests/unit/test_blackouts.py index 2a841b3..4962ef8 100644 --- a/tests/unit/test_blackouts.py +++ b/tests/unit/test_blackouts.py @@ -13,31 +13,33 @@ def setUp(self): self.blackout = """ { "blackout": { - "createTime": "2018-08-26T20:45:04.622Z", + "createTime": "2021-04-14T20:36:06.453Z", "customer": null, "duration": 3600, - "endTime": "2018-08-26T21:45:04.622Z", + "endTime": "2021-04-14T21:36:06.453Z", "environment": "Production", - "event": null, - "group": null, - "href": "http://localhost:8080/blackout/e18a4be8-60d7-4ce2-9b3d-f18d814f7b85", - "id": "e18a4be8-60d7-4ce2-9b3d-f18d814f7b85", - "priority": 3, - "remaining": 3599, - "resource": null, + "event": "node_down", + "group": "Network", + "href": "http://local.alerta.io:8080/blackout/5ed223a3-27dc-4c4c-97d1-504f107d8a1a", + "id": "5ed223a3-27dc-4c4c-97d1-504f107d8a1a", + "origin": "foo/xyz", + "priority": 8, + "remaining": 3600, + "resource": "web01", "service": [ - "Network" + "Web", + "App" ], - "startTime": "2018-08-26T20:45:04.622Z", + "startTime": "2021-04-14T20:36:06.453Z", "status": "active", "tags": [ - "london", - "linux" + "london", + "linux" ], "text": "Network outage in Bracknell", - "user": "admin@alerta.io" + "user": "admin@alerta.dev" }, - "id": "e18a4be8-60d7-4ce2-9b3d-f18d814f7b85", + "id": "5ed223a3-27dc-4c4c-97d1-504f107d8a1a", "status": "ok" } """ @@ -45,10 +47,13 @@ def setUp(self): @requests_mock.mock() def test_blackout(self, m): m.post('http://localhost:8080/blackout', text=self.blackout) - alert = self.client.create_blackout(environment='Production', service=[ - 'Web', 'App'], resource='web01', event='node_down', group='Network', tags=['london', 'linux']) + alert = self.client.create_blackout(environment='Production', service=['Web', 'App'], resource='web01', + event='node_down', group='Network', tags=['london', 'linux'], + origin='foo/xyz', text='Network outage in Bracknell') self.assertEqual(alert.environment, 'Production') - self.assertEqual(alert.service, ['Network']) + self.assertEqual(alert.service, ['Web', 'App']) + self.assertEqual(alert.group, 'Network') self.assertIn('london', alert.tags) + self.assertEqual(alert.origin, 'foo/xyz') self.assertEqual(alert.text, 'Network outage in Bracknell') - self.assertEqual(alert.user, 'admin@alerta.io') + self.assertEqual(alert.user, 'admin@alerta.dev') From eda07569d26d94a5e5db47b3eeac8774a447116c Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 17 Apr 2021 23:27:52 +0200 Subject: [PATCH 04/90] feat: add option to show decoded auth token claims (#254) --- alertaclient/commands/cmd_token.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/alertaclient/commands/cmd_token.py b/alertaclient/commands/cmd_token.py index a99e651..a66cf55 100644 --- a/alertaclient/commands/cmd_token.py +++ b/alertaclient/commands/cmd_token.py @@ -1,11 +1,21 @@ import click +from alertaclient.auth.token import Jwt from alertaclient.auth.utils import get_token @click.command('token', short_help='Display current auth token') +@click.option('--decode', '-D', is_flag=True, help='Decode auth token.') @click.pass_obj -def cli(obj): - """Display the auth token for logged in user.""" +def cli(obj, decode): + """Display the auth token for logged in user, with option to decode it.""" client = obj['client'] - click.echo(get_token(client.endpoint)) + token = get_token(client.endpoint) + if decode: + jwt = Jwt() + for k, v in jwt.parse(token).items(): + if isinstance(v, list): + v = ', '.join(v) + click.echo('{:20}: {}'.format(k, v)) + else: + click.echo(token) From 78562c7b29f41248100b9e8f2f8c9ea6e5d3cae9 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 17 Apr 2021 23:42:08 +0200 Subject: [PATCH 05/90] feat: show user details (#255) --- alertaclient/api.py | 2 +- alertaclient/commands/cmd_user.py | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index 092dfbc..c0a7540 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -354,7 +354,7 @@ def create_user(self, name, email, password, status, roles=None, attributes=None r = self.http.post('/user', data) return User.parse(r['user']) - def get_user(self): + def get_user(self, id): return User.parse(self.http.get('/user/%s' % id)['user']) def get_user_groups(self, id): diff --git a/alertaclient/commands/cmd_user.py b/alertaclient/commands/cmd_user.py index 889a931..96a862d 100644 --- a/alertaclient/commands/cmd_user.py +++ b/alertaclient/commands/cmd_user.py @@ -1,6 +1,7 @@ import sys import click +from tabulate import tabulate class CommandWithOptionalPassword(click.Command): @@ -30,23 +31,29 @@ def parse_args(self, ctx, args): @click.option('--delete', '-D', metavar='UUID', help='Delete user using ID') @click.pass_obj def cli(obj, id, name, email, password, status, roles, text, email_verified, delete): - """Create user or update user details, including password reset.""" + """Create user, show or update user details, including password reset.""" client = obj['client'] if delete: client.delete_user(delete) elif id: if not any([name, email, password, status, roles, text, (email_verified is not None)]): - click.echo('Nothing to update.') - sys.exit(1) - try: - user = client.update_user( - id, name=name, email=email, password=password, status=status, - roles=roles, attributes=None, text=text, email_verified=email_verified - ) - except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) - sys.exit(1) - click.echo(user.id) + user = client.get_user(id) + timezone = obj['timezone'] + headers = {'id': 'ID', 'name': 'USER', 'email': 'EMAIL', 'roles': 'ROLES', 'status': 'STATUS', + 'text': 'TEXT', + 'createTime': 'CREATED', 'updateTime': 'LAST UPDATED', 'lastLogin': 'LAST LOGIN', + 'email_verified': 'VERIFIED'} + click.echo(tabulate([user.tabular(timezone)], headers=headers, tablefmt=obj['output'])) + else: + try: + user = client.update_user( + id, name=name, email=email, password=password, status=status, + roles=roles, attributes=None, text=text, email_verified=email_verified + ) + except Exception as e: + click.echo('ERROR: {}'.format(e), err=True) + sys.exit(1) + click.echo(user.id) else: if not email: raise click.UsageError('Need "--email" to create user.') From 273d8620182b34acf15a07dcf27f698a186ae9b6 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 00:11:37 +0200 Subject: [PATCH 06/90] feat: add alerts command for list alert attributes (#256) --- alertaclient/commands/cmd_alerts.py | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 alertaclient/commands/cmd_alerts.py diff --git a/alertaclient/commands/cmd_alerts.py b/alertaclient/commands/cmd_alerts.py new file mode 100644 index 0000000..dc5c990 --- /dev/null +++ b/alertaclient/commands/cmd_alerts.py @@ -0,0 +1,47 @@ +import json + +import click +from tabulate import tabulate + + +@click.command('alerts', short_help='List alert environments, services, groups and tags') +@click.option('--environments', '-E', is_flag=True, help='List alert environments.') +@click.option('--services', '-S', is_flag=True, help='List alert services.') +@click.option('--groups', '-g', is_flag=True, help='List alert groups.') +@click.option('--tags', '-T', is_flag=True, help='List alert tags.') +@click.pass_obj +def cli(obj, environments, services, groups, tags): + """List alert environments, services, groups and tags.""" + + client = obj['client'] + + if environments: + if obj['output'] == 'json': + r = client.http.get('/environments') + click.echo(json.dumps(r['environments'], sort_keys=True, indent=4, ensure_ascii=False)) + else: + headers = {'environment': 'ENVIRONMENT', 'count': 'COUNT', 'severityCounts': 'SEVERITY COUNTS', 'statusCounts': 'STATUS COUNTS'} + click.echo(tabulate(client.get_environments(), headers=headers, tablefmt=obj['output'])) + elif services: + if obj['output'] == 'json': + r = client.http.get('/services') + click.echo(json.dumps(r['services'], sort_keys=True, indent=4, ensure_ascii=False)) + else: + headers = {'environment': 'ENVIRONMENT', 'service': 'SERVICE', 'count': 'COUNT', 'severityCounts': 'SEVERITY COUNTS', 'statusCounts': 'STATUS COUNTS'} + click.echo(tabulate(client.get_services(), headers=headers, tablefmt=obj['output'])) + elif groups: + if obj['output'] == 'json': + r = client.http.get('/alerts/groups') + click.echo(json.dumps(r['groups'], sort_keys=True, indent=4, ensure_ascii=False)) + else: + headers = {'environment': 'ENVIRONMENT', 'group': 'GROUP', 'count': 'COUNT', 'severityCounts': 'SEVERITY COUNTS', 'statusCounts': 'STATUS COUNTS'} + click.echo(tabulate(client.get_groups(), headers=headers, tablefmt=obj['output'])) + elif tags: + if obj['output'] == 'json': + r = client.http.get('/alerts/tags') + click.echo(json.dumps(r['tags'], sort_keys=True, indent=4, ensure_ascii=False)) + else: + headers = {'environment': 'ENVIRONMENT', 'tag': 'TAG', 'count': 'COUNT', 'severityCounts': 'SEVERITY COUNTS', 'statusCounts': 'STATUS COUNTS'} + click.echo(tabulate(client.get_tags(), headers=headers, tablefmt=obj['output'])) + else: + raise click.UsageError('Must choose an alert attribute to list.') From 1c0bda698524d47dff67ae2165a41749d9f32ab4 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 09:05:57 +0200 Subject: [PATCH 07/90] fix: add scopes cmd and minor fixes (#257) --- alertaclient/commands/cmd_alerts.py | 2 +- alertaclient/commands/cmd_blackout.py | 2 +- alertaclient/commands/cmd_scopes.py | 18 ++++++++++++++++++ alertaclient/commands/cmd_user.py | 2 +- alertaclient/models/enums.py | 8 +++++++- 5 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 alertaclient/commands/cmd_scopes.py diff --git a/alertaclient/commands/cmd_alerts.py b/alertaclient/commands/cmd_alerts.py index dc5c990..7041b2b 100644 --- a/alertaclient/commands/cmd_alerts.py +++ b/alertaclient/commands/cmd_alerts.py @@ -4,7 +4,7 @@ from tabulate import tabulate -@click.command('alerts', short_help='List alert environments, services, groups and tags') +@click.command('alerts', short_help='List environments, services, groups and tags') @click.option('--environments', '-E', is_flag=True, help='List alert environments.') @click.option('--services', '-S', is_flag=True, help='List alert services.') @click.option('--groups', '-g', is_flag=True, help='List alert groups.') diff --git a/alertaclient/commands/cmd_blackout.py b/alertaclient/commands/cmd_blackout.py index c51ed06..2810efc 100644 --- a/alertaclient/commands/cmd_blackout.py +++ b/alertaclient/commands/cmd_blackout.py @@ -15,7 +15,7 @@ @click.option('--start', metavar='DATETIME', help='Start time in ISO8601 eg. 2018-02-01T12:00:00.000Z') @click.option('--duration', metavar='SECONDS', type=int, help='Blackout period in seconds') @click.option('--text', help='Reason for blackout') -@click.option('--delete', '-D', help='Delete blackout using ID') +@click.option('--delete', '-D', metavar='ID', help='Delete blackout using ID') @click.pass_obj def cli(obj, environment, service, resource, event, group, tags, origin, customer, start, duration, text, delete): """Suppress alerts for specified duration based on alert attributes.""" diff --git a/alertaclient/commands/cmd_scopes.py b/alertaclient/commands/cmd_scopes.py new file mode 100644 index 0000000..280d3f3 --- /dev/null +++ b/alertaclient/commands/cmd_scopes.py @@ -0,0 +1,18 @@ +import json + +import click +from tabulate import tabulate + + +@click.command('scopes', short_help='List scopes') +@click.pass_obj +def cli(obj): + """List scopes.""" + client = obj['client'] + + if obj['output'] == 'json': + r = client.http.get('/scopes') + click.echo(json.dumps(r['scopes'], sort_keys=True, indent=4, ensure_ascii=False)) + else: + headers = {'scope': 'SCOPE'} + click.echo(tabulate([s.tabular() for s in client.get_scopes()], headers=headers, tablefmt=obj['output'])) diff --git a/alertaclient/commands/cmd_user.py b/alertaclient/commands/cmd_user.py index 96a862d..34c391c 100644 --- a/alertaclient/commands/cmd_user.py +++ b/alertaclient/commands/cmd_user.py @@ -28,7 +28,7 @@ def parse_args(self, ctx, args): @click.option('--role', 'roles', multiple=True, help='List of roles') @click.option('--text', help='Description of user') @click.option('--email-verified/--email-not-verified', default=None, help='Email address verified flag') -@click.option('--delete', '-D', metavar='UUID', help='Delete user using ID') +@click.option('--delete', '-D', metavar='ID', help='Delete user using ID') @click.pass_obj def cli(obj, id, name, email, password, status, roles, text, email_verified, delete): """Create user, show or update user details, including password reset.""" diff --git a/alertaclient/models/enums.py b/alertaclient/models/enums.py index bb7b596..46eb45c 100644 --- a/alertaclient/models/enums.py +++ b/alertaclient/models/enums.py @@ -1,13 +1,14 @@ from enum import Enum -class Scope(str, Enum): +class Scope(str): read = 'read' write = 'write' admin = 'admin' read_alerts = 'read:alerts' write_alerts = 'write:alerts' + delete_alerts = 'delete:alerts' admin_alerts = 'admin:alerts' read_blackouts = 'read:blackouts' write_blackouts = 'write:blackouts' @@ -58,6 +59,11 @@ def from_str(action: str, resource: str = None): else: return Scope(action) + def tabular(self): + return { + 'scope': self + } + ADMIN_SCOPES = [Scope.admin, Scope.read, Scope.write] From fa8c43b0933f754f7170d4326f96452c574f0fcd Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 09:31:45 +0200 Subject: [PATCH 08/90] feat: add option to list groups to user cmd (#258) --- alertaclient/commands/cmd_user.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/alertaclient/commands/cmd_user.py b/alertaclient/commands/cmd_user.py index 34c391c..fa34fff 100644 --- a/alertaclient/commands/cmd_user.py +++ b/alertaclient/commands/cmd_user.py @@ -28,12 +28,17 @@ def parse_args(self, ctx, args): @click.option('--role', 'roles', multiple=True, help='List of roles') @click.option('--text', help='Description of user') @click.option('--email-verified/--email-not-verified', default=None, help='Email address verified flag') +@click.option('--groups', '-g', is_flag=True, help='Get list of user groups') @click.option('--delete', '-D', metavar='ID', help='Delete user using ID') @click.pass_obj -def cli(obj, id, name, email, password, status, roles, text, email_verified, delete): - """Create user, show or update user details, including password reset.""" +def cli(obj, id, name, email, password, status, roles, text, email_verified, groups, delete): + """Create user, show or update user details, including password reset, list user groups and delete user.""" client = obj['client'] - if delete: + if groups: + user_groups = client.get_user_groups(id) + headers = {'id': 'ID', 'name': 'USER', 'text': 'TEXT', 'count': 'COUNT'} + click.echo(tabulate([ug.tabular() for ug in user_groups], headers=headers, tablefmt=obj['output'])) + elif delete: client.delete_user(delete) elif id: if not any([name, email, password, status, roles, text, (email_verified is not None)]): From 21d985a1e82ade589746f87d0610fd678f0a5e4e Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 09:49:35 +0200 Subject: [PATCH 09/90] feat: add option to list users to group cmd (#259) --- alertaclient/commands/cmd_group.py | 14 ++++++++++++-- alertaclient/commands/cmd_user.py | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/alertaclient/commands/cmd_group.py b/alertaclient/commands/cmd_group.py index e2e3396..ebe6d0d 100644 --- a/alertaclient/commands/cmd_group.py +++ b/alertaclient/commands/cmd_group.py @@ -1,17 +1,27 @@ import sys import click +from tabulate import tabulate @click.command('group', short_help='Create user group') +@click.option('--id', '-i', metavar='UUID', help='Group ID') @click.option('--name', help='Group name') @click.option('--text', help='Description of user group') +@click.option('--users', '-U', is_flag=True, metavar='ID', help='Get list of group users') @click.option('--delete', '-D', metavar='ID', help='Delete user group using ID') @click.pass_obj -def cli(obj, name, text, delete): +def cli(obj, id, name, text, users, delete): """Create or delete a user group.""" client = obj['client'] - if delete: + if users: + group_users = client.get_group_users(id) + timezone = obj['timezone'] + headers = {'id': 'ID', 'name': 'USER', 'email': 'EMAIL', 'roles': 'ROLES', 'status': 'STATUS', + 'text': 'TEXT', 'createTime': 'CREATED', 'updateTime': 'LAST UPDATED', + 'lastLogin': 'LAST LOGIN', 'email_verified': 'VERIFIED'} + click.echo(tabulate([gu.tabular(timezone) for gu in group_users], headers=headers, tablefmt=obj['output'])) + elif delete: client.delete_group(delete) else: try: diff --git a/alertaclient/commands/cmd_user.py b/alertaclient/commands/cmd_user.py index fa34fff..7016f07 100644 --- a/alertaclient/commands/cmd_user.py +++ b/alertaclient/commands/cmd_user.py @@ -28,7 +28,7 @@ def parse_args(self, ctx, args): @click.option('--role', 'roles', multiple=True, help='List of roles') @click.option('--text', help='Description of user') @click.option('--email-verified/--email-not-verified', default=None, help='Email address verified flag') -@click.option('--groups', '-g', is_flag=True, help='Get list of user groups') +@click.option('--groups', '-G', is_flag=True, help='Get list of user groups') @click.option('--delete', '-D', metavar='ID', help='Delete user using ID') @click.pass_obj def cli(obj, id, name, email, password, status, roles, text, email_verified, groups, delete): From d6483cb487d07edfbcb0ed64eb87b598de9aa043 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 10:14:17 +0200 Subject: [PATCH 10/90] feat: add and remove users to/from groups (#260) --- alertaclient/commands/cmd_group.py | 11 ++++++++--- alertaclient/commands/cmd_user.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/alertaclient/commands/cmd_group.py b/alertaclient/commands/cmd_group.py index ebe6d0d..ee4c693 100644 --- a/alertaclient/commands/cmd_group.py +++ b/alertaclient/commands/cmd_group.py @@ -8,13 +8,18 @@ @click.option('--id', '-i', metavar='UUID', help='Group ID') @click.option('--name', help='Group name') @click.option('--text', help='Description of user group') -@click.option('--users', '-U', is_flag=True, metavar='ID', help='Get list of group users') +@click.option('--user', '-U', help='Add user to group') +@click.option('--users', is_flag=True, metavar='ID', help='Get list of group users') @click.option('--delete', '-D', metavar='ID', help='Delete user group using ID') @click.pass_obj -def cli(obj, id, name, text, users, delete): +def cli(obj, id, name, text, user, users, delete): """Create or delete a user group.""" client = obj['client'] - if users: + if id and user: + client.add_user_to_group(id, user) + elif id and delete: + client.remove_user_from_group(id, delete) + elif users: group_users = client.get_group_users(id) timezone = obj['timezone'] headers = {'id': 'ID', 'name': 'USER', 'email': 'EMAIL', 'roles': 'ROLES', 'status': 'STATUS', diff --git a/alertaclient/commands/cmd_user.py b/alertaclient/commands/cmd_user.py index 7016f07..2638b6a 100644 --- a/alertaclient/commands/cmd_user.py +++ b/alertaclient/commands/cmd_user.py @@ -28,7 +28,7 @@ def parse_args(self, ctx, args): @click.option('--role', 'roles', multiple=True, help='List of roles') @click.option('--text', help='Description of user') @click.option('--email-verified/--email-not-verified', default=None, help='Email address verified flag') -@click.option('--groups', '-G', is_flag=True, help='Get list of user groups') +@click.option('--groups', is_flag=True, help='Get list of user groups') @click.option('--delete', '-D', metavar='ID', help='Delete user using ID') @click.pass_obj def cli(obj, id, name, email, password, status, roles, text, email_verified, groups, delete): From d7e6333431b55e435073b4a88f87f4346b7c64b5 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 10:31:43 +0200 Subject: [PATCH 11/90] feat: add examples for group cmd (#261) --- alertaclient/commands/cmd_group.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/alertaclient/commands/cmd_group.py b/alertaclient/commands/cmd_group.py index ee4c693..244c428 100644 --- a/alertaclient/commands/cmd_group.py +++ b/alertaclient/commands/cmd_group.py @@ -10,10 +10,34 @@ @click.option('--text', help='Description of user group') @click.option('--user', '-U', help='Add user to group') @click.option('--users', is_flag=True, metavar='ID', help='Get list of group users') -@click.option('--delete', '-D', metavar='ID', help='Delete user group using ID') +@click.option('--delete', '-D', metavar='ID', help='Delete user group, or remove user from group') @click.pass_obj def cli(obj, id, name, text, user, users, delete): - """Create or delete a user group.""" + """ + Create or delete a user group, add and remove users from groups. + + EXAMPLES + + Create a group. + + $ alerta group --name --text + + Add a user to a group. + + $ alerta group -id --user + + List users in a group. + + $ alerta group --users + + Delete a user from a group. + + $ alerta group -id -D + + Delete a group. + + $ alerta group -D + """ client = obj['client'] if id and user: client.add_user_to_group(id, user) From e47f857d501caebec7f612f2c69fae9c84215c44 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 13:01:17 +0200 Subject: [PATCH 12/90] fix: consistent use of ID as metavar (#262) --- alertaclient/commands/cmd_ack.py | 2 +- alertaclient/commands/cmd_action.py | 2 +- alertaclient/commands/cmd_close.py | 2 +- alertaclient/commands/cmd_delete.py | 2 +- alertaclient/commands/cmd_group.py | 2 +- alertaclient/commands/cmd_history.py | 2 +- alertaclient/commands/cmd_note.py | 4 ++-- alertaclient/commands/cmd_notes.py | 2 +- alertaclient/commands/cmd_query.py | 2 +- alertaclient/commands/cmd_raw.py | 2 +- alertaclient/commands/cmd_revoke.py | 2 +- alertaclient/commands/cmd_shelve.py | 2 +- alertaclient/commands/cmd_tag.py | 2 +- alertaclient/commands/cmd_unack.py | 2 +- alertaclient/commands/cmd_unshelve.py | 2 +- alertaclient/commands/cmd_untag.py | 2 +- alertaclient/commands/cmd_update.py | 2 +- alertaclient/commands/cmd_user.py | 2 +- alertaclient/commands/cmd_watch.py | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/alertaclient/commands/cmd_ack.py b/alertaclient/commands/cmd_ack.py index 8f198f2..aaf58ee 100644 --- a/alertaclient/commands/cmd_ack.py +++ b/alertaclient/commands/cmd_ack.py @@ -4,7 +4,7 @@ @click.command('ack', short_help='Acknowledge alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--text', help='Message associated with status change') diff --git a/alertaclient/commands/cmd_action.py b/alertaclient/commands/cmd_action.py index 662e52e..55095e9 100644 --- a/alertaclient/commands/cmd_action.py +++ b/alertaclient/commands/cmd_action.py @@ -5,7 +5,7 @@ @click.command('action', short_help='Action alerts') @click.option('--action', '-a', metavar='ACTION', help='Custom action (user-defined)') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--text', help='Message associated with action') diff --git a/alertaclient/commands/cmd_close.py b/alertaclient/commands/cmd_close.py index 7c40866..e3576b7 100644 --- a/alertaclient/commands/cmd_close.py +++ b/alertaclient/commands/cmd_close.py @@ -4,7 +4,7 @@ @click.command('ack', short_help='Close alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--text', help='Message associated with status change') diff --git a/alertaclient/commands/cmd_delete.py b/alertaclient/commands/cmd_delete.py index 2991fde..f333203 100644 --- a/alertaclient/commands/cmd_delete.py +++ b/alertaclient/commands/cmd_delete.py @@ -4,7 +4,7 @@ @click.command('delete', short_help='Delete alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.pass_obj diff --git a/alertaclient/commands/cmd_group.py b/alertaclient/commands/cmd_group.py index 244c428..81c9ba8 100644 --- a/alertaclient/commands/cmd_group.py +++ b/alertaclient/commands/cmd_group.py @@ -5,7 +5,7 @@ @click.command('group', short_help='Create user group') -@click.option('--id', '-i', metavar='UUID', help='Group ID') +@click.option('--id', '-i', metavar='ID', help='Group ID') @click.option('--name', help='Group name') @click.option('--text', help='Description of user group') @click.option('--user', '-U', help='Add user to group') diff --git a/alertaclient/commands/cmd_history.py b/alertaclient/commands/cmd_history.py index a454fdd..694dd4c 100644 --- a/alertaclient/commands/cmd_history.py +++ b/alertaclient/commands/cmd_history.py @@ -7,7 +7,7 @@ @click.command('history', short_help='Show alert history') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.pass_obj diff --git a/alertaclient/commands/cmd_note.py b/alertaclient/commands/cmd_note.py index 7b77212..ac7e182 100644 --- a/alertaclient/commands/cmd_note.py +++ b/alertaclient/commands/cmd_note.py @@ -4,8 +4,8 @@ @click.command('note', short_help='Add note') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of note IDs') -@click.option('--alert-ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of note IDs') +@click.option('--alert-ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--text', help='Note or message') diff --git a/alertaclient/commands/cmd_notes.py b/alertaclient/commands/cmd_notes.py index 7435f67..7348454 100644 --- a/alertaclient/commands/cmd_notes.py +++ b/alertaclient/commands/cmd_notes.py @@ -5,7 +5,7 @@ @click.command('notes', short_help='List notes') -@click.option('--alert-id', '-i', metavar='UUID', help='alert IDs (can use short 8-char id)') +@click.option('--alert-id', '-i', metavar='ID', help='alert IDs (can use short 8-char id)') @click.pass_obj def cli(obj, alert_id): """List notes.""" diff --git a/alertaclient/commands/cmd_query.py b/alertaclient/commands/cmd_query.py index 9ba95e4..fee7b67 100644 --- a/alertaclient/commands/cmd_query.py +++ b/alertaclient/commands/cmd_query.py @@ -17,7 +17,7 @@ @click.command('query', short_help='Search for alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--oneline', 'display', flag_value='oneline', default=True, help='Show alerts using table format') diff --git a/alertaclient/commands/cmd_raw.py b/alertaclient/commands/cmd_raw.py index c5b74d8..e48ca92 100644 --- a/alertaclient/commands/cmd_raw.py +++ b/alertaclient/commands/cmd_raw.py @@ -5,7 +5,7 @@ @click.command('raw', short_help='Show alert raw data') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.pass_obj diff --git a/alertaclient/commands/cmd_revoke.py b/alertaclient/commands/cmd_revoke.py index 346edd7..8adb128 100644 --- a/alertaclient/commands/cmd_revoke.py +++ b/alertaclient/commands/cmd_revoke.py @@ -4,7 +4,7 @@ @click.command('revoke', short_help='Revoke API key') -@click.option('--api-key', '-K', required=True, help='API Key or UUID') +@click.option('--api-key', '-K', required=True, help='API Key or ID') @click.pass_context def cli(ctx, api_key): ctx.invoke(revoke, delete=api_key) diff --git a/alertaclient/commands/cmd_shelve.py b/alertaclient/commands/cmd_shelve.py index 5bc25c3..8c19faf 100644 --- a/alertaclient/commands/cmd_shelve.py +++ b/alertaclient/commands/cmd_shelve.py @@ -4,7 +4,7 @@ @click.command('shelve', short_help='Shelve alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--timeout', metavar='SECONDS', type=int, help='Seconds before alert auto-unshelved.', default=7200, show_default=True) diff --git a/alertaclient/commands/cmd_tag.py b/alertaclient/commands/cmd_tag.py index a10e010..ce749a3 100644 --- a/alertaclient/commands/cmd_tag.py +++ b/alertaclient/commands/cmd_tag.py @@ -4,7 +4,7 @@ @click.command('tag', short_help='Tag alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--tag', '-T', 'tags', required=True, multiple=True, help='List of tags') diff --git a/alertaclient/commands/cmd_unack.py b/alertaclient/commands/cmd_unack.py index 4242432..23e26d7 100644 --- a/alertaclient/commands/cmd_unack.py +++ b/alertaclient/commands/cmd_unack.py @@ -4,7 +4,7 @@ @click.command('unack', short_help='Un-acknowledge alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--text', help='Message associated with status change') diff --git a/alertaclient/commands/cmd_unshelve.py b/alertaclient/commands/cmd_unshelve.py index ac23f4c..30348cb 100644 --- a/alertaclient/commands/cmd_unshelve.py +++ b/alertaclient/commands/cmd_unshelve.py @@ -4,7 +4,7 @@ @click.command('unshelve', short_help='Un-shelve alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--text', help='Message associated with status change') diff --git a/alertaclient/commands/cmd_untag.py b/alertaclient/commands/cmd_untag.py index 579ce33..3214934 100644 --- a/alertaclient/commands/cmd_untag.py +++ b/alertaclient/commands/cmd_untag.py @@ -4,7 +4,7 @@ @click.command('untag', short_help='Untag alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--tag', '-T', 'tags', required=True, multiple=True, help='List of tags') diff --git a/alertaclient/commands/cmd_update.py b/alertaclient/commands/cmd_update.py index 71f68ed..2873262 100644 --- a/alertaclient/commands/cmd_update.py +++ b/alertaclient/commands/cmd_update.py @@ -4,7 +4,7 @@ @click.command('update', short_help='Update alert attributes') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--attributes', '-A', metavar='KEY=VALUE', multiple=True, required=True, help='List of attributes eg. priority=high') diff --git a/alertaclient/commands/cmd_user.py b/alertaclient/commands/cmd_user.py index 2638b6a..532a757 100644 --- a/alertaclient/commands/cmd_user.py +++ b/alertaclient/commands/cmd_user.py @@ -20,7 +20,7 @@ def parse_args(self, ctx, args): @click.command('user', cls=CommandWithOptionalPassword, short_help='Update user') -@click.option('--id', '-i', metavar='UUID', help='User ID') +@click.option('--id', '-i', metavar='ID', help='User ID') @click.option('--name', help='Name of user') @click.option('--email', help='Email address (login username)') @click.option('--password', help='Password (will prompt if not supplied)') diff --git a/alertaclient/commands/cmd_watch.py b/alertaclient/commands/cmd_watch.py index f9c8e5d..ffe8c9a 100644 --- a/alertaclient/commands/cmd_watch.py +++ b/alertaclient/commands/cmd_watch.py @@ -7,7 +7,7 @@ @click.command('watch', short_help='Watch alerts') -@click.option('--ids', '-i', metavar='UUID', multiple=True, help='List of alert IDs (can use short 8-char id)') +@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--details', is_flag=True, help='Compact output with details') From 2576cc1ea69170eb6aa570402d951e01e5bb73bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Apr 2021 19:46:25 +0200 Subject: [PATCH 13/90] Bump pyyaml from 5.3.1 to 5.4.1 (#243) Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.3.1 to 5.4.1. - [Release notes](https://github.com/yaml/pyyaml/releases) - [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES) - [Commits](https://github.com/yaml/pyyaml/compare/5.3.1...5.4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 013b89e..67c8a65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Click==7.1.2 pytz==2020.4 -PyYAML==5.3.1 +PyYAML==5.4.1 requests==2.25.0 requests-hawk==1.0.1 tabulate==0.8.7 From 7203525fb551bf20d23e61e45da2a448161dce78 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 19:50:01 +0200 Subject: [PATCH 14/90] fix: improve alert note command (#263) --- alertaclient/commands/cmd_note.py | 23 +++++++++++++++++++---- alertaclient/models/note.py | 4 ++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/alertaclient/commands/cmd_note.py b/alertaclient/commands/cmd_note.py index ac7e182..c469b39 100644 --- a/alertaclient/commands/cmd_note.py +++ b/alertaclient/commands/cmd_note.py @@ -4,15 +4,30 @@ @click.command('note', short_help='Add note') -@click.option('--ids', '-i', metavar='ID', multiple=True, help='List of note IDs') @click.option('--alert-ids', '-i', metavar='ID', multiple=True, help='List of alert IDs (can use short 8-char id)') @click.option('--query', '-q', 'query', metavar='QUERY', help='severity:"warning" AND resource:web') @click.option('--filter', '-f', 'filters', metavar='FILTER', multiple=True, help='KEY=VALUE eg. serverity=warning resource=web') @click.option('--text', help='Note or message') -@click.option('--delete', '-D', metavar='ID', nargs=2, help='Delete note parent ID and note ID') +@click.option('--delete', '-D', metavar='ID', nargs=2, help='Delete note, using alert ID and note ID') @click.pass_obj -def cli(obj, ids, alert_ids, query, filters, text, delete): - """Add or delete note to alerts.""" +def cli(obj, alert_ids, query, filters, text, delete): + """ + Add or delete note to alerts. + + EXAMPLES + + Add a note to an alert. + + $ alerta note --alert-ids --text + + List notes for an alert. + + $ alerta notes --alert-ids + + Delete a note for an alert. + + $ alerta note -D + """ client = obj['client'] if delete: client.delete_alert_note(*delete) diff --git a/alertaclient/models/note.py b/alertaclient/models/note.py index 8718949..3979eef 100644 --- a/alertaclient/models/note.py +++ b/alertaclient/models/note.py @@ -40,12 +40,12 @@ def tabular(self, timezone=None): note = { 'id': self.id, 'text': self.text, + 'createTime': DateTime.localtime(self.create_time, timezone), 'user': self.user, # 'attributes': self.attributes, 'type': self.note_type, - 'createTime': DateTime.localtime(self.create_time, timezone), - 'updateTime': DateTime.localtime(self.update_time, timezone), 'related': self.alert, + 'updateTime': DateTime.localtime(self.update_time, timezone), 'customer': self.customer } return note From 18ca17c46c5c1075856b20166eafc070152edda4 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 18 Apr 2021 21:56:00 +0200 Subject: [PATCH 15/90] Bump version 8.4.0 -> 8.5.0 --- CHANGELOG.md | 20 +++++++++++++++++++- VERSION | 2 +- alertaclient/version.py | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 268a01b..14173b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,19 @@ -## Unreleased +## v8.5.0 (2021-04-18) + +### Fix + +- improve alert note command (#263) +- consistent use of ID as metavar (#262) +- add scopes cmd and minor fixes (#257) +- **build**: run tests against correct branch + +### Feat + +- add examples for group cmd (#261) +- add and remove users to/from groups (#260) +- add option to list users to group cmd (#259) +- add option to list groups to user cmd (#258) +- add alerts command for list alert attributes (#256) +- show user details (#255) +- add option to show decoded auth token claims (#254) +- **auth**: add HMAC authentication as config option (#248) diff --git a/VERSION b/VERSION index a2f28f4..6d28907 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.4.0 +8.5.0 diff --git a/alertaclient/version.py b/alertaclient/version.py index b0193b9..1d71024 100644 --- a/alertaclient/version.py +++ b/alertaclient/version.py @@ -1 +1 @@ -__version__ = '8.4.0' +__version__ = '8.5.0' From 2ccded37ee625ccd8f8ff6aa4fbac348f66a0f87 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Tue, 20 Apr 2021 08:08:47 +0200 Subject: [PATCH 16/90] test: remove docker login --- .github/workflows/github-ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/github-ci.yml b/.github/workflows/github-ci.yml index 4903c09..364b01c 100644 --- a/.github/workflows/github-ci.yml +++ b/.github/workflows/github-ci.yml @@ -51,12 +51,6 @@ jobs: DATABASE_URL: postgres://postgres:postgres@localhost:5432/alerta run: | pytest --cov=alertaclient tests/unit - - name: Docker Login - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - run: | - docker login $REPOSITORY_URL --username "$DOCKER_USERNAME" --password "$DOCKER_PASSWORD" - name: Integration Test id: integration-test run: | From 38ace7fa30419fbe7c06fbdc8a6c7a1d0213dbd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:19:45 +0200 Subject: [PATCH 17/90] Bump requests-hawk from 1.0.1 to 1.1.0 (#240) Bumps [requests-hawk](https://github.com/mozilla-services/requests-hawk) from 1.0.1 to 1.1.0. - [Release notes](https://github.com/mozilla-services/requests-hawk/releases) - [Changelog](https://github.com/mozilla-services/requests-hawk/blob/master/CHANGES.txt) - [Commits](https://github.com/mozilla-services/requests-hawk/compare/1.0.1...1.1.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67c8a65..595a2ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,5 @@ Click==7.1.2 pytz==2020.4 PyYAML==5.4.1 requests==2.25.0 -requests-hawk==1.0.1 +requests-hawk==1.1.0 tabulate==0.8.7 From d8853d961b045e6d9e400d780f621ae0190bf7cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:27:42 +0200 Subject: [PATCH 18/90] Bump requests from 2.25.0 to 2.25.1 (#241) Bumps [requests](https://github.com/psf/requests) from 2.25.0 to 2.25.1. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/master/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.0...v2.25.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 595a2ba..5a33cc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Click==7.1.2 pytz==2020.4 PyYAML==5.4.1 -requests==2.25.0 +requests==2.25.1 requests-hawk==1.1.0 tabulate==0.8.7 From 130b94a6b2bb7dd5a176c8a1f0a451d534f02f7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:27:56 +0200 Subject: [PATCH 19/90] Bump pytz from 2020.4 to 2021.1 (#247) Bumps [pytz](https://github.com/stub42/pytz) from 2020.4 to 2021.1. - [Release notes](https://github.com/stub42/pytz/releases) - [Commits](https://github.com/stub42/pytz/compare/release_2020.4...release_2021.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5a33cc7..3a2d818 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Click==7.1.2 -pytz==2020.4 +pytz==2021.1 PyYAML==5.4.1 requests==2.25.1 requests-hawk==1.1.0 From 44a99009f436655d1a9c651150ab358166bde008 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:28:06 +0200 Subject: [PATCH 20/90] Bump tabulate from 0.8.7 to 0.8.9 (#264) Bumps [tabulate](https://github.com/astanin/python-tabulate) from 0.8.7 to 0.8.9. - [Release notes](https://github.com/astanin/python-tabulate/releases) - [Changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG) - [Commits](https://github.com/astanin/python-tabulate/compare/v0.8.7...v0.8.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3a2d818..8bc3bb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ pytz==2021.1 PyYAML==5.4.1 requests==2.25.1 requests-hawk==1.1.0 -tabulate==0.8.7 +tabulate==0.8.9 From 797b6a11c55d6d7951b001d48e186a1efabf9474 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:28:22 +0200 Subject: [PATCH 21/90] Bump pre-commit from 2.9.2 to 2.12.1 (#265) Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.9.2 to 2.12.1. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.9.2...v2.12.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 52efccd..92852b9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ mypy==0.790 -pre-commit==2.9.2 +pre-commit==2.12.1 pylint==2.6.0 pytest-cov pytest>=5.4.3 From cf0d34b0d2fce8de70b9c557f74c804c5303f426 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:52:55 +0200 Subject: [PATCH 22/90] Bump mypy from 0.790 to 0.812 (#266) Bumps [mypy](https://github.com/python/mypy) from 0.790 to 0.812. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.790...v0.812) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 92852b9..bc9d411 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -mypy==0.790 +mypy==0.812 pre-commit==2.12.1 pylint==2.6.0 pytest-cov From 60ee2779ab56c03e854574db471af7bf5cbf14f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:53:22 +0200 Subject: [PATCH 23/90] Bump pylint from 2.6.0 to 2.7.4 (#267) Bumps [pylint](https://github.com/PyCQA/pylint) from 2.6.0 to 2.7.4. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.6.0...pylint-2.7.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index bc9d411..c0a7720 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ mypy==0.812 pre-commit==2.12.1 -pylint==2.6.0 +pylint==2.7.4 pytest-cov pytest>=5.4.3 python-dotenv From 1e7d917ebc429b0d51a63e91fe6b5712403726e4 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 8 May 2021 20:45:47 +0200 Subject: [PATCH 24/90] feat: Add option to use custom value when creating API key (#270) * Support user-defined API keys * Add tests for user-defined API key --- alertaclient/api.py | 4 +++- alertaclient/commands/cmd_key.py | 5 +++-- tests/integration/test_keys.py | 3 ++- tests/unit/test_keys.py | 6 +++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index c0a7540..fce1bb6 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -265,7 +265,7 @@ def delete_heartbeat(self, id): return self.http.delete('/heartbeat/%s' % id) # API Keys - def create_key(self, username, scopes=None, expires=None, text='', customer=None): + def create_key(self, username, scopes=None, expires=None, text='', customer=None, **kwargs): data = { 'user': username, 'scopes': scopes or list(), @@ -274,6 +274,8 @@ def create_key(self, username, scopes=None, expires=None, text='', customer=None } if expires: data['expireTime'] = DateTime.iso8601(expires) + if kwargs.get('key'): + data['key'] = kwargs['key'] r = self.http.post('/key', data) return ApiKey.parse(r['data']) diff --git a/alertaclient/commands/cmd_key.py b/alertaclient/commands/cmd_key.py index 58819e9..a58cc0b 100644 --- a/alertaclient/commands/cmd_key.py +++ b/alertaclient/commands/cmd_key.py @@ -5,6 +5,7 @@ @click.command('key', short_help='Create API key') +@click.option('--api-key', '-K', help='User-defined API Key. [default: random string]') @click.option('--username', '-u', help='User (Admin only)') @click.option('--scope', 'scopes', multiple=True, help='List of permissions eg. admin:keys, write:alerts') @click.option('--duration', metavar='SECONDS', type=int, help='Duration API key is valid') @@ -12,7 +13,7 @@ @click.option('--customer', metavar='STRING', help='Customer') @click.option('--delete', '-D', metavar='ID', help='Delete API key using ID or KEY') @click.pass_obj -def cli(obj, username, scopes, duration, text, customer, delete): +def cli(obj, api_key, username, scopes, duration, text, customer, delete): """Create or delete an API key.""" client = obj['client'] if delete: @@ -20,7 +21,7 @@ def cli(obj, username, scopes, duration, text, customer, delete): else: try: expires = datetime.utcnow() + timedelta(seconds=duration) if duration else None - key = client.create_key(username, scopes, expires, text, customer) + key = client.create_key(username, scopes, expires, text, customer, key=api_key) except Exception as e: click.echo('ERROR: {}'.format(e), err=True) sys.exit(1) diff --git a/tests/integration/test_keys.py b/tests/integration/test_keys.py index d0bc75d..639c7c2 100644 --- a/tests/integration/test_keys.py +++ b/tests/integration/test_keys.py @@ -23,8 +23,9 @@ def test_key(self): self.assertEqual(api_key.text, 'Updated Ops API Key') api_key = self.client.create_key( - username='key@alerta.io', scopes=[Scope.admin], text='Admin API Key' + username='key@alerta.io', scopes=[Scope.admin], text='Admin API Key', key='admin-key' ) + self.assertEqual(api_key.key, 'admin-key') api_keys = self.client.get_keys(query=[('user', 'key@alerta.io')]) self.assertEqual(len(api_keys), 2) diff --git a/tests/unit/test_keys.py b/tests/unit/test_keys.py index dbaa3fd..2951a66 100644 --- a/tests/unit/test_keys.py +++ b/tests/unit/test_keys.py @@ -28,7 +28,7 @@ def setUp(self): "type": "read-write", "user": "johndoe@example.com" }, - "key": "BpSG0Ck5JCqk5TJiuBSLAWuTs03QKc_527T5cDtw", + "key": "demo-key", "status": "ok" } """ @@ -36,7 +36,7 @@ def setUp(self): @requests_mock.mock() def test_key(self, m): m.post('http://localhost:8080/key', text=self.key) - api_key = self.client.create_key(username='johndoe@example.com', scopes=[ - 'write:alerts', 'admin:keys'], text='Ops API Key') + api_key = self.client.create_key(username='johndoe@example.com', scopes=['write:alerts', 'admin:keys'], + text='Ops API Key', key='demo-key') self.assertEqual(api_key.user, 'johndoe@example.com') self.assertEqual(sorted(api_key.scopes), sorted(['write:alerts', 'admin:keys'])) From 70efe6d00509dfc28bb108598093d1a626f5d79f Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 8 May 2021 21:24:02 +0200 Subject: [PATCH 25/90] refactor: assign api key directly (#271) --- alertaclient/api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index fce1bb6..965bb55 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -270,12 +270,11 @@ def create_key(self, username, scopes=None, expires=None, text='', customer=None 'user': username, 'scopes': scopes or list(), 'text': text, - 'customer': customer + 'customer': customer, + 'key': kwargs.get('key') } if expires: data['expireTime'] = DateTime.iso8601(expires) - if kwargs.get('key'): - data['key'] = kwargs['key'] r = self.http.post('/key', data) return ApiKey.parse(r['data']) From 255c6707626f18a03414f7520e8e9dc0f4ce3a72 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 9 May 2021 16:28:49 +0200 Subject: [PATCH 26/90] refactor: convert formatted strings to f-strings (#272) --- alertaclient/api.py | 34 +++++++++++------------ alertaclient/auth/utils.py | 6 ++--- alertaclient/commands/cmd_ack.py | 2 +- alertaclient/commands/cmd_action.py | 2 +- alertaclient/commands/cmd_blackout.py | 2 +- alertaclient/commands/cmd_blackouts.py | 2 +- alertaclient/commands/cmd_close.py | 2 +- alertaclient/commands/cmd_config.py | 2 +- alertaclient/commands/cmd_customer.py | 2 +- alertaclient/commands/cmd_delete.py | 2 +- alertaclient/commands/cmd_group.py | 2 +- alertaclient/commands/cmd_heartbeat.py | 2 +- alertaclient/commands/cmd_heartbeats.py | 12 ++++----- alertaclient/commands/cmd_key.py | 2 +- alertaclient/commands/cmd_login.py | 4 +-- alertaclient/commands/cmd_me.py | 2 +- alertaclient/commands/cmd_note.py | 2 +- alertaclient/commands/cmd_notes.py | 2 +- alertaclient/commands/cmd_perm.py | 2 +- alertaclient/commands/cmd_query.py | 36 ++++++++++++------------- alertaclient/commands/cmd_send.py | 10 +++---- alertaclient/commands/cmd_shelve.py | 2 +- alertaclient/commands/cmd_signup.py | 2 +- alertaclient/commands/cmd_tag.py | 2 +- alertaclient/commands/cmd_token.py | 2 +- alertaclient/commands/cmd_unack.py | 2 +- alertaclient/commands/cmd_unshelve.py | 2 +- alertaclient/commands/cmd_untag.py | 2 +- alertaclient/commands/cmd_update.py | 2 +- alertaclient/commands/cmd_user.py | 4 +-- alertaclient/commands/cmd_version.py | 6 ++--- alertaclient/commands/cmd_whoami.py | 2 +- alertaclient/config.py | 4 +-- alertaclient/models/blackout.py | 4 +-- alertaclient/models/enums.py | 2 +- alertaclient/models/heartbeat.py | 6 ++--- alertaclient/top.py | 2 +- alertaclient/utils.py | 4 +-- 38 files changed, 91 insertions(+), 91 deletions(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index 965bb55..4ab8e47 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -150,22 +150,22 @@ def alert_note(self, id, text): data = { 'text': text } - r = self.http.put('/alert/{}/note'.format(id), data) + r = self.http.put(f'/alert/{id}/note', data) return Note.parse(r['note']) def get_alert_notes(self, id, page=1, page_size=None): - r = self.http.get('/alert/{}/notes'.format(id), page=page, page_size=page_size) + r = self.http.get(f'/alert/{id}/notes', page=page, page_size=page_size) return [Note.parse(n) for n in r['notes']] def update_alert_note(self, id, note_id, text): data = { 'text': text, } - r = self.http.put('/alert/{}/note/{}'.format(id, note_id), data) + r = self.http.put(f'/alert/{id}/note/{note_id}', data) return Note.parse(r['note']) def delete_alert_note(self, id, note_id): - return self.http.delete('/alert/{}/note/{}'.format(id, note_id)) + return self.http.delete(f'/alert/{id}/note/{note_id}') # Blackouts def create_blackout(self, environment, service=None, resource=None, event=None, group=None, tags=None, @@ -208,7 +208,7 @@ def update_blackout(self, id, **kwargs): 'text': kwargs.get('text'), } - r = self.http.put('/blackout/{}'.format(id), data) + r = self.http.put(f'/blackout/{id}', data) return Blackout.parse(r['blackout']) def delete_blackout(self, id): @@ -235,7 +235,7 @@ def update_customer(self, id, **kwargs): 'match': kwargs.get('match'), 'customer': kwargs.get('customer') } - r = self.http.put('/customer/{}'.format(id), data) + r = self.http.put(f'/customer/{id}', data) return Customer.parse(r['customer']) def delete_customer(self, id): @@ -292,7 +292,7 @@ def update_key(self, id, **kwargs): 'expireTime': kwargs.get('expireTime'), 'customer': kwargs.get('customer') } - r = self.http.put('/key/{}'.format(id), data) + r = self.http.put(f'/key/{id}', data) return ApiKey.parse(r['key']) def delete_key(self, id): @@ -319,7 +319,7 @@ def update_perm(self, id, **kwargs): 'match': kwargs.get('match'), # role 'scopes': kwargs.get('scopes') } - r = self.http.put('/perm/{}'.format(id), data) + r = self.http.put(f'/perm/{id}', data) return Permission.parse(r['permission']) def delete_perm(self, id): @@ -359,7 +359,7 @@ def get_user(self, id): return User.parse(self.http.get('/user/%s' % id)['user']) def get_user_groups(self, id): - r = self.http.get('/user/{}/groups'.format(id)) + r = self.http.get(f'/user/{id}/groups') return [Group.parse(g) for g in r['groups']] def get_me(self): @@ -383,7 +383,7 @@ def update_user(self, id, **kwargs): 'text': kwargs.get('text'), 'email_verified': kwargs.get('email_verified') } - r = self.http.put('/user/{}'.format(id), data) + r = self.http.put(f'/user/{id}', data) return User.parse(r['user']) def update_me(self, **kwargs): @@ -452,7 +452,7 @@ def get_group(self): return Group.parse(self.http.get('/group/%s' % id)['group']) def get_group_users(self, id): - r = self.http.get('/group/{}/users'.format(id)) + r = self.http.get(f'/group/{id}/users') return [User.parse(u) for u in r['users']] def get_users_groups(self, query=None): @@ -464,14 +464,14 @@ def update_group(self, id, **kwargs): 'name': kwargs.get('name'), 'text': kwargs.get('text') } - r = self.http.put('/group/{}'.format(id), data) + r = self.http.put(f'/group/{id}', data) return Group.parse(r['group']) def add_user_to_group(self, group_id, user_id): - return self.http.put('/group/{}/user/{}'.format(group_id, user_id)) + return self.http.put(f'/group/{group_id}/user/{user_id}') def remove_user_from_group(self, group_id, user_id): - return self.http.delete('/group/{}/user/{}'.format(group_id, user_id)) + return self.http.delete(f'/group/{group_id}/user/{user_id}') def delete_group(self, id): return self.http.delete('/group/%s' % id) @@ -500,7 +500,7 @@ def __init__(self, api_key=None, auth_token=None): self.auth_token = auth_token def __call__(self, r): - r.headers['Authorization'] = 'Key {}'.format(self.api_key) + r.headers['Authorization'] = f'Key {self.api_key}' return r @@ -510,7 +510,7 @@ def __init__(self, auth_token=None): self.auth_token = auth_token def __call__(self, r): - r.headers['Authorization'] = 'Bearer {}'.format(self.auth_token) + r.headers['Authorization'] = f'Bearer {self.auth_token}' return r @@ -608,7 +608,7 @@ def delete(self, path): def _handle_error(self, response): if self.debug: - print('\nbody: {}'.format(response.text)) + print(f'\nbody: {response.text}') resp = response.json() status = resp.get('status', None) if status == 'ok': diff --git a/alertaclient/auth/utils.py b/alertaclient/auth/utils.py index 2fa85e1..d966cf5 100644 --- a/alertaclient/auth/utils.py +++ b/alertaclient/auth/utils.py @@ -29,7 +29,7 @@ def save_token(endpoint, username, token): try: info = netrc(NETRC_FILE) except Exception as e: - raise ConfigurationError('{}'.format(e)) + raise ConfigurationError(f'{e}') info.hosts[machine(endpoint)] = (username, None, token) with open(NETRC_FILE, 'w') as f: f.write(dump_netrc(info)) @@ -39,13 +39,13 @@ def clear_token(endpoint): try: info = netrc(NETRC_FILE) except Exception as e: - raise ConfigurationError('{}'.format(e)) + raise ConfigurationError(f'{e}') try: del info.hosts[machine(endpoint)] with open(NETRC_FILE, 'w') as f: f.write(dump_netrc(info)) except KeyError as e: - raise ConfigurationError('No credentials stored for {}'.format(e)) + raise ConfigurationError(f'No credentials stored for {e}') # See https://bugs.python.org/issue30806 diff --git a/alertaclient/commands/cmd_ack.py b/alertaclient/commands/cmd_ack.py index aaf58ee..1797020 100644 --- a/alertaclient/commands/cmd_ack.py +++ b/alertaclient/commands/cmd_ack.py @@ -22,4 +22,4 @@ def cli(obj, ids, query, filters, text): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - action_progressbar(client, action='ack', ids=ids, label='Acking {} alerts'.format(total), text=text) + action_progressbar(client, action='ack', ids=ids, label=f'Acking {total} alerts', text=text) diff --git a/alertaclient/commands/cmd_action.py b/alertaclient/commands/cmd_action.py index 55095e9..be4b29b 100644 --- a/alertaclient/commands/cmd_action.py +++ b/alertaclient/commands/cmd_action.py @@ -23,5 +23,5 @@ def cli(obj, action, ids, query, filters, text): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - label = 'Action ({}) {} alerts'.format(action, total) + label = f'Action ({action}) {total} alerts' action_progressbar(client, action=action, ids=ids, label=label, text=text) diff --git a/alertaclient/commands/cmd_blackout.py b/alertaclient/commands/cmd_blackout.py index 2810efc..697a4df 100644 --- a/alertaclient/commands/cmd_blackout.py +++ b/alertaclient/commands/cmd_blackout.py @@ -40,6 +40,6 @@ def cli(obj, environment, service, resource, event, group, tags, origin, custome text=text ) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(blackout.id) diff --git a/alertaclient/commands/cmd_blackouts.py b/alertaclient/commands/cmd_blackouts.py index 583dd33..edbb77e 100644 --- a/alertaclient/commands/cmd_blackouts.py +++ b/alertaclient/commands/cmd_blackouts.py @@ -27,6 +27,6 @@ def cli(obj, purge): expired = [b for b in blackouts if b.status == 'expired'] if purge: - with click.progressbar(expired, label='Purging {} blackouts'.format(len(expired))) as bar: + with click.progressbar(expired, label=f'Purging {len(expired)} blackouts') as bar: for b in bar: client.delete_blackout(b.id) diff --git a/alertaclient/commands/cmd_close.py b/alertaclient/commands/cmd_close.py index e3576b7..8b7ac78 100644 --- a/alertaclient/commands/cmd_close.py +++ b/alertaclient/commands/cmd_close.py @@ -22,4 +22,4 @@ def cli(obj, ids, query, filters, text): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - action_progressbar(client, action='close', ids=ids, label='Closing {} alerts'.format(total), text=text) + action_progressbar(client, action='close', ids=ids, label=f'Closing {total} alerts', text=text) diff --git a/alertaclient/commands/cmd_config.py b/alertaclient/commands/cmd_config.py index f458601..04b7c4e 100644 --- a/alertaclient/commands/cmd_config.py +++ b/alertaclient/commands/cmd_config.py @@ -8,4 +8,4 @@ def cli(obj): for k, v in obj.items(): if isinstance(v, list): v = ', '.join(v) - click.echo('{:20}: {}'.format(k, v)) + click.echo(f'{k:20}: {v}') diff --git a/alertaclient/commands/cmd_customer.py b/alertaclient/commands/cmd_customer.py index 33942f3..51f7540 100644 --- a/alertaclient/commands/cmd_customer.py +++ b/alertaclient/commands/cmd_customer.py @@ -21,6 +21,6 @@ def cli(obj, customer, match, delete): try: customer = client.create_customer(customer, match) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(customer.id) diff --git a/alertaclient/commands/cmd_delete.py b/alertaclient/commands/cmd_delete.py index f333203..c3ca575 100644 --- a/alertaclient/commands/cmd_delete.py +++ b/alertaclient/commands/cmd_delete.py @@ -23,6 +23,6 @@ def cli(obj, ids, query, filters): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - with click.progressbar(ids, label='Deleting {} alerts'.format(total)) as bar: + with click.progressbar(ids, label=f'Deleting {total} alerts') as bar: for id in bar: client.delete_alert(id) diff --git a/alertaclient/commands/cmd_group.py b/alertaclient/commands/cmd_group.py index 81c9ba8..63ec9a4 100644 --- a/alertaclient/commands/cmd_group.py +++ b/alertaclient/commands/cmd_group.py @@ -56,6 +56,6 @@ def cli(obj, id, name, text, user, users, delete): try: group = client.create_group(name, text) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(group.id) diff --git a/alertaclient/commands/cmd_heartbeat.py b/alertaclient/commands/cmd_heartbeat.py index 3c6760a..901c948 100644 --- a/alertaclient/commands/cmd_heartbeat.py +++ b/alertaclient/commands/cmd_heartbeat.py @@ -52,6 +52,6 @@ def cli(obj, origin, environment, severity, service, group, tags, timeout, custo try: heartbeat = client.heartbeat(origin=origin, tags=tags, attributes=attributes, timeout=timeout, customer=customer) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(heartbeat.id) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index 079f738..db9ffd1 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -48,12 +48,12 @@ def cli(obj, alert, severity, timeout, purge): not_ok = [hb for hb in heartbeats if hb.status != 'ok'] if purge: - with click.progressbar(not_ok, label='Purging {} heartbeats'.format(len(not_ok))) as bar: + with click.progressbar(not_ok, label=f'Purging {len(not_ok)} heartbeats') as bar: for b in bar: client.delete_heartbeat(b.id) if alert: - with click.progressbar(heartbeats, label='Alerting {} heartbeats'.format(len(heartbeats))) as bar: + with click.progressbar(heartbeats, label=f'Alerting {len(heartbeats)} heartbeats') as bar: for b in bar: want_environment = b.attributes.pop('environment', 'Production') @@ -70,8 +70,8 @@ def cli(obj, alert, severity, timeout, purge): correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], service=want_service, group=want_group, - value='{}'.format(b.since), - text='Heartbeat not received in {} seconds'.format(b.timeout), + value=f'{b.since}', + text=f'Heartbeat not received in {b.timeout} seconds', tags=b.tags, attributes=b.attributes, origin=origin(), @@ -88,8 +88,8 @@ def cli(obj, alert, severity, timeout, purge): correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], service=want_service, group=want_group, - value='{}ms'.format(b.latency), - text='Heartbeat took more than {}ms to be processed'.format(b.max_latency), + value=f'{b.latency}ms', + text=f'Heartbeat took more than {b.max_latency}ms to be processed', tags=b.tags, attributes=b.attributes, origin=origin(), diff --git a/alertaclient/commands/cmd_key.py b/alertaclient/commands/cmd_key.py index a58cc0b..5889b1d 100644 --- a/alertaclient/commands/cmd_key.py +++ b/alertaclient/commands/cmd_key.py @@ -23,6 +23,6 @@ def cli(obj, api_key, username, scopes, duration, text, customer, delete): expires = datetime.utcnow() + timedelta(seconds=duration) if duration else None key = client.create_key(username, scopes, expires, text, customer, key=api_key) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(key.key) diff --git a/alertaclient/commands/cmd_login.py b/alertaclient/commands/cmd_login.py index 74191f6..ddbeac3 100644 --- a/alertaclient/commands/cmd_login.py +++ b/alertaclient/commands/cmd_login.py @@ -37,7 +37,7 @@ def cli(obj, username): password = click.prompt('Password', hide_input=True) token = client.login(username, password)['token'] else: - click.echo('ERROR: unknown provider {provider}'.format(provider=provider), err=True) + click.echo(f'ERROR: unknown provider {provider}', err=True) sys.exit(1) except Exception as e: raise AuthError(e) @@ -46,7 +46,7 @@ def cli(obj, username): preferred_username = jwt.parse(token)['preferred_username'] if preferred_username: save_token(client.endpoint, preferred_username, token) - click.echo('Logged in as {}'.format(preferred_username)) + click.echo(f'Logged in as {preferred_username}') else: click.echo('Failed to login.') sys.exit(1) diff --git a/alertaclient/commands/cmd_me.py b/alertaclient/commands/cmd_me.py index 09e7640..e073f73 100644 --- a/alertaclient/commands/cmd_me.py +++ b/alertaclient/commands/cmd_me.py @@ -20,6 +20,6 @@ def cli(obj, name, email, password, status, text): try: user = client.update_me(name=name, email=email, password=password, status=status, attributes=None, text=text) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(user.id) diff --git a/alertaclient/commands/cmd_note.py b/alertaclient/commands/cmd_note.py index c469b39..f341163 100644 --- a/alertaclient/commands/cmd_note.py +++ b/alertaclient/commands/cmd_note.py @@ -42,6 +42,6 @@ def cli(obj, alert_ids, query, filters, text, delete): total, _, _ = client.get_count(query) alert_ids = [a.id for a in client.get_alerts(query)] - with click.progressbar(alert_ids, label='Add note to {} alerts'.format(total)) as bar: + with click.progressbar(alert_ids, label=f'Add note to {total} alerts') as bar: for id in bar: client.alert_note(id, text=text) diff --git a/alertaclient/commands/cmd_notes.py b/alertaclient/commands/cmd_notes.py index 7348454..fc57943 100644 --- a/alertaclient/commands/cmd_notes.py +++ b/alertaclient/commands/cmd_notes.py @@ -12,7 +12,7 @@ def cli(obj, alert_id): client = obj['client'] if alert_id: if obj['output'] == 'json': - r = client.http.get('/alert/{}/notes'.format(alert_id)) + r = client.http.get(f'/alert/{alert_id}/notes') click.echo(json.dumps(r['notes'], sort_keys=True, indent=4, ensure_ascii=False)) else: timezone = obj['timezone'] diff --git a/alertaclient/commands/cmd_perm.py b/alertaclient/commands/cmd_perm.py index 361acee..236ee59 100644 --- a/alertaclient/commands/cmd_perm.py +++ b/alertaclient/commands/cmd_perm.py @@ -21,6 +21,6 @@ def cli(obj, role, scopes, delete): try: perm = client.create_perm(role, scopes) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(perm.id) diff --git a/alertaclient/commands/cmd_query.py b/alertaclient/commands/cmd_query.py index fee7b67..f794cee 100644 --- a/alertaclient/commands/cmd_query.py +++ b/alertaclient/commands/cmd_query.py @@ -72,21 +72,21 @@ def cli(obj, ids, query, filters, display, from_date=None): alert.group, alert.event, alert.value or 'n/a'), fg=color['fg']) - click.secho(' |{}'.format(alert.text), fg=color['fg']) + click.secho(f' |{alert.text}', fg=color['fg']) if display == 'full': click.secho(' severity | {} -> {}'.format(alert.previous_severity, alert.severity), fg=color['fg']) - click.secho(' trend | {}'.format(alert.trend_indication), fg=color['fg']) - click.secho(' status | {}'.format(alert.status), fg=color['fg']) - click.secho(' resource | {}'.format(alert.resource), fg=color['fg']) - click.secho(' group | {}'.format(alert.group), fg=color['fg']) - click.secho(' event | {}'.format(alert.event), fg=color['fg']) - click.secho(' value | {}'.format(alert.value), fg=color['fg']) + click.secho(f' trend | {alert.trend_indication}', fg=color['fg']) + click.secho(f' status | {alert.status}', fg=color['fg']) + click.secho(f' resource | {alert.resource}', fg=color['fg']) + click.secho(f' group | {alert.group}', fg=color['fg']) + click.secho(f' event | {alert.event}', fg=color['fg']) + click.secho(f' value | {alert.value}', fg=color['fg']) click.secho(' tags | {}'.format(' '.join(alert.tags)), fg=color['fg']) for key, value in alert.attributes.items(): - click.secho(' {} | {}'.format(key.ljust(10), value), fg=color['fg']) + click.secho(f' {key.ljust(10)} | {value}', fg=color['fg']) latency = alert.receive_time - alert.create_time @@ -96,18 +96,18 @@ def cli(obj, ids, query, filters, display, from_date=None): DateTime.localtime(alert.receive_time, timezone)), fg=color['fg']) click.secho(' last received | {}'.format( DateTime.localtime(alert.last_receive_time, timezone)), fg=color['fg']) - click.secho(' latency | {}ms'.format(latency.microseconds / 1000), fg=color['fg']) - click.secho(' timeout | {}s'.format(alert.timeout), fg=color['fg']) + click.secho(f' latency | {latency.microseconds / 1000}ms', fg=color['fg']) + click.secho(f' timeout | {alert.timeout}s', fg=color['fg']) - click.secho(' alert id | {}'.format(alert.id), fg=color['fg']) - click.secho(' last recv id | {}'.format(alert.last_receive_id), fg=color['fg']) - click.secho(' customer | {}'.format(alert.customer), fg=color['fg']) - click.secho(' environment | {}'.format(alert.environment), fg=color['fg']) + click.secho(f' alert id | {alert.id}', fg=color['fg']) + click.secho(f' last recv id | {alert.last_receive_id}', fg=color['fg']) + click.secho(f' customer | {alert.customer}', fg=color['fg']) + click.secho(f' environment | {alert.environment}', fg=color['fg']) click.secho(' service | {}'.format(','.join(alert.service)), fg=color['fg']) - click.secho(' resource | {}'.format(alert.resource), fg=color['fg']) - click.secho(' type | {}'.format(alert.event_type), fg=color['fg']) - click.secho(' repeat | {}'.format(alert.repeat), fg=color['fg']) - click.secho(' origin | {}'.format(alert.origin), fg=color['fg']) + click.secho(f' resource | {alert.resource}', fg=color['fg']) + click.secho(f' type | {alert.event_type}', fg=color['fg']) + click.secho(f' repeat | {alert.repeat}', fg=color['fg']) + click.secho(f' origin | {alert.origin}', fg=color['fg']) click.secho(' correlate | {}'.format(','.join(alert.correlate)), fg=color['fg']) return auto_refresh, last_time diff --git a/alertaclient/commands/cmd_send.py b/alertaclient/commands/cmd_send.py index 00ba2a3..bf01a2c 100644 --- a/alertaclient/commands/cmd_send.py +++ b/alertaclient/commands/cmd_send.py @@ -48,15 +48,15 @@ def send_alert(resource, event, **kwargs): customer=kwargs.get('customer') ) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) if alert: if alert.repeat: - message = '{} duplicates'.format(alert.duplicate_count) + message = f'{alert.duplicate_count} duplicates' else: - message = '{} -> {}'.format(alert.previous_severity, alert.severity) - click.echo('{} ({})'.format(id, message)) + message = f'{alert.previous_severity} -> {alert.severity}' + click.echo(f'{id} ({message})') # read raw data from file or stdin if raw_data and raw_data.startswith('@') or raw_data == '-': @@ -71,7 +71,7 @@ def send_alert(resource, event, **kwargs): try: payload = json.loads(line) except Exception as e: - click.echo("ERROR: JSON parse failure - input must be in 'json_lines' format: {}".format(e), err=True) + click.echo(f"ERROR: JSON parse failure - input must be in 'json_lines' format: {e}", err=True) sys.exit(1) send_alert(**payload) sys.exit(0) diff --git a/alertaclient/commands/cmd_shelve.py b/alertaclient/commands/cmd_shelve.py index 8c19faf..20a4e40 100644 --- a/alertaclient/commands/cmd_shelve.py +++ b/alertaclient/commands/cmd_shelve.py @@ -24,4 +24,4 @@ def cli(obj, ids, query, filters, timeout, text): ids = [a.id for a in client.get_alerts(query)] action_progressbar(client, action='shelve', ids=ids, - label='Shelving {} alerts'.format(total), text=text, timeout=timeout) + label=f'Shelving {total} alerts', text=text, timeout=timeout) diff --git a/alertaclient/commands/cmd_signup.py b/alertaclient/commands/cmd_signup.py index dfa736d..cf6be9c 100644 --- a/alertaclient/commands/cmd_signup.py +++ b/alertaclient/commands/cmd_signup.py @@ -22,7 +22,7 @@ def cli(obj, name, email, password, status, text): try: r = client.signup(name=name, email=email, password=password, status=status, attributes=None, text=text) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) if 'token' in r: click.echo('Signed Up.') diff --git a/alertaclient/commands/cmd_tag.py b/alertaclient/commands/cmd_tag.py index ce749a3..f98c678 100644 --- a/alertaclient/commands/cmd_tag.py +++ b/alertaclient/commands/cmd_tag.py @@ -22,6 +22,6 @@ def cli(obj, ids, query, filters, tags): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - with click.progressbar(ids, label='Tagging {} alerts'.format(total)) as bar: + with click.progressbar(ids, label=f'Tagging {total} alerts') as bar: for id in bar: client.tag_alert(id, tags) diff --git a/alertaclient/commands/cmd_token.py b/alertaclient/commands/cmd_token.py index a66cf55..044bbae 100644 --- a/alertaclient/commands/cmd_token.py +++ b/alertaclient/commands/cmd_token.py @@ -16,6 +16,6 @@ def cli(obj, decode): for k, v in jwt.parse(token).items(): if isinstance(v, list): v = ', '.join(v) - click.echo('{:20}: {}'.format(k, v)) + click.echo(f'{k:20}: {v}') else: click.echo(token) diff --git a/alertaclient/commands/cmd_unack.py b/alertaclient/commands/cmd_unack.py index 23e26d7..64b25d5 100644 --- a/alertaclient/commands/cmd_unack.py +++ b/alertaclient/commands/cmd_unack.py @@ -22,4 +22,4 @@ def cli(obj, ids, query, filters, text): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - action_progressbar(client, action='unack', ids=ids, label='Un-acking {} alerts'.format(total), text=text) + action_progressbar(client, action='unack', ids=ids, label=f'Un-acking {total} alerts', text=text) diff --git a/alertaclient/commands/cmd_unshelve.py b/alertaclient/commands/cmd_unshelve.py index 30348cb..278a8e7 100644 --- a/alertaclient/commands/cmd_unshelve.py +++ b/alertaclient/commands/cmd_unshelve.py @@ -22,4 +22,4 @@ def cli(obj, ids, query, filters, text): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - action_progressbar(client, 'unshelve', ids, label='Un-shelving {} alerts'.format(total), text=text) + action_progressbar(client, 'unshelve', ids, label=f'Un-shelving {total} alerts', text=text) diff --git a/alertaclient/commands/cmd_untag.py b/alertaclient/commands/cmd_untag.py index 3214934..d2026cd 100644 --- a/alertaclient/commands/cmd_untag.py +++ b/alertaclient/commands/cmd_untag.py @@ -22,6 +22,6 @@ def cli(obj, ids, query, filters, tags): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - with click.progressbar(ids, label='Untagging {} alerts'.format(total)) as bar: + with click.progressbar(ids, label=f'Untagging {total} alerts') as bar: for id in bar: client.untag_alert(id, tags) diff --git a/alertaclient/commands/cmd_update.py b/alertaclient/commands/cmd_update.py index 2873262..90c0155 100644 --- a/alertaclient/commands/cmd_update.py +++ b/alertaclient/commands/cmd_update.py @@ -22,6 +22,6 @@ def cli(obj, ids, query, filters, attributes): total, _, _ = client.get_count(query) ids = [a.id for a in client.get_alerts(query)] - with click.progressbar(ids, label='Updating {} alerts'.format(total)) as bar: + with click.progressbar(ids, label=f'Updating {total} alerts') as bar: for id in bar: client.update_attributes(id, dict(a.split('=') for a in attributes)) diff --git a/alertaclient/commands/cmd_user.py b/alertaclient/commands/cmd_user.py index 532a757..491853e 100644 --- a/alertaclient/commands/cmd_user.py +++ b/alertaclient/commands/cmd_user.py @@ -56,7 +56,7 @@ def cli(obj, id, name, email, password, status, roles, text, email_verified, gro roles=roles, attributes=None, text=text, email_verified=email_verified ) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(user.id) else: @@ -70,6 +70,6 @@ def cli(obj, id, name, email, password, status, roles, text, email_verified, gro roles=roles, attributes=None, text=text, email_verified=email_verified ) except Exception as e: - click.echo('ERROR: {}'.format(e), err=True) + click.echo(f'ERROR: {e}', err=True) sys.exit(1) click.echo(user.id) diff --git a/alertaclient/commands/cmd_version.py b/alertaclient/commands/cmd_version.py index b576353..60f639e 100644 --- a/alertaclient/commands/cmd_version.py +++ b/alertaclient/commands/cmd_version.py @@ -11,7 +11,7 @@ def cli(ctx, obj): """Show Alerta server and client versions.""" client = obj['client'] click.echo('alerta {}'.format(client.mgmt_status()['version'])) - click.echo('alerta client {}'.format(client_version)) - click.echo('requests {}'.format(requests_version)) - click.echo('click {}'.format(click.__version__)) + click.echo(f'alerta client {client_version}') + click.echo(f'requests {requests_version}') + click.echo(f'click {click.__version__}') ctx.exit() diff --git a/alertaclient/commands/cmd_whoami.py b/alertaclient/commands/cmd_whoami.py index 7feef78..f3861ac 100644 --- a/alertaclient/commands/cmd_whoami.py +++ b/alertaclient/commands/cmd_whoami.py @@ -12,6 +12,6 @@ def cli(obj, show_userinfo): for k, v in userinfo.items(): if isinstance(v, list): v = ', '.join(v) - click.echo('{:20}: {}'.format(k, v)) + click.echo(f'{k:20}: {v}') else: click.echo(userinfo['preferred_username']) diff --git a/alertaclient/config.py b/alertaclient/config.py index 9c46f38..43953d1 100644 --- a/alertaclient/config.py +++ b/alertaclient/config.py @@ -64,8 +64,8 @@ def get_remote_config(self, endpoint=None): r.raise_for_status() remote_config = r.json() except requests.RequestException as e: - raise ClientException('Failed to get config from {}. Reason: {}'.format(config_url, e)) + raise ClientException(f'Failed to get config from {config_url}. Reason: {e}') except json.decoder.JSONDecodeError: - raise ClientException('Failed to get config from {}: Reason: not a JSON object'.format(config_url)) + raise ClientException(f'Failed to get config from {config_url}: Reason: not a JSON object') self.options = {**remote_config, **self.options} diff --git a/alertaclient/models/blackout.py b/alertaclient/models/blackout.py index 8323dea..856f0ce 100644 --- a/alertaclient/models/blackout.py +++ b/alertaclient/models/blackout.py @@ -129,9 +129,9 @@ def tabular(self, timezone=None): 'customer': self.customer, 'startTime': DateTime.localtime(self.start_time, timezone), 'endTime': DateTime.localtime(self.end_time, timezone), - 'duration': '{}s'.format(self.duration), + 'duration': f'{self.duration}s', 'status': self.status, - 'remaining': '{}s'.format(self.remaining), + 'remaining': f'{self.remaining}s', 'user': self.user, 'createTime': DateTime.localtime(self.create_time, timezone), 'text': self.text diff --git a/alertaclient/models/enums.py b/alertaclient/models/enums.py index 46eb45c..5eff97b 100644 --- a/alertaclient/models/enums.py +++ b/alertaclient/models/enums.py @@ -55,7 +55,7 @@ def from_str(action: str, resource: str = None): :return: Scope """ if resource: - return Scope('{}:{}'.format(action, resource)) + return Scope(f'{action}:{resource}') else: return Scope(action) diff --git a/alertaclient/models/heartbeat.py b/alertaclient/models/heartbeat.py index 4b8f56f..1f13809 100644 --- a/alertaclient/models/heartbeat.py +++ b/alertaclient/models/heartbeat.py @@ -70,8 +70,8 @@ def tabular(self, timezone=None): 'createTime': DateTime.localtime(self.create_time, timezone), 'receiveTime': DateTime.localtime(self.receive_time, timezone), 'since': self.since, - 'timeout': '{}s'.format(self.timeout), - 'latency': '{:.0f}ms'.format(self.latency), - 'maxLatency': '{}ms'.format(self.max_latency), + 'timeout': f'{self.timeout}s', + 'latency': f'{self.latency:.0f}ms', + 'maxLatency': f'{self.max_latency}ms', 'status': self.status } diff --git a/alertaclient/top.py b/alertaclient/top.py index c3756fe..702dc51 100644 --- a/alertaclient/top.py +++ b/alertaclient/top.py @@ -84,7 +84,7 @@ def update(self): # draw header self._addstr(0, 0, self.client.endpoint, curses.A_BOLD) - self._addstr(0, 'C', 'alerta {}'.format(version), curses.A_BOLD) + self._addstr(0, 'C', f'alerta {version}', curses.A_BOLD) self._addstr(0, 'R', '{}'.format(now.strftime('%H:%M:%S %d/%m/%y')), curses.A_BOLD) # TODO - draw bars diff --git a/alertaclient/utils.py b/alertaclient/utils.py index 12549d7..9577073 100644 --- a/alertaclient/utils.py +++ b/alertaclient/utils.py @@ -50,7 +50,7 @@ def action_progressbar(client, action, ids, label, text=None, timeout=None): def show_skipped(id): if not id and skipped: - return '(skipped {})'.format(skipped) + return f'(skipped {skipped})' with click.progressbar(ids, label=label, show_eta=True, item_show_func=show_skipped) as bar: for id in bar: @@ -62,4 +62,4 @@ def show_skipped(id): def origin(): prog = os.path.basename(sys.argv[0]) - return '{}/{}'.format(prog, platform.uname()[1]) + return f'{prog}/{platform.uname()[1]}' From 269d5177bdd47603a5dae420ddc30431139eeff1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Sep 2021 21:51:24 +0200 Subject: [PATCH 27/90] Bump click from 7.1.2 to 8.0.1 (#276) Bumps [click](https://github.com/pallets/click) from 7.1.2 to 8.0.1. - [Release notes](https://github.com/pallets/click/releases) - [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/click/compare/7.1.2...8.0.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8bc3bb9..6b89d4c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Click==7.1.2 +Click==8.0.1 pytz==2021.1 PyYAML==5.4.1 requests==2.25.1 From 6c5c2bff583e3747c8fcc9f0cde966dd70afa743 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Sep 2021 21:51:35 +0200 Subject: [PATCH 28/90] Bump requests-hawk from 1.1.0 to 1.1.1 (#278) Bumps [requests-hawk](https://github.com/mozilla-services/requests-hawk) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/mozilla-services/requests-hawk/releases) - [Changelog](https://github.com/mozilla-services/requests-hawk/blob/master/CHANGES.txt) - [Commits](https://github.com/mozilla-services/requests-hawk/compare/1.1.0...1.1.1) --- updated-dependencies: - dependency-name: requests-hawk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6b89d4c..f9b57bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,5 @@ Click==8.0.1 pytz==2021.1 PyYAML==5.4.1 requests==2.25.1 -requests-hawk==1.1.0 +requests-hawk==1.1.1 tabulate==0.8.9 From 16c4d936b515d298add8450931b6c0aeae948188 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Sep 2021 21:51:47 +0200 Subject: [PATCH 29/90] Bump pre-commit from 2.12.1 to 2.15.0 (#287) Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.12.1 to 2.15.0. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.12.1...v2.15.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c0a7720..542447d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ mypy==0.812 -pre-commit==2.12.1 +pre-commit==2.15.0 pylint==2.7.4 pytest-cov pytest>=5.4.3 From 1e40eba3d8d95f9c8ce39c523bcd93edd89da931 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Sep 2021 22:11:01 +0200 Subject: [PATCH 30/90] Bump pylint from 2.7.4 to 2.11.1 (#288) Bumps [pylint](https://github.com/PyCQA/pylint) from 2.7.4 to 2.11.1. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.7.4...v2.11.1) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 542447d..e67c88a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ mypy==0.812 pre-commit==2.15.0 -pylint==2.7.4 +pylint==2.11.1 pytest-cov pytest>=5.4.3 python-dotenv From c0e65ee13669a1a000d0bd20a76f67b2401f4258 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 07:23:25 +0200 Subject: [PATCH 31/90] Bump requests from 2.25.1 to 2.26.0 (#289) Bumps [requests](https://github.com/psf/requests) from 2.25.1 to 2.26.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.26.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9b57bd..5dce97d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Click==8.0.1 pytz==2021.1 PyYAML==5.4.1 -requests==2.25.1 +requests==2.26.0 requests-hawk==1.1.1 tabulate==0.8.9 From 1714270e8e032c0f35b0a7adcd37624a8fa20341 Mon Sep 17 00:00:00 2001 From: Matthieu Serrepuy Date: Mon, 18 Oct 2021 22:07:12 +0200 Subject: [PATCH 32/90] feat: Add a --alert flag to alert keys to alert on expired and expiring key (#274) --- alertaclient/commands/cmd_keys.py | 68 ++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/alertaclient/commands/cmd_keys.py b/alertaclient/commands/cmd_keys.py index d740eb1..bdcf18b 100644 --- a/alertaclient/commands/cmd_keys.py +++ b/alertaclient/commands/cmd_keys.py @@ -1,12 +1,19 @@ import json +from datetime import datetime, timedelta import click from tabulate import tabulate +from alertaclient.utils import origin + @click.command('keys', short_help='List API keys') +@click.option('--alert', is_flag=True, help='Alert on expiring and expired keys') +@click.option('--maxage', metavar='DAYS', default=7, type=int, help='Max remaining days before alerting') +@click.option('--timeout', metavar='SECONDS', default=86400, type=int, help='Seconds before expired key alerts will be expired') +@click.option('--severity', '-s', metavar='SEVERITY', default='warning', help='Severity for expiring and expired alerts') @click.pass_obj -def cli(obj): +def cli(obj, alert, maxage, severity, timeout): """List API keys.""" client = obj['client'] @@ -20,3 +27,62 @@ def cli(obj): 'expireTime': 'EXPIRES', 'count': 'COUNT', 'lastUsedTime': 'LAST USED', 'customer': 'CUSTOMER' } click.echo(tabulate([k.tabular(timezone) for k in client.get_keys()], headers=headers, tablefmt=obj['output'])) + + if alert: + keys = r['keys'] + service = ['Alerta'] + group = 'System' + environment = 'Production' + with click.progressbar(keys, label=f'Analysing {len(keys)} keys') as bar: + for b in bar: + if b['status'] == 'expired': + client.send_alert( + resource=b['id'], + event='ApiKeyExpired', + environment=environment, + severity=severity, + correlate=['ApiKeyExpired', 'ApiKeyExpiring', 'ApiKeyOK'], + service=service, + group=group, + value='Expired', + text=f"Key expired on {b['expireTime']}", + origin=origin(), + type='apiKeyAlert', + timeout=timeout, + customer=b['customer'] + ) + elif b['status'] == 'active': + expiration = datetime.fromisoformat(b['expireTime'].split('.')[0]) + remaining_validity = expiration - datetime.now() + if remaining_validity < timedelta(days=maxage): + client.send_alert( + resource=b['id'], + event='ApiKeyExpiring', + environment=environment, + severity=severity, + correlate=['ApiKeyExpired', 'ApiKeyExpiring', 'ApiKeyOK'], + service=service, + group=group, + value=str(remaining_validity), + text=f"Key is active and expires on {b['expireTime']}", + origin=origin(), + type='apiKeyAlert', + timeout=timeout, + customer=b['customer'] + ) + else: + client.send_alert( + resource=b['id'], + event='ApiKeyOK', + environment=environment, + severity='ok', + correlate=['ApiKeyExpired', 'ApiKeyExpiring', 'ApiKeyOK'], + service=service, + group=group, + value=str(remaining_validity), + text=f"Key is active and expires on {b['expireTime']}", + origin=origin(), + type='apiKeyAlert', + timeout=timeout, + customer=b['customer'] + ) From 597951e5d06843d5fe7954de4e693c7ad1c3054a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Nov 2021 21:11:22 +0100 Subject: [PATCH 33/90] Bump pytz from 2021.1 to 2021.3 (#290) Bumps [pytz](https://github.com/stub42/pytz) from 2021.1 to 2021.3. - [Release notes](https://github.com/stub42/pytz/releases) - [Commits](https://github.com/stub42/pytz/compare/release_2021.1...release_2021.3) --- updated-dependencies: - dependency-name: pytz dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5dce97d..1014736 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Click==8.0.1 -pytz==2021.1 +pytz==2021.3 PyYAML==5.4.1 requests==2.26.0 requests-hawk==1.1.1 From 0a461eefb32a487c4914353d0196deccf0a5d95a Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Fri, 19 Nov 2021 23:54:15 +0100 Subject: [PATCH 34/90] Add release workflow --- .github/workflows/release.yml | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..36dae2f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,48 @@ +name: release + +on: + push: + tags: + - 'v*' + +env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Lint + id: lint + run: | + make pylint + make mypy + - name: Test + id: test + run: make test + + - uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + + release: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + id: build + run: make build + - name: Publish to PyPI + id: publish + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + + - uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} \ No newline at end of file From 23f785d4168cd2415f4334969c6eedfddebdff65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Nov 2021 23:55:26 +0100 Subject: [PATCH 35/90] Bump click from 8.0.1 to 8.0.3 (#291) Bumps [click](https://github.com/pallets/click) from 8.0.1 to 8.0.3. - [Release notes](https://github.com/pallets/click/releases) - [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/click/compare/8.0.1...8.0.3) --- updated-dependencies: - dependency-name: click dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1014736..d95fb45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Click==8.0.1 +Click==8.0.3 pytz==2021.3 PyYAML==5.4.1 requests==2.26.0 From a5724403a4d3bfdae60ceb9f1a8ba6cc44afaaf0 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Fri, 19 Nov 2021 23:57:23 +0100 Subject: [PATCH 36/90] Bump version 8.5.0 -> 8.5.1 --- VERSION | 2 +- alertaclient/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 6d28907..f9c71a5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.5.0 +8.5.1 diff --git a/alertaclient/version.py b/alertaclient/version.py index 1d71024..19d0d0d 100644 --- a/alertaclient/version.py +++ b/alertaclient/version.py @@ -1 +1 @@ -__version__ = '8.5.0' +__version__ = '8.5.1' From 421c6f7066e44f13744c8cc1cec57f5d584d685d Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 21 Nov 2021 17:33:32 +0100 Subject: [PATCH 37/90] Update release pipeline (#294) --- .github/workflows/release.yml | 9 ++++--- .pre-commit-config.yaml | 5 +++- Makefile | 49 ++++++++++++++++------------------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36dae2f..0567b3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,11 +13,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - name: Install + id: install + run: make install - name: Lint id: lint run: | - make pylint - make mypy + make format + make lint - name: Test id: test run: make test @@ -45,4 +48,4 @@ jobs: - uses: act10ns/slack@v1 with: status: ${{ job.status }} - steps: ${{ toJson(steps) }} \ No newline at end of file + steps: ${{ toJson(steps) }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb5d3c6..19475d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,6 @@ repos: - id: debug-statements - id: double-quote-string-fixer - id: end-of-file-fixer - - id: flake8 - id: fix-encoding-pragma args: ['--remove'] - id: pretty-format-json @@ -26,6 +25,10 @@ repos: args: ['--django'] - id: requirements-txt-fixer - id: trailing-whitespace +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 - repo: https://github.com/asottile/pyupgrade rev: v1.27.0 hooks: diff --git a/Makefile b/Makefile index 9c71260..91ae397 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,11 @@ VENV=venv PYTHON=$(VENV)/bin/python PIP=$(VENV)/bin/pip --disable-pip-version-check -PYLINT=$(VENV)/bin/pylint +FLAKE8=$(VENV)/bin/flake8 MYPY=$(VENV)/bin/mypy -BLACK=$(VENV)/bin/black TOX=$(VENV)/bin/tox PYTEST=$(VENV)/bin/pytest +DOCKER_COMPOSE=docker-compose PRE_COMMIT=$(VENV)/bin/pre-commit WHEEL=$(VENV)/bin/wheel TWINE=$(VENV)/bin/twine @@ -31,23 +31,20 @@ all: help $(PIP): python3 -m venv $(VENV) -$(PYLINT): $(PIP) - $(PIP) install pylint +$(FLAKE8): $(PIP) + $(PIP) install flake8==4.0.1 $(MYPY): $(PIP) - $(PIP) install mypy - -$(BLACK): $(PIP) - $(PIP) install black + $(PIP) install mypy==0.812 $(TOX): $(PIP) $(PIP) install tox $(PYTEST): $(PIP) - $(PIP) install pytest + $(PIP) install pytest==6.2.5 pytest-cov==3.0.0 $(PRE_COMMIT): $(PIP) - $(PIP) install pre-commit + $(PIP) install pre-commit==2.15.0 $(WHEEL): $(PIP) $(PIP) install wheel @@ -59,19 +56,18 @@ ifdef TOXENV toxparams?=-e $(TOXENV) endif -## format - Code formatter. -format: $(BLACK) - $(BLACK) -l120 -S -v $(PROJECT) - -## lint - Lint and type checking. -lint: $(PYLINT) $(MYPY) $(BLACK) - $(PYLINT) --rcfile pylintrc $(PROJECT) - $(MYPY) $(PROJECT)/ - $(BLACK) -l120 -S --check -v $(PROJECT) || true +## install - Install dependencies. +install: $(PIP) + $(PIP) install -r requirements.txt ## hooks - Run pre-commit hooks. hooks: $(PRE_COMMIT) - $(PRE_COMMIT) run -a + $(PRE_COMMIT) run --all-files --show-diff-on-failure + +## lint - Lint and type checking. +lint: $(FLAKE8) $(MYPY) + $(FLAKE8) $(PROJECT)/ + $(MYPY) $(PROJECT)/ ## test - Run all tests. test: test.unit test.integration @@ -82,11 +78,11 @@ test.unit: $(TOX) $(PYTEST) ## test.integration - Run integration tests. test.integration: - docker-compose -f docker-compose.ci.yaml rm --stop --force - docker-compose -f docker-compose.ci.yaml pull - docker-compose -f docker-compose.ci.yaml build sut - docker-compose -f docker-compose.ci.yaml up --exit-code-from sut - docker-compose -f docker-compose.ci.yaml rm --stop --force + $(DOCKER_COMPOSE) -f docker-compose.ci.yaml rm --stop --force + $(DOCKER_COMPOSE) -f docker-compose.ci.yaml pull + $(DOCKER_COMPOSE) -f docker-compose.ci.yaml build sut + $(DOCKER_COMPOSE) -f docker-compose.ci.yaml up --exit-code-from sut + $(DOCKER_COMPOSE) -f docker-compose.ci.yaml rm --stop --force ## run - Run application. run: @@ -102,7 +98,8 @@ build: $(PIP) $(WHEEL) $(PKG_SDIST) $(PKG_WHEEL) $(PKG_SDIST): $(PYTHON) setup.py sdist -$(PKG_WHEEL): + +$(PKG_WHEEL): $(WHEEL) $(PYTHON) setup.py bdist_wheel ## upload - Upload package to PyPI. From df2f5e2dbd71c1f0188b65a52be70b126fc5f73f Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sun, 21 Nov 2021 17:34:02 +0100 Subject: [PATCH 38/90] No format --- .github/workflows/release.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0567b3f..ba0e3b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,9 +18,7 @@ jobs: run: make install - name: Lint id: lint - run: | - make format - make lint + run: make lint - name: Test id: test run: make test From 9f5a8c4ed996d8cf134ef28338316030a8353f29 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Mon, 6 Dec 2021 19:14:24 +0100 Subject: [PATCH 39/90] build(docker): standardise docker build pipeline --- .github/workflows/docker.yml | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9917ac0..be66b3a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,16 +1,20 @@ -name: Docker Build and Push +name: Docker on: push: branches: [ master, release/* ] + tags: [ '**' ] + +env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} jobs: build: + name: Build & Push runs-on: ubuntu-latest env: - REPOSITORY_URL: docker.pkg.github.com - IMAGE_NAME: ${{ github.repository }}/alerta-cli - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + REPOSITORY_URL: ghcr.io + IMAGE_NAME: ${{ github.repository_owner }}/alerta-cli steps: - name: Checkout uses: actions/checkout@v2 @@ -22,16 +26,18 @@ jobs: run: >- docker build -t $IMAGE_NAME + -t $REPOSITORY_URL/$IMAGE_NAME:$(cat VERSION) -t $REPOSITORY_URL/$IMAGE_NAME:${{ steps.vars.outputs.SHORT_COMMIT_ID }} -t $REPOSITORY_URL/$IMAGE_NAME:latest . - name: Docker Login - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - run: docker login $REPOSITORY_URL --username "$DOCKER_USERNAME" --password "$DOCKER_PASSWORD" + uses: docker/login-action@v1 + with: + registry: ${{ env.REPOSITORY_URL }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Publish Image id: docker-push - run: docker push $REPOSITORY_URL/$IMAGE_NAME + run: docker push --all-tags $REPOSITORY_URL/$IMAGE_NAME - uses: act10ns/slack@v1 with: From 2a58ffd184a97338b81a002c95cd60cba6f58397 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 09:42:37 +0100 Subject: [PATCH 40/90] Update github actions workflows --- .github/workflows/docker.yml | 11 +-- .github/workflows/release.yml | 73 +++++++++++++------ .../workflows/{github-ci.yml => tests.yml} | 22 +++--- .pre-commit-config.yaml | 2 +- Makefile | 57 +++++++-------- requirements-dev.txt | 8 +- 6 files changed, 98 insertions(+), 75 deletions(-) rename .github/workflows/{github-ci.yml => tests.yml} (79%) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index be66b3a..741e5f0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,20 +17,17 @@ jobs: IMAGE_NAME: ${{ github.repository_owner }}/alerta-cli steps: - name: Checkout - uses: actions/checkout@v2 - - name: Variables - id: vars - run: echo "::set-output name=SHORT_COMMIT_ID::$(git rev-parse --short HEAD)" + uses: actions/checkout@v3 - name: Build image id: docker-build run: >- docker build -t $IMAGE_NAME -t $REPOSITORY_URL/$IMAGE_NAME:$(cat VERSION) - -t $REPOSITORY_URL/$IMAGE_NAME:${{ steps.vars.outputs.SHORT_COMMIT_ID }} + -t $REPOSITORY_URL/$IMAGE_NAME:$(git rev-parse --short HEAD) -t $REPOSITORY_URL/$IMAGE_NAME:latest . - name: Docker Login - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ${{ env.REPOSITORY_URL }} username: ${{ github.actor }} @@ -39,7 +36,7 @@ jobs: id: docker-push run: docker push --all-tags $REPOSITORY_URL/$IMAGE_NAME - - uses: act10ns/slack@v1 + - uses: act10ns/slack@v2 with: status: ${{ job.status }} steps: ${{ toJson(steps) }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba0e3b5..940b8dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,49 +1,80 @@ -name: release +name: Release on: push: - tags: - - 'v*' + tags: [ 'v*' ] + branches: [ develop ] env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} jobs: test: + name: Test runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v2 - - name: Install - id: install - run: make install - - name: Lint - id: lint - run: make lint - - name: Test + - uses: actions/checkout@v3 + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Install dependencies + id: install-deps + run: | + python3 -m pip install --upgrade pip + pip install flake8 pytest + pip install -r requirements.txt + pip install -r requirements-dev.txt + pip install . + - name: Pre-commit hooks + id: hooks + run: | + pre-commit run -a --show-diff-on-failure + - name: Test with pytest id: test - run: make test - - - uses: act10ns/slack@v1 + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/alerta + run: | + pytest --cov=alerta tests/*.py + - uses: act10ns/slack@v2 with: status: ${{ job.status }} steps: ${{ toJson(steps) }} + if: failure() release: + name: Publish needs: test runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: 3.11 - name: Build id: build - run: make build + run: | + python3 -m pip install --upgrade build + python3 -m build - name: Publish to PyPI id: publish - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + python3 -m pip install --upgrade twine + python3 -m twine check dist/* + python3 -m twine upload --verbose dist/* - - uses: act10ns/slack@v1 + - uses: act10ns/slack@v2 with: status: ${{ job.status }} steps: ${{ toJson(steps) }} + + - name: Test Install + run: | + python3 -m pip install --upgrade alerta-server + python3 -m pip freeze diff --git a/.github/workflows/github-ci.yml b/.github/workflows/tests.yml similarity index 79% rename from .github/workflows/github-ci.yml rename to .github/workflows/tests.yml index 364b01c..d55e54d 100644 --- a/.github/workflows/github-ci.yml +++ b/.github/workflows/tests.yml @@ -1,27 +1,26 @@ -name: CI Tests +name: Tests on: push: - branches: [ master, release/* ] pull_request: branches: [ master ] +env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + REPOSITORY_URL: docker.pkg.github.com + jobs: test: runs-on: ubuntu-latest - env: - REPOSITORY_URL: docker.pkg.github.com - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ['3.8', '3.11'] steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -57,8 +56,7 @@ jobs: docker-compose -f docker-compose.ci.yaml build sut docker-compose -f docker-compose.ci.yaml up --exit-code-from sut docker-compose -f docker-compose.ci.yaml rm --stop --force - - - uses: act10ns/slack@v1 + - uses: act10ns/slack@v2 with: status: ${{ job.status }} steps: ${{ toJson(steps) }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19475d8..39ea43c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: args: ['--django'] - id: requirements-txt-fixer - id: trailing-whitespace -- repo: https://gitlab.com/pycqa/flake8 +- repo: https://github.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 diff --git a/Makefile b/Makefile index 91ae397..e089f89 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ #!make VENV=venv -PYTHON=$(VENV)/bin/python +PYTHON=$(VENV)/bin/python3 PIP=$(VENV)/bin/pip --disable-pip-version-check FLAKE8=$(VENV)/bin/flake8 MYPY=$(VENV)/bin/mypy @@ -9,6 +9,7 @@ TOX=$(VENV)/bin/tox PYTEST=$(VENV)/bin/pytest DOCKER_COMPOSE=docker-compose PRE_COMMIT=$(VENV)/bin/pre-commit +BUILD=$(VENV)/bin/build WHEEL=$(VENV)/bin/wheel TWINE=$(VENV)/bin/twine GIT=git @@ -18,46 +19,48 @@ GIT=git -include .env .env.local .env.*.local ifndef PROJECT -$(error PROJECT is not set) + $(error PROJECT is not set) endif +PYPI_REPOSITORY ?= pypi VERSION=$(shell cut -d "'" -f 2 $(PROJECT)/version.py) -PKG_SDIST=dist/*-$(VERSION).tar.gz -PKG_WHEEL=dist/*-$(VERSION)-*.whl - all: help -$(PIP): +$(VENV): python3 -m venv $(VENV) -$(FLAKE8): $(PIP) - $(PIP) install flake8==4.0.1 +$(FLAKE8): $(VENV) + $(PIP) install flake8 -$(MYPY): $(PIP) - $(PIP) install mypy==0.812 +$(MYPY): $(VENV) + $(PIP) install mypy -$(TOX): $(PIP) +$(TOX): $(VENV) $(PIP) install tox -$(PYTEST): $(PIP) - $(PIP) install pytest==6.2.5 pytest-cov==3.0.0 +$(PYTEST): $(VENV) + $(PIP) install pytest pytest-cov + +$(PRE_COMMIT): $(VENV) + $(PIP) install pre-commit + $(PRE_COMMIT) install -$(PRE_COMMIT): $(PIP) - $(PIP) install pre-commit==2.15.0 +$(BUILD): $(VENV) + $(PIP) install --upgrade build -$(WHEEL): $(PIP) - $(PIP) install wheel +$(WHEEL): $(VENV) + $(PIP) install --upgrade wheel -$(TWINE): $(PIP) - $(PIP) install wheel twine +$(TWINE): $(VENV) + $(PIP) install --upgrade wheel twine ifdef TOXENV -toxparams?=-e $(TOXENV) + toxparams?=-e $(TOXENV) endif ## install - Install dependencies. -install: $(PIP) +install: $(VENV) $(PIP) install -r requirements.txt ## hooks - Run pre-commit hooks. @@ -94,17 +97,13 @@ tag: $(GIT) push --tags ## build - Build package. -build: $(PIP) $(WHEEL) $(PKG_SDIST) $(PKG_WHEEL) - -$(PKG_SDIST): - $(PYTHON) setup.py sdist - -$(PKG_WHEEL): $(WHEEL) - $(PYTHON) setup.py bdist_wheel +build: $(BUILD) + $(PYTHON) -m build ## upload - Upload package to PyPI. upload: $(TWINE) - $(TWINE) upload dist/* + $(TWINE) check dist/* + $(TWINE) upload --repository $(PYPI_REPOSITORY) --verbose dist/* ## clean - Clean source. clean: diff --git a/requirements-dev.txt b/requirements-dev.txt index e67c88a..1723f37 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,9 +1,7 @@ -mypy==0.812 -pre-commit==2.15.0 -pylint==2.11.1 +mypy==1.0.1 +pre-commit==2.20.0 +pylint==2.16.2 pytest-cov pytest>=5.4.3 python-dotenv requests_mock -twine -wheel From 47bff1cc865f13cf523042cbbba6911d6e276f93 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 09:49:35 +0100 Subject: [PATCH 41/90] Update supported python versions --- .github/workflows/tests.yml | 2 +- setup.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d55e54d..259bb15 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - python-version: ['3.8', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 diff --git a/setup.py b/setup.py index 19df3fb..9a71f13 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def read(filename): url='https://github.com/guardian/python-alerta', license='Apache License 2.0', author='Nick Satterly', - author_email='nick.satterly@gmail.com', + author_email='nfsatterly@gmail.com', packages=setuptools.find_packages(exclude=['tests']), install_requires=[ 'Click', @@ -42,12 +42,12 @@ def read(filename): 'Intended Audience :: System Administrators', 'Intended Audience :: Telecommunications Industry', 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.6', 'Topic :: System :: Monitoring', 'Topic :: Software Development :: Libraries :: Python Modules' ], - python_requires='>=3.6' + python_requires='>=3.8' ) From 95219b459379fabeb2c1f70eec3754146627bfa2 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 09:50:58 +0100 Subject: [PATCH 42/90] Skip type checking for now --- .github/workflows/tests.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 259bb15..cf539a0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,10 +40,6 @@ jobs: run: | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics flake8 . --count --exit-zero --max-complexity=50 --max-line-length=127 --statistics - - name: Type Check - id: types - run: | - python -m mypy alertaclient/ - name: Test with pytest id: unit-test env: From 1093cdc1d9e0a15fe6b1e65a80823944a413dd56 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 10:46:59 +0100 Subject: [PATCH 43/90] Fix integration tests --- docker-compose.ci.yaml | 27 +++++---------------------- tests/integration/test_alerts.py | 2 +- tests/integration/test_blackouts.py | 2 +- tests/integration/test_customers.py | 2 +- tests/integration/test_groups.py | 2 +- tests/integration/test_heartbeats.py | 2 +- tests/integration/test_history.py | 2 +- tests/integration/test_keys.py | 2 +- tests/integration/test_notes.py | 2 +- tests/integration/test_permissions.py | 2 +- tests/integration/test_users.py | 2 +- 11 files changed, 15 insertions(+), 32 deletions(-) diff --git a/docker-compose.ci.yaml b/docker-compose.ci.yaml index f0ddd12..e20abd2 100644 --- a/docker-compose.ci.yaml +++ b/docker-compose.ci.yaml @@ -1,8 +1,8 @@ version: '3.7' services: - api: - image: ghcr.io/alerta/alerta-api:latest + alerta: + image: alerta/alerta-web ports: - "8080:8080" depends_on: @@ -14,34 +14,17 @@ services: - ADMIN_USERS=admin@alerta.io,devops@alerta.io #default password: alerta - ADMIN_KEY=demo-key # assigned to first user in ADMIN_USERS list # - PLUGINS=reject,blackout,normalise,enhance - networks: - net: - aliases: - - api db: - image: postgres:9.6 - volumes: - - /var/lib/postgresql/data + image: postgres:14 environment: - POSTGRES_DB=monitoring - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres restart: always - networks: - net: - aliases: - - db sut: build: . depends_on: - - api - command: ["./wait-for-it.sh", "api:8080", "-t", "60", "--", "pytest", "tests/integration/"] - networks: - net: - aliases: - - sut - -networks: - net: {} + - alerta + command: ["./wait-for-it.sh", "alerta:8080", "-t", "60", "--", "pytest", "tests/integration/"] diff --git a/tests/integration/test_alerts.py b/tests/integration/test_alerts.py index 7c1a439..4b4989c 100644 --- a/tests/integration/test_alerts.py +++ b/tests/integration/test_alerts.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_alert(self): id, alert, message = self.client.send_alert( diff --git a/tests/integration/test_blackouts.py b/tests/integration/test_blackouts.py index e6d2264..517eb28 100644 --- a/tests/integration/test_blackouts.py +++ b/tests/integration/test_blackouts.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_blackout(self): blackout = self.client.create_blackout( diff --git a/tests/integration/test_customers.py b/tests/integration/test_customers.py index aa713a2..819f4b3 100644 --- a/tests/integration/test_customers.py +++ b/tests/integration/test_customers.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_customer(self): customer = self.client.create_customer(customer='ACME Corp.', match='example.com') diff --git a/tests/integration/test_groups.py b/tests/integration/test_groups.py index 7c52988..6f091c3 100644 --- a/tests/integration/test_groups.py +++ b/tests/integration/test_groups.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_group(self): group = self.client.create_group(name='myGroup', text='test group') diff --git a/tests/integration/test_heartbeats.py b/tests/integration/test_heartbeats.py index 1e69bae..153d756 100644 --- a/tests/integration/test_heartbeats.py +++ b/tests/integration/test_heartbeats.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_heartbeat(self): hb = self.client.heartbeat(origin='app/web01', timeout=10, tags=['london', 'linux']) diff --git a/tests/integration/test_history.py b/tests/integration/test_history.py index 962ef51..7177c3f 100644 --- a/tests/integration/test_history.py +++ b/tests/integration/test_history.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_alert(self): id, alert, message = self.client.send_alert( diff --git a/tests/integration/test_keys.py b/tests/integration/test_keys.py index 639c7c2..5aa1b08 100644 --- a/tests/integration/test_keys.py +++ b/tests/integration/test_keys.py @@ -7,7 +7,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_key(self): api_key = self.client.create_key( diff --git a/tests/integration/test_notes.py b/tests/integration/test_notes.py index f71b6ae..12962ea 100644 --- a/tests/integration/test_notes.py +++ b/tests/integration/test_notes.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_notes(self): # add tests here when /notes endpoints are created diff --git a/tests/integration/test_permissions.py b/tests/integration/test_permissions.py index 647ef73..4b358d9 100644 --- a/tests/integration/test_permissions.py +++ b/tests/integration/test_permissions.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_permission(self): perm = self.client.create_perm(role='websys', scopes=['admin:users', 'admin:keys', 'write']) diff --git a/tests/integration/test_users.py b/tests/integration/test_users.py index 22713eb..f951111 100644 --- a/tests/integration/test_users.py +++ b/tests/integration/test_users.py @@ -6,7 +6,7 @@ class AlertTestCase(unittest.TestCase): def setUp(self): - self.client = Client(endpoint='http://api:8080', key='demo-key') + self.client = Client(endpoint='http://alerta:8080/api', key='demo-key') def test_user(self): users = self.client.get_users() From c2a0df281da8bbaeb864e4414794ae8937c7d4ff Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 14:02:50 +0100 Subject: [PATCH 44/90] build(deps): Bump all python dependencies (#347) --- requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index d95fb45..737a551 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -Click==8.0.3 -pytz==2021.3 -PyYAML==5.4.1 -requests==2.26.0 +Click==8.1.3 +pytz==2022.7.1 +PyYAML==6.0 +requests==2.28.2 requests-hawk==1.1.1 -tabulate==0.8.9 +tabulate==0.9.0 From 89be3b0b5b5a87e762c6042a554ad386085049b8 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 14:06:24 +0100 Subject: [PATCH 45/90] Update copyright date --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0eb90d6..3d21369 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ License ------- Alerta monitoring system and console - Copyright 2012-2020 Nick Satterly + Copyright 2012-2023 Nick Satterly Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 3f769403ec1b20683df243f1332b16ad99fa53d6 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 14:07:15 +0100 Subject: [PATCH 46/90] Bump version 8.5.1 -> 8.5.2 --- CHANGELOG.md | 18 ++++++++++++++++++ VERSION | 2 +- alertaclient/version.py | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14173b2..e221bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## v8.5.2 (2023-03-18) + +### Refactor + +- convert formatted strings to f-strings (#272) + +## v8.5.1 (2021-11-21) + +### Feat + +- Add a --alert flag to alert keys to alert on expired and expiring key (#274) +- Add option to use custom value when creating API key (#270) + +### Refactor + +- convert formatted strings to f-strings (#272) +- assign api key directly (#271) + ## v8.5.0 (2021-04-18) ### Fix diff --git a/VERSION b/VERSION index f9c71a5..85e2cd5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.5.1 +8.5.2 diff --git a/alertaclient/version.py b/alertaclient/version.py index 19d0d0d..838c9cc 100644 --- a/alertaclient/version.py +++ b/alertaclient/version.py @@ -1 +1 @@ -__version__ = '8.5.1' +__version__ = '8.5.2' From 273a0c7a8826328b59b61534f1b1649cb7aeaed3 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 14:21:47 +0100 Subject: [PATCH 47/90] Fix github action Release workflow --- .github/workflows/release.yml | 4 +--- .github/workflows/tests.yml | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 940b8dd..876a14c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,10 +33,8 @@ jobs: pre-commit run -a --show-diff-on-failure - name: Test with pytest id: test - env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/alerta run: | - pytest --cov=alerta tests/*.py + pytest --cov=alertaclient tests/unit - uses: act10ns/slack@v2 with: status: ${{ job.status }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cf539a0..faee115 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,8 +42,6 @@ jobs: flake8 . --count --exit-zero --max-complexity=50 --max-line-length=127 --statistics - name: Test with pytest id: unit-test - env: - DATABASE_URL: postgres://postgres:postgres@localhost:5432/alerta run: | pytest --cov=alertaclient tests/unit - name: Integration Test From e338c5f7b1c1ebbd792af9da7ed24ad8be715a56 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 14:39:25 +0100 Subject: [PATCH 48/90] Fix release workflow test install --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 876a14c..1a8d9e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,5 +74,5 @@ jobs: - name: Test Install run: | - python3 -m pip install --upgrade alerta-server + python3 -m pip install --upgrade alerta python3 -m pip freeze From e2d7e1367f9ecda3df52ee19941dbcf336c4b8c1 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Sat, 18 Mar 2023 14:55:05 +0100 Subject: [PATCH 49/90] Fix release workflow trigger --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a8d9e0..2574b98 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,6 @@ name: Release on: push: tags: [ 'v*' ] - branches: [ develop ] env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From c9ad80eb7bfa8e64656edf2109e12ab8e910e5f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:52:53 +0200 Subject: [PATCH 50/90] Bump pytz from 2022.7.1 to 2023.3 (#351) Bumps [pytz](https://github.com/stub42/pytz) from 2022.7.1 to 2023.3. - [Release notes](https://github.com/stub42/pytz/releases) - [Commits](https://github.com/stub42/pytz/compare/release_2022.7.1...release_2023.3) --- updated-dependencies: - dependency-name: pytz dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 737a551..c004d56 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Click==8.1.3 -pytz==2022.7.1 +pytz==2023.3 PyYAML==6.0 requests==2.28.2 requests-hawk==1.1.1 From b1a20013fd68007f98d29933a5d6da292cfb308d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:53:16 +0200 Subject: [PATCH 51/90] Bump pylint from 2.16.2 to 2.17.4 (#357) Bumps [pylint](https://github.com/PyCQA/pylint) from 2.16.2 to 2.17.4. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.2...v2.17.4) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1723f37..cf7659d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ mypy==1.0.1 pre-commit==2.20.0 -pylint==2.16.2 +pylint==2.17.4 pytest-cov pytest>=5.4.3 python-dotenv From 4ee223867d7a0c47a404dc41e99f6b00cdbe30ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:53:31 +0200 Subject: [PATCH 52/90] Bump mypy from 1.0.1 to 1.3.0 (#360) Bumps [mypy](https://github.com/python/mypy) from 1.0.1 to 1.3.0. - [Commits](https://github.com/python/mypy/compare/v1.0.1...v1.3.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cf7659d..81949c5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -mypy==1.0.1 +mypy==1.3.0 pre-commit==2.20.0 pylint==2.17.4 pytest-cov From b9644cb02f95842d0ccc64deba8ba42a19d6a446 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:53:53 +0200 Subject: [PATCH 53/90] Bump requests from 2.28.2 to 2.31.0 (#363) Bumps [requests](https://github.com/psf/requests) from 2.28.2 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.28.2...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c004d56..30ec799 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Click==8.1.3 pytz==2023.3 PyYAML==6.0 -requests==2.28.2 +requests==2.31.0 requests-hawk==1.1.1 tabulate==0.9.0 From 0e4fc331eae082e45ba692cdd52d592eb69fa268 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:58:30 +0200 Subject: [PATCH 54/90] Bump pre-commit from 2.20.0 to 3.3.3 (#364) Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.20.0 to 3.3.3. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.20.0...v3.3.3) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 81949c5..9adbb06 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ mypy==1.3.0 -pre-commit==2.20.0 +pre-commit==3.3.3 pylint==2.17.4 pytest-cov pytest>=5.4.3 From 08587ed18e4c26b760c288426abb02fc24da1689 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Tue, 9 Apr 2024 11:16:31 +0200 Subject: [PATCH 55/90] Bump github actions --- .github/workflows/docker.yml | 2 +- .github/workflows/release.yml | 8 ++++---- .github/workflows/tests.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 741e5f0..6b01fab 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,7 +17,7 @@ jobs: IMAGE_NAME: ${{ github.repository_owner }}/alerta-cli steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build image id: docker-build run: >- diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2574b98..c2884f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.11 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.11 - name: Install dependencies @@ -46,9 +46,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.11 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.11 - name: Build diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index faee115..9dac04e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,9 +18,9 @@ jobs: python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From e1f333636e073e6f529d2390c87ff9c0d572f326 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:36:50 +0200 Subject: [PATCH 56/90] Bump pyyaml from 6.0 to 6.0.1 (#370) Bumps [pyyaml](https://github.com/yaml/pyyaml) from 6.0 to 6.0.1. - [Changelog](https://github.com/yaml/pyyaml/blob/6.0.1/CHANGES) - [Commits](https://github.com/yaml/pyyaml/compare/6.0...6.0.1) --- updated-dependencies: - dependency-name: pyyaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 30ec799..35c8b82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Click==8.1.3 pytz==2023.3 -PyYAML==6.0 +PyYAML==6.0.1 requests==2.31.0 requests-hawk==1.1.1 tabulate==0.9.0 From 1856fe903f035064a970785741c230fa2da5a7d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:38:17 +0200 Subject: [PATCH 57/90] Bump requests-hawk from 1.1.1 to 1.2.1 (#366) Bumps [requests-hawk](https://github.com/mozilla-services/requests-hawk) from 1.1.1 to 1.2.1. - [Release notes](https://github.com/mozilla-services/requests-hawk/releases) - [Changelog](https://github.com/mozilla-services/requests-hawk/blob/master/CHANGES.txt) - [Commits](https://github.com/mozilla-services/requests-hawk/compare/1.1.1...1.2.1) --- updated-dependencies: - dependency-name: requests-hawk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 35c8b82..e3f0a93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,5 @@ Click==8.1.3 pytz==2023.3 PyYAML==6.0.1 requests==2.31.0 -requests-hawk==1.1.1 +requests-hawk==1.2.1 tabulate==0.9.0 From a9e96b114699376178c5db8a45f69ef39a492dc9 Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Thu, 11 Apr 2024 23:24:08 +0200 Subject: [PATCH 58/90] Bump pytz to latest --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e3f0a93..8af285a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Click==8.1.3 -pytz==2023.3 +pytz==2024.1 PyYAML==6.0.1 requests==2.31.0 requests-hawk==1.2.1 From 484baa168014ce57e9c951c45b7f36aa319c11c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 23:26:33 +0200 Subject: [PATCH 59/90] Bump click from 8.1.3 to 8.1.7 (#374) Bumps [click](https://github.com/pallets/click) from 8.1.3 to 8.1.7. - [Release notes](https://github.com/pallets/click/releases) - [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/click/compare/8.1.3...8.1.7) --- updated-dependencies: - dependency-name: click dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nick Satterly --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8af285a..e87975f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Click==8.1.3 +Click==8.1.7 pytz==2024.1 PyYAML==6.0.1 requests==2.31.0 From c27b52828721902637ca03c2803d87972ac87004 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 23:30:32 +0200 Subject: [PATCH 60/90] Bump mypy from 1.3.0 to 1.9.0 (#376) Bumps [mypy](https://github.com/python/mypy) from 1.3.0 to 1.9.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.3.0...1.9.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9adbb06..48e8896 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -mypy==1.3.0 +mypy==1.9.0 pre-commit==3.3.3 pylint==2.17.4 pytest-cov From f91f804b66385568d392f2b10459a4574bfd9411 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 23:30:50 +0200 Subject: [PATCH 61/90] Bump pylint from 2.17.4 to 3.1.0 (#375) Bumps [pylint](https://github.com/pylint-dev/pylint) from 2.17.4 to 3.1.0. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v2.17.4...v3.1.0) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 48e8896..45a588c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ mypy==1.9.0 pre-commit==3.3.3 -pylint==2.17.4 +pylint==3.1.0 pytest-cov pytest>=5.4.3 python-dotenv From b166ba7abb6f12fdd5123c2801462e28fef8660c Mon Sep 17 00:00:00 2001 From: Nick Satterly Date: Thu, 11 Apr 2024 23:36:00 +0200 Subject: [PATCH 62/90] Bump version 8.5.2 -> 8.5.3 --- README.md | 2 +- VERSION | 2 +- alertaclient/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d21369..8279418 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ License ------- Alerta monitoring system and console - Copyright 2012-2023 Nick Satterly + Copyright 2012-2024 Nick Satterly Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/VERSION b/VERSION index 85e2cd5..7dbcd5d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.5.2 +8.5.3 diff --git a/alertaclient/version.py b/alertaclient/version.py index 838c9cc..c0b8631 100644 --- a/alertaclient/version.py +++ b/alertaclient/version.py @@ -1 +1 @@ -__version__ = '8.5.2' +__version__ = '8.5.3' From 1ac3b783bb6cecf5557d2e0e4a63b475740035f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Aug 2024 09:34:40 +0200 Subject: [PATCH 63/90] Bump requests from 2.31.0 to 2.32.3 (#383) Bumps [requests](https://github.com/psf/requests) from 2.31.0 to 2.32.3. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.31.0...v2.32.3) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e87975f..ef5ebf5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Click==8.1.7 pytz==2024.1 PyYAML==6.0.1 -requests==2.31.0 +requests==2.32.3 requests-hawk==1.2.1 tabulate==0.9.0 From 58065eb596d43483a7776ce612071c7e05cff63f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Aug 2024 09:35:30 +0200 Subject: [PATCH 64/90] Bump pylint from 3.1.0 to 3.2.5 (#386) Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.1.0 to 3.2.5. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.1.0...v3.2.5) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 45a588c..35b855d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ mypy==1.9.0 pre-commit==3.3.3 -pylint==3.1.0 +pylint==3.2.5 pytest-cov pytest>=5.4.3 python-dotenv From d4e370673ccd2bc11416409989a6150de59ea05d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Aug 2024 09:35:55 +0200 Subject: [PATCH 65/90] Bump mypy from 1.9.0 to 1.10.1 (#385) Bumps [mypy](https://github.com/python/mypy) from 1.9.0 to 1.10.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 35b855d..d438b06 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -mypy==1.9.0 +mypy==1.10.1 pre-commit==3.3.3 pylint==3.2.5 pytest-cov From b5ce6274643035a45ea858894cd3d0ab6663fb33 Mon Sep 17 00:00:00 2001 From: sbgap Date: Mon, 23 Oct 2023 16:31:20 +0200 Subject: [PATCH 66/90] feat: add escalation rules --- alertaclient/api.py | 3 +++ alertaclient/commands/cmd_escalate.py | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 alertaclient/commands/cmd_escalate.py diff --git a/alertaclient/api.py b/alertaclient/api.py index 4ab8e47..634dd46 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -492,6 +492,9 @@ def housekeeping(self, expired_delete_hours=None, info_delete_hours=None): if response.status_code != 200: raise UnknownError(response.text) + def escalate(self): + self.http.session.get('escalate', auth=self.http.auth) + class ApiKeyAuth(AuthBase): diff --git a/alertaclient/commands/cmd_escalate.py b/alertaclient/commands/cmd_escalate.py new file mode 100644 index 0000000..7fb3a8d --- /dev/null +++ b/alertaclient/commands/cmd_escalate.py @@ -0,0 +1,9 @@ +import click + + +@click.command('escalate', short_help='Escalate alerts using escaltion rules') +@click.pass_obj +def cli(obj): + """Trigger escalation of alerts""" + client = obj['client'] + client.escalate() From f7ceac8a76697b959ce858767efe2bc10814c428 Mon Sep 17 00:00:00 2001 From: sbgap Date: Mon, 23 Oct 2023 17:29:13 +0200 Subject: [PATCH 67/90] fix: add / to escalate url --- alertaclient/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index 634dd46..9abcbe9 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -493,7 +493,7 @@ def housekeeping(self, expired_delete_hours=None, info_delete_hours=None): raise UnknownError(response.text) def escalate(self): - self.http.session.get('escalate', auth=self.http.auth) + self.http.get('/escalate', auth=self.http.auth) class ApiKeyAuth(AuthBase): From 07de9f5b0139e7c398a6327763044b1df749d987 Mon Sep 17 00:00:00 2001 From: sbgap Date: Mon, 23 Oct 2023 17:57:55 +0200 Subject: [PATCH 68/90] fix: add session for escalate --- alertaclient/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index 9abcbe9..d75ae98 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -493,7 +493,7 @@ def housekeeping(self, expired_delete_hours=None, info_delete_hours=None): raise UnknownError(response.text) def escalate(self): - self.http.get('/escalate', auth=self.http.auth) + self.http.session.get(self.http.endpoint + '/escalate', auth=self.http.auth, timeout=self.http.timeout) class ApiKeyAuth(AuthBase): From 298a5c894d7c401226ec8aa74cd480c003264ed2 Mon Sep 17 00:00:00 2001 From: sbgap Date: Fri, 15 Mar 2024 10:39:55 +0100 Subject: [PATCH 69/90] feat: add reactivate notification rules command --- alertaclient/api.py | 3 +++ .../commands/cmd_reactivate_notification_rules.py | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 alertaclient/commands/cmd_reactivate_notification_rules.py diff --git a/alertaclient/api.py b/alertaclient/api.py index d75ae98..f3931a8 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -495,6 +495,9 @@ def housekeeping(self, expired_delete_hours=None, info_delete_hours=None): def escalate(self): self.http.session.get(self.http.endpoint + '/escalate', auth=self.http.auth, timeout=self.http.timeout) + def reactivate_notification_rules(self): + self.http.session.get(self.http.endpoint + '/notificationrules/reactivate', auth=self.http.auth, timeout=self.http.timeout) + class ApiKeyAuth(AuthBase): diff --git a/alertaclient/commands/cmd_reactivate_notification_rules.py b/alertaclient/commands/cmd_reactivate_notification_rules.py new file mode 100644 index 0000000..10a45db --- /dev/null +++ b/alertaclient/commands/cmd_reactivate_notification_rules.py @@ -0,0 +1,9 @@ +import click + + +@click.command('reactivate_notitification_rules', short_help='Reactivate inactive notification rules after reactivate time is up') +@click.pass_obj +def cli(obj): + """Trigger reactivation of notification rules""" + client = obj['client'] + client.reactivate_notification_rules() From 8abf2a463acf318dae3455e7e1d8b58744572eb5 Mon Sep 17 00:00:00 2001 From: sbgap Date: Mon, 10 Jun 2024 09:00:10 +0200 Subject: [PATCH 70/90] feat: add firing of delayed notification rules --- alertaclient/api.py | 3 +++ alertaclient/commands/cmd_notfication_delay.py | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 alertaclient/commands/cmd_notfication_delay.py diff --git a/alertaclient/api.py b/alertaclient/api.py index f3931a8..d42028c 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -498,6 +498,9 @@ def escalate(self): def reactivate_notification_rules(self): self.http.session.get(self.http.endpoint + '/notificationrules/reactivate', auth=self.http.auth, timeout=self.http.timeout) + def fire_delayed_notifications(self): + self.http.session.get(self.http.endpoint + '/notificationdelay/fire', auth=self.http.auth, timeout=self.http.timeout) + class ApiKeyAuth(AuthBase): diff --git a/alertaclient/commands/cmd_notfication_delay.py b/alertaclient/commands/cmd_notfication_delay.py new file mode 100644 index 0000000..f6e5a58 --- /dev/null +++ b/alertaclient/commands/cmd_notfication_delay.py @@ -0,0 +1,9 @@ +import click + + +@click.command('fire_delayed_notifications', short_help='Fire delayed notifications') +@click.pass_obj +def cli(obj): + """Firing delayed notification rules""" + client = obj['client'] + client.fire_delayed_notifications() From 9c70e64ba12fe11b7a51afe32ebd882c86cc9646 Mon Sep 17 00:00:00 2001 From: sbgap Date: Tue, 25 Mar 2025 13:09:16 +0100 Subject: [PATCH 71/90] refactor: heartbeats only sends in new event/status --- alertaclient/commands/cmd_heartbeats.py | 94 ++++++++++++++----------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index db9ffd1..aa67113 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -54,60 +54,68 @@ def cli(obj, alert, severity, timeout, purge): if alert: with click.progressbar(heartbeats, label=f'Alerting {len(heartbeats)} heartbeats') as bar: + alerts = client.get_alerts(query=[('environment', 'Heartbeats')], page_size=len(heartbeats)) for b in bar: - - want_environment = b.attributes.pop('environment', 'Production') + want_environment = b.attributes.pop('environment', 'Heartbeats') want_severity = b.attributes.pop('severity', severity) want_service = b.attributes.pop('service', ['Alerta']) want_group = b.attributes.pop('group', 'System') + state_map = { + 'expired': { + 'event': 'HeartbeatFail', + 'value': f'{b.since}', + 'text': f'Heartbeat not received in {b.timeout} seconds', + 'severity': want_severity + }, + 'slow': { + 'event': 'HeartbeatSlow', + 'value': f'{b.latency}ms', + 'text': f'Heartbeat took more than {b.max_latency}ms to be processed', + 'severity': want_severity + }, + 'ok': { + 'event': 'HeartbeatOK', + 'value': '', + 'text': 'Heartbeat OK', + 'severity': default_normal_severity + } + } - if b.status == 'expired': # aka. "stale" - client.send_alert( - resource=b.origin, - event='HeartbeatFail', - environment=want_environment, - severity=want_severity, - correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], - service=want_service, - group=want_group, - value=f'{b.since}', - text=f'Heartbeat not received in {b.timeout} seconds', - tags=b.tags, - attributes=b.attributes, - origin=origin(), - type='heartbeatAlert', - timeout=timeout, - customer=b.customer - ) - elif b.status == 'slow': - client.send_alert( - resource=b.origin, - event='HeartbeatSlow', - environment=want_environment, - severity=want_severity, - correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], - service=want_service, - group=want_group, - value=f'{b.latency}ms', - text=f'Heartbeat took more than {b.max_latency}ms to be processed', - tags=b.tags, - attributes=b.attributes, - origin=origin(), - type='heartbeatAlert', - timeout=timeout, - customer=b.customer - ) - else: + state = state_map[b.status] + alert_exists = False + for alert in alerts: + if alert.environment == want_environment and alert.resource == b.origin: + alert_exists = True + if state['event'] != alert.event: + client.send_alert( + resource=b.origin, + event=state['event'], + environment=want_environment, + severity=state['severity'], + correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], + service=want_service, + group=want_group, + value=state['value'], + text=state['text'], + tags=b.tags, + attributes=b.attributes, + origin=origin(), + type='heartbeatAlert', + timeout=timeout, + customer=b.customer + ) + break + if not alert_exists: client.send_alert( resource=b.origin, - event='HeartbeatOK', + event=state['event'], environment=want_environment, - severity=default_normal_severity, + severity=state['severity'], correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], service=want_service, group=want_group, - value='', - text='Heartbeat OK', + value=state['value'], + text=state['text'], tags=b.tags, attributes=b.attributes, origin=origin(), From de9fea4f7e4c94a86b6dcf71937ae313282b6b52 Mon Sep 17 00:00:00 2001 From: sbgap Date: Tue, 6 May 2025 09:20:39 +0200 Subject: [PATCH 72/90] fix: remove heartbeat alerts from Production environment when adding new heartbeat alerts to Heartbeat environment --- alertaclient/commands/cmd_heartbeats.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index aa67113..4102acc 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -106,6 +106,20 @@ def cli(obj, alert, severity, timeout, purge): ) break if not alert_exists: + old_alerts = client.get_alerts( + query=[ + ('environment', 'Production'), + ('event', 'HeartbeatFail'), + ('event', 'HeartbeatSlow'), + ('event', 'HeartbeatOK'), + ('resource', b.origin) + ], + page_size=len(heartbeats) + ) + + for old_alert in old_alerts: + client.delete_alert(old_alert.id) + client.send_alert( resource=b.origin, event=state['event'], From 7cae0d73d54392fa47a078f35bbe17be36624462 Mon Sep 17 00:00:00 2001 From: sbgap Date: Tue, 27 May 2025 09:13:58 +0200 Subject: [PATCH 73/90] fix: make all heartbeat alerts use heartbeats environment and delte all other heartbeat alerts --- alertaclient/commands/cmd_heartbeats.py | 52 +++++++------------------ 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index 4102acc..d798920 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -54,9 +54,9 @@ def cli(obj, alert, severity, timeout, purge): if alert: with click.progressbar(heartbeats, label=f'Alerting {len(heartbeats)} heartbeats') as bar: - alerts = client.get_alerts(query=[('environment', 'Heartbeats')], page_size=len(heartbeats)) + alerts = client.get_alerts(query=[('event', '~Heartbeat')], page_size='ALL') for b in bar: - want_environment = b.attributes.pop('environment', 'Heartbeats') + want_environment = 'Heartbeats' want_severity = b.attributes.pop('severity', severity) want_service = b.attributes.pop('service', ['Alerta']) want_group = b.attributes.pop('group', 'System') @@ -82,44 +82,15 @@ def cli(obj, alert, severity, timeout, purge): } state = state_map[b.status] - alert_exists = False - for alert in alerts: - if alert.environment == want_environment and alert.resource == b.origin: - alert_exists = True - if state['event'] != alert.event: - client.send_alert( - resource=b.origin, - event=state['event'], - environment=want_environment, - severity=state['severity'], - correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], - service=want_service, - group=want_group, - value=state['value'], - text=state['text'], - tags=b.tags, - attributes=b.attributes, - origin=origin(), - type='heartbeatAlert', - timeout=timeout, - customer=b.customer - ) + alert = None + # Find heartbeat alert in existing alerts + for a in alerts: + if a.environment == want_environment and a.resource == b.origin: + alert = alerts.pop(alerts.index(a)) break - if not alert_exists: - old_alerts = client.get_alerts( - query=[ - ('environment', 'Production'), - ('event', 'HeartbeatFail'), - ('event', 'HeartbeatSlow'), - ('event', 'HeartbeatOK'), - ('resource', b.origin) - ], - page_size=len(heartbeats) - ) - - for old_alert in old_alerts: - client.delete_alert(old_alert.id) + # Only send in new/updated alert + if alert is None or state['event'] != alert.event: client.send_alert( resource=b.origin, event=state['event'], @@ -137,3 +108,8 @@ def cli(obj, alert, severity, timeout, purge): timeout=timeout, customer=b.customer ) + + # Remove unused/old heartbeat alerts, there is no heartbeat matching the alert + with click.progressbar(alerts, label=f'Removing {len(alerts)} old alerts') as bar: + for alert in bar: + client.delete_alert(alert.id) From 8115ee2834f9b3e91c68ee550d9bd3c9a584b888 Mon Sep 17 00:00:00 2001 From: sbgap Date: Tue, 23 Sep 2025 15:36:16 +0200 Subject: [PATCH 74/90] feat: add support for external heartbeats to have more precise times --- alertaclient/models/heartbeat.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/alertaclient/models/heartbeat.py b/alertaclient/models/heartbeat.py index 1f13809..a808806 100644 --- a/alertaclient/models/heartbeat.py +++ b/alertaclient/models/heartbeat.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta from alertaclient.utils import DateTime +import re DEFAULT_MAX_LATENCY = 2000 # ms @@ -46,6 +47,14 @@ def parse(cls, json): if not isinstance(json.get('timeout', 0), int): raise ValueError('timeout must be an integer') + create_time = json.get('createTime') + if create_time is not None: + create_time = re.sub(r'(\d{3})(\d*)(Z)', r'\1\3', create_time) + + receiveTime = json.get('receiveTime') + if receiveTime is not None: + receiveTime = re.sub(r'(\d{3})(\d*)(Z)', r'\1\3', receiveTime) + return Heartbeat( id=json.get('id', None), origin=json.get('origin', None), @@ -53,10 +62,10 @@ def parse(cls, json): tags=json.get('tags', list()), attributes=json.get('attributes', dict()), event_type=json.get('type', None), - create_time=DateTime.parse(json.get('createTime')), + create_time=DateTime.parse(create_time), timeout=json.get('timeout', None), max_latency=json.get('maxLatency', None) or DEFAULT_MAX_LATENCY, - receive_time=DateTime.parse(json.get('receiveTime')), + receive_time=DateTime.parse(receiveTime), customer=json.get('customer', None) ) From 1eebba9561f4ec56e1d0bf5509fdb3e8ab37276c Mon Sep 17 00:00:00 2001 From: sbgap Date: Wed, 12 Feb 2025 13:42:16 +0100 Subject: [PATCH 75/90] refactor: heartbeats only sends in new event/status --- alertaclient/commands/cmd_heartbeats.py | 1 + 1 file changed, 1 insertion(+) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index d798920..f51c5c7 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -6,6 +6,7 @@ from alertaclient.models.heartbeat import Heartbeat from alertaclient.utils import origin +from time import sleep @click.command('heartbeats', short_help='List heartbeats') @click.option('--alert', is_flag=True, help='Alert on stale or slow heartbeats') From f90002e2ae8dfd266c35832f8bac19b66b2bd3da Mon Sep 17 00:00:00 2001 From: sbgap Date: Wed, 12 Feb 2025 16:01:16 +0100 Subject: [PATCH 76/90] refactor: optimize heartbeats --- alertaclient/api.py | 5 +++++ alertaclient/commands/cmd_heartbeats.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index d42028c..3f0fc8f 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -69,6 +69,11 @@ def send_alert(self, resource, event, **kwargs): alert = Alert.parse(r['alert']) if 'alert' in r else None return r.get('id', '-'), alert, r.get('message', None) + def send_alerts(self, data: list): + r = self.http.post('/alerts', data) + alerts = [Alert.parse(alert) for alert in r['alerts']] if 'alerts' in r else None + return [alert.id for alert in alerts], alerts, r.get('message', None) + def get_alert(self, id): return Alert.parse(self.http.get('/alert/%s' % id)['alert']) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index f51c5c7..10c2c3b 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -6,7 +6,7 @@ from alertaclient.models.heartbeat import Heartbeat from alertaclient.utils import origin -from time import sleep +from datetime import datetime @click.command('heartbeats', short_help='List heartbeats') @click.option('--alert', is_flag=True, help='Alert on stale or slow heartbeats') @@ -16,6 +16,7 @@ @click.pass_obj def cli(obj, alert, severity, timeout, purge): """List heartbeats.""" + start = datetime.now() client = obj['client'] try: From bd9c1b270a9457074ba727f7c86fd5e09e8ae822 Mon Sep 17 00:00:00 2001 From: sbgap Date: Wed, 26 Mar 2025 11:41:17 +0100 Subject: [PATCH 77/90] refac: add progress bar for sending alerts --- alertaclient/commands/cmd_heartbeats.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index 10c2c3b..d798920 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -6,7 +6,6 @@ from alertaclient.models.heartbeat import Heartbeat from alertaclient.utils import origin -from datetime import datetime @click.command('heartbeats', short_help='List heartbeats') @click.option('--alert', is_flag=True, help='Alert on stale or slow heartbeats') @@ -16,7 +15,6 @@ @click.pass_obj def cli(obj, alert, severity, timeout, purge): """List heartbeats.""" - start = datetime.now() client = obj['client'] try: From 107583b62c126ac8d1d56eaf265e15fd39cabe90 Mon Sep 17 00:00:00 2001 From: sbgap Date: Thu, 29 Jan 2026 09:03:48 +0100 Subject: [PATCH 78/90] fix: add correct bulk alerting --- alertaclient/api.py | 3 +++ alertaclient/commands/cmd_heartbeats.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index 3f0fc8f..5658ec7 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -107,6 +107,9 @@ def update_attributes(self, id, attributes): def delete_alert(self, id): return self.http.delete('/alert/%s' % id) + + def delete_alerts(self, ids): + return self.http.delete('/alerts?%s' % '&'.join([f'id={id}'for id in ids])) def search(self, query=None, page=1, page_size=None): return self.get_alerts(query, page, page_size) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index d798920..f4bae21 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -33,7 +33,7 @@ def cli(obj, alert, severity, timeout, purge): ) if obj['output'] == 'json': - r = client.http.get('/heartbeats') + r = client.http.get('/heartbeats?page-size=ALL') heartbeats = [Heartbeat.parse(hb) for hb in r['heartbeats']] click.echo(json.dumps(r['heartbeats'], sort_keys=True, indent=4, ensure_ascii=False)) else: From 1ec2da279f011c9fd23179ee3d2d2c0f54edb371 Mon Sep 17 00:00:00 2001 From: sbgap Date: Thu, 29 Jan 2026 09:52:48 +0100 Subject: [PATCH 79/90] tests: update test flow --- .github/workflows/release.yml | 9 --------- .github/workflows/tests.yml | 2 +- .pre-commit-config.yaml | 19 ++++++++----------- alertaclient/__init__.py | 26 -------------------------- alertaclient/api.py | 2 +- alertaclient/models/heartbeat.py | 2 +- requirements-dev.txt | 2 +- 7 files changed, 12 insertions(+), 50 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2884f2..df4293c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,15 +56,6 @@ jobs: run: | python3 -m pip install --upgrade build python3 -m build - - name: Publish to PyPI - id: publish - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: | - python3 -m pip install --upgrade twine - python3 -m twine check dist/* - python3 -m twine upload --verbose dist/* - uses: act10ns/slack@v2 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9dac04e..7c1beb7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39ea43c..1b8b58c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: -- repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v1.5.1 +- repo: https://github.com/hhatto/autopep8 + rev: v2.0.4 hooks: - id: autopep8 - repo: https://github.com/pre-commit/pre-commit-hooks.git - rev: v2.5.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-ast @@ -23,22 +23,19 @@ repos: args: ['--autofix'] - id: name-tests-test args: ['--django'] + exclude: ^tests/helpers/ - id: requirements-txt-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + rev: 6.1.0 hooks: - id: flake8 - repo: https://github.com/asottile/pyupgrade - rev: v1.27.0 + rev: v3.21.2 hooks: - id: pyupgrade args: ['--py3-plus'] -- repo: https://github.com/asottile/seed-isort-config - rev: v1.9.4 - hooks: - - id: seed-isort-config -- repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 +- repo: https://github.com/PyCQA/isort + rev: 7.0.0 hooks: - id: isort diff --git a/alertaclient/__init__.py b/alertaclient/__init__.py index 193f832..e69de29 100644 --- a/alertaclient/__init__.py +++ b/alertaclient/__init__.py @@ -1,26 +0,0 @@ -import sys - -if sys.version_info < (3,): - raise ImportError( - """You are running Alerta 6.0 on Python 2 - -Alerta 6.0 and above are no longer compatible with Python 2. - -Make sure you have pip >= 9.0 to avoid this kind of issue, -as well as setuptools >= 24.2: - - $ pip install pip setuptools --upgrade - -Your choices: - -- Upgrade to Python 3. - -- Install an older version of Alerta: - - $ pip install 'alerta<6.0' - -See the following URL for more up-to-date information: - -https://github.com/alerta/alerta/wiki/Python-3 - -""") diff --git a/alertaclient/api.py b/alertaclient/api.py index 5658ec7..a6dda7c 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -107,7 +107,7 @@ def update_attributes(self, id, attributes): def delete_alert(self, id): return self.http.delete('/alert/%s' % id) - + def delete_alerts(self, ids): return self.http.delete('/alerts?%s' % '&'.join([f'id={id}'for id in ids])) diff --git a/alertaclient/models/heartbeat.py b/alertaclient/models/heartbeat.py index a808806..f61292b 100644 --- a/alertaclient/models/heartbeat.py +++ b/alertaclient/models/heartbeat.py @@ -1,7 +1,7 @@ +import re from datetime import datetime, timedelta from alertaclient.utils import DateTime -import re DEFAULT_MAX_LATENCY = 2000 # ms diff --git a/requirements-dev.txt b/requirements-dev.txt index d438b06..5fb0f7f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ mypy==1.10.1 pre-commit==3.3.3 pylint==3.2.5 -pytest-cov pytest>=5.4.3 +pytest-cov python-dotenv requests_mock From 010e7d106cf0b4f55f2e7b60972a2a1db121a5fe Mon Sep 17 00:00:00 2001 From: sbgap Date: Wed, 12 Feb 2025 13:42:16 +0100 Subject: [PATCH 80/90] refactor: heartbeats only sends in new event/status --- alertaclient/commands/cmd_heartbeats.py | 36 ++++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index f4bae21..6590d71 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -6,6 +6,7 @@ from alertaclient.models.heartbeat import Heartbeat from alertaclient.utils import origin +from time import sleep @click.command('heartbeats', short_help='List heartbeats') @click.option('--alert', is_flag=True, help='Alert on stale or slow heartbeats') @@ -54,9 +55,9 @@ def cli(obj, alert, severity, timeout, purge): if alert: with click.progressbar(heartbeats, label=f'Alerting {len(heartbeats)} heartbeats') as bar: - alerts = client.get_alerts(query=[('event', '~Heartbeat')], page_size='ALL') + alerts = client.get_alerts(query=[('service', 'Alerta')], page_size=len(heartbeats)) for b in bar: - want_environment = 'Heartbeats' + want_environment = b.attributes.pop('environment', 'Heartbeats') want_severity = b.attributes.pop('severity', severity) want_service = b.attributes.pop('service', ['Alerta']) want_group = b.attributes.pop('group', 'System') @@ -82,15 +83,30 @@ def cli(obj, alert, severity, timeout, purge): } state = state_map[b.status] - alert = None - # Find heartbeat alert in existing alerts - for a in alerts: - if a.environment == want_environment and a.resource == b.origin: - alert = alerts.pop(alerts.index(a)) + alert_exists = False + for alert in alerts: + if alert.environment == want_environment and alert.resource == b.origin: + alert_exists = True + if state['event'] != alert.event: + client.send_alert( + resource=b.origin, + event=state['event'], + environment=want_environment, + severity=state['severity'], + correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], + service=want_service, + group=want_group, + value=state['value'], + text=state['text'], + tags=b.tags, + attributes=b.attributes, + origin=origin(), + type='heartbeatAlert', + timeout=timeout, + customer=b.customer + ) break - - # Only send in new/updated alert - if alert is None or state['event'] != alert.event: + if not alert_exists: client.send_alert( resource=b.origin, event=state['event'], From 4b64b42899a7cf483cbc8c0314b0b65a6753e854 Mon Sep 17 00:00:00 2001 From: sbgap Date: Wed, 12 Feb 2025 16:01:16 +0100 Subject: [PATCH 81/90] refactor: optimize heartbeats --- alertaclient/commands/cmd_heartbeats.py | 84 ++++++++++++++----------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index 6590d71..f0c07d8 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -6,7 +6,7 @@ from alertaclient.models.heartbeat import Heartbeat from alertaclient.utils import origin -from time import sleep +from datetime import datetime @click.command('heartbeats', short_help='List heartbeats') @click.option('--alert', is_flag=True, help='Alert on stale or slow heartbeats') @@ -16,6 +16,7 @@ @click.pass_obj def cli(obj, alert, severity, timeout, purge): """List heartbeats.""" + start = datetime.now() client = obj['client'] try: @@ -55,7 +56,9 @@ def cli(obj, alert, severity, timeout, purge): if alert: with click.progressbar(heartbeats, label=f'Alerting {len(heartbeats)} heartbeats') as bar: - alerts = client.get_alerts(query=[('service', 'Alerta')], page_size=len(heartbeats)) + alerts = client.get_alerts(query=[('environment', 'Heartbeats')], page_size=len(heartbeats)) + new_alerts = [] + for b in bar: want_environment = b.attributes.pop('environment', 'Heartbeats') want_severity = b.attributes.pop('severity', severity) @@ -88,44 +91,49 @@ def cli(obj, alert, severity, timeout, purge): if alert.environment == want_environment and alert.resource == b.origin: alert_exists = True if state['event'] != alert.event: - client.send_alert( - resource=b.origin, - event=state['event'], - environment=want_environment, - severity=state['severity'], - correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], - service=want_service, - group=want_group, - value=state['value'], - text=state['text'], - tags=b.tags, - attributes=b.attributes, - origin=origin(), - type='heartbeatAlert', - timeout=timeout, - customer=b.customer + new_alerts.append( + { + 'resource': b.origin, + 'event': state['event'], + 'environment': want_environment, + 'severity': state['severity'], + 'correlate': ['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], + 'service': want_service, + 'group': want_group, + 'value': state['value'], + 'text': state['text'], + 'tags': b.tags, + 'attributes': b.attributes, + 'origin': origin(), + 'type': 'heartbeatAlert', + 'timeout': timeout, + 'customer': b.customer + } ) break if not alert_exists: - client.send_alert( - resource=b.origin, - event=state['event'], - environment=want_environment, - severity=state['severity'], - correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], - service=want_service, - group=want_group, - value=state['value'], - text=state['text'], - tags=b.tags, - attributes=b.attributes, - origin=origin(), - type='heartbeatAlert', - timeout=timeout, - customer=b.customer + new_alerts.append( + { + 'resource': b.origin, + 'event': state['event'], + 'environment': want_environment, + 'severity': state['severity'], + 'correlate': ['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], + 'service': want_service, + 'group': want_group, + 'value': state['value'], + 'text': state['text'], + 'tags': b.tags, + 'attributes': b.attributes, + 'origin': origin(), + 'type': 'heartbeatAlert', + 'timeout': timeout, + 'customer': b.customer + } ) - # Remove unused/old heartbeat alerts, there is no heartbeat matching the alert - with click.progressbar(alerts, label=f'Removing {len(alerts)} old alerts') as bar: - for alert in bar: - client.delete_alert(alert.id) + if len(new_alerts) > 0: + client.send_alerts(new_alerts) + end = datetime.now() + print('\n') + print(end - start) From 3a7de5e2ff4ce6931dc7d4867e91ff91d102d572 Mon Sep 17 00:00:00 2001 From: sbgap Date: Tue, 25 Mar 2025 11:36:35 +0100 Subject: [PATCH 82/90] feat: update heartbeat alerts in bulks of 200 alerts per send --- alertaclient/commands/cmd_heartbeats.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index f0c07d8..8d7b45c 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -131,9 +131,11 @@ def cli(obj, alert, severity, timeout, purge): 'customer': b.customer } ) - - if len(new_alerts) > 0: - client.send_alerts(new_alerts) + number_of_co = 200 + number_of_alerts = len(new_alerts) + number_of_sends = number_of_alerts // number_of_co + for i in range(number_of_sends): + client.send_alerts(new_alerts[i * number_of_co:(i + 1) * number_of_co]) + if number_of_alerts % number_of_co: + client.send_alerts(new_alerts[number_of_co * number_of_sends:number_of_co * number_of_sends + number_of_alerts % number_of_co]) end = datetime.now() - print('\n') - print(end - start) From c9844023c815349e26aa3a70388fe53abdc446ad Mon Sep 17 00:00:00 2001 From: sbgap Date: Wed, 26 Mar 2025 11:29:38 +0100 Subject: [PATCH 83/90] refac: remove alert from loop when found --- alertaclient/commands/cmd_heartbeats.py | 31 +++++-------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index 8d7b45c..e96752b 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -86,32 +86,13 @@ def cli(obj, alert, severity, timeout, purge): } state = state_map[b.status] - alert_exists = False - for alert in alerts: - if alert.environment == want_environment and alert.resource == b.origin: - alert_exists = True - if state['event'] != alert.event: - new_alerts.append( - { - 'resource': b.origin, - 'event': state['event'], - 'environment': want_environment, - 'severity': state['severity'], - 'correlate': ['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'], - 'service': want_service, - 'group': want_group, - 'value': state['value'], - 'text': state['text'], - 'tags': b.tags, - 'attributes': b.attributes, - 'origin': origin(), - 'type': 'heartbeatAlert', - 'timeout': timeout, - 'customer': b.customer - } - ) + found_alert = None + for a in alerts: + if a.environment == want_environment and a.resource == b.origin: + found_alert = alerts.pop(alerts.index(a)) break - if not alert_exists: + + if found_alert is None or state['event'] != found_alert.event: new_alerts.append( { 'resource': b.origin, From 82c7583281d6bf2ce18b515048425a597cf25d02 Mon Sep 17 00:00:00 2001 From: sbgap Date: Wed, 26 Mar 2025 11:41:17 +0100 Subject: [PATCH 84/90] refac: add progress bar for sending alerts --- alertaclient/commands/cmd_heartbeats.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index e96752b..d7938ca 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -6,7 +6,6 @@ from alertaclient.models.heartbeat import Heartbeat from alertaclient.utils import origin -from datetime import datetime @click.command('heartbeats', short_help='List heartbeats') @click.option('--alert', is_flag=True, help='Alert on stale or slow heartbeats') @@ -16,7 +15,6 @@ @click.pass_obj def cli(obj, alert, severity, timeout, purge): """List heartbeats.""" - start = datetime.now() client = obj['client'] try: @@ -55,7 +53,7 @@ def cli(obj, alert, severity, timeout, purge): client.delete_heartbeat(b.id) if alert: - with click.progressbar(heartbeats, label=f'Alerting {len(heartbeats)} heartbeats') as bar: + with click.progressbar(heartbeats, label=f'Checking {len(heartbeats)} heartbeats') as bar: alerts = client.get_alerts(query=[('environment', 'Heartbeats')], page_size=len(heartbeats)) new_alerts = [] @@ -112,11 +110,14 @@ def cli(obj, alert, severity, timeout, purge): 'customer': b.customer } ) - number_of_co = 200 - number_of_alerts = len(new_alerts) - number_of_sends = number_of_alerts // number_of_co - for i in range(number_of_sends): - client.send_alerts(new_alerts[i * number_of_co:(i + 1) * number_of_co]) - if number_of_alerts % number_of_co: - client.send_alerts(new_alerts[number_of_co * number_of_sends:number_of_co * number_of_sends + number_of_alerts % number_of_co]) - end = datetime.now() + + number_of_co = 200 + number_of_alerts = len(new_alerts) + number_of_sends = number_of_alerts // number_of_co + alert_groups = [new_alerts[i * number_of_co:(i + 1) * number_of_co] for i in range(number_of_sends)] + if number_of_alerts % number_of_co: + alert_groups.append(new_alerts[number_of_co * number_of_sends:number_of_co * number_of_sends + number_of_alerts % number_of_co]) + + with click.progressbar(alert_groups, label=f'Alerting {len(new_alerts)} heartbeats') as bar: + for b in bar: + client.send_alerts(b) From d25853fd2e5e727733beecac921907b8e91f58b7 Mon Sep 17 00:00:00 2001 From: sbgap Date: Wed, 26 Mar 2025 11:56:56 +0100 Subject: [PATCH 85/90] refac: bulk 100 alerts instead of 200 --- alertaclient/commands/cmd_heartbeats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index d7938ca..bc06bcf 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -111,7 +111,7 @@ def cli(obj, alert, severity, timeout, purge): } ) - number_of_co = 200 + number_of_co = 100 number_of_alerts = len(new_alerts) number_of_sends = number_of_alerts // number_of_co alert_groups = [new_alerts[i * number_of_co:(i + 1) * number_of_co] for i in range(number_of_sends)] From 84c678de76f4bc6a2a093a01f5e56778800980d3 Mon Sep 17 00:00:00 2001 From: sbgap Date: Thu, 29 Jan 2026 09:03:48 +0100 Subject: [PATCH 86/90] fix: add correct bulk alerting --- alertaclient/api.py | 3 +++ alertaclient/commands/cmd_heartbeats.py | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index a6dda7c..351fad0 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -107,6 +107,9 @@ def update_attributes(self, id, attributes): def delete_alert(self, id): return self.http.delete('/alert/%s' % id) + + def delete_alerts(self, ids): + return self.http.delete('/alerts?%s' % '&'.join([f'id={id}'for id in ids])) def delete_alerts(self, ids): return self.http.delete('/alerts?%s' % '&'.join([f'id={id}'for id in ids])) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index bc06bcf..5be8bae 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -54,11 +54,11 @@ def cli(obj, alert, severity, timeout, purge): if alert: with click.progressbar(heartbeats, label=f'Checking {len(heartbeats)} heartbeats') as bar: - alerts = client.get_alerts(query=[('environment', 'Heartbeats')], page_size=len(heartbeats)) + alerts = client.get_alerts(query=[('event', '~Heartbeat')], page_size='ALL') new_alerts = [] for b in bar: - want_environment = b.attributes.pop('environment', 'Heartbeats') + want_environment = 'Heartbeats' want_severity = b.attributes.pop('severity', severity) want_service = b.attributes.pop('service', ['Alerta']) want_group = b.attributes.pop('group', 'System') @@ -111,7 +111,7 @@ def cli(obj, alert, severity, timeout, purge): } ) - number_of_co = 100 + number_of_co = 50 number_of_alerts = len(new_alerts) number_of_sends = number_of_alerts // number_of_co alert_groups = [new_alerts[i * number_of_co:(i + 1) * number_of_co] for i in range(number_of_sends)] @@ -121,3 +121,14 @@ def cli(obj, alert, severity, timeout, purge): with click.progressbar(alert_groups, label=f'Alerting {len(new_alerts)} heartbeats') as bar: for b in bar: client.send_alerts(b) + + + alerts = [alert.id for alert in alerts] + number_of_deletes = len(alerts) + number_of_delete_sends = number_of_deletes // number_of_co + delete_groups = [alerts[i * number_of_co:(i + 1) * number_of_co] for i in range(number_of_delete_sends)] + if number_of_deletes % number_of_co: + delete_groups.append(alerts[number_of_co * number_of_delete_sends:number_of_co * number_of_delete_sends + number_of_deletes % number_of_co]) + with click.progressbar(delete_groups, label=f'Removing {len(alerts)} old alerts') as bar: + for delete_group in bar: + client.delete_alerts(delete_group) From a037c792ad17cb6ee0e3a39a553898b212cba7ab Mon Sep 17 00:00:00 2001 From: sbgap Date: Fri, 30 Jan 2026 08:58:57 +0100 Subject: [PATCH 87/90] test: update tests with new functionality of heartbeats --- tests/unit/test_commands.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py index 3ec72ab..2de26ca 100644 --- a/tests/unit/test_commands.py +++ b/tests/unit/test_commands.py @@ -84,8 +84,7 @@ def test_heartbeat_cmd(self, m): @requests_mock.mock() def test_heartbeats_cmd(self, m): - heartbeats_response = """ - { + heartbeats_response = { "heartbeats": [ { "attributes": { @@ -96,7 +95,7 @@ def test_heartbeats_cmd(self, m): "region": "EU" }, "createTime": "2020-03-10T20:25:54.541Z", - "customer": null, + "customer": None, "href": "http://127.0.0.1/heartbeat/52c202e8-d949-45ed-91e0-cdad4f37de73", "id": "52c202e8-d949-45ed-91e0-cdad4f37de73", "latency": 0, @@ -113,11 +112,10 @@ def test_heartbeats_cmd(self, m): "status": "ok", "total": 1 } - """ heartbeat_alert_response = """ { - "alert": { + "alerts": [{ "attributes": {}, "correlate": [ "HeartbeatFail", @@ -166,25 +164,31 @@ def test_heartbeats_cmd(self, m): "type": "heartbeatAlert", "updateTime": "2020-03-10T21:55:07.916Z", "value": "22ms" - }, - "id": "6cfbc30f-c2d6-4edf-b672-841070995206", + }], "status": "ok" } """ - m.get('/heartbeats', text=heartbeats_response) - m.post('/alert', text=heartbeat_alert_response) + empty_alerts_response=""" + { + "alerts":[], + "status": "ok" + } + """ + + m.get('/heartbeats', json=heartbeats_response) + m.get('/alerts', text=empty_alerts_response) + m.post('/alerts', text=heartbeat_alert_response) result = self.runner.invoke(heartbeats_cmd, ['--alert'], obj=self.obj) self.assertEqual(result.exit_code, 0, result.exception) self.assertIn('monitoring-01', result.output) - - history = m.request_history - data = history[1].json() - self.assertEqual(data['environment'], 'Infrastructure') + + data = m.last_request.json()[0] + self.assertEqual(data['environment'], 'Heartbeats') self.assertEqual(data['severity'], 'major') self.assertEqual(data['service'], ['Internal']) self.assertEqual(data['group'], 'Heartbeats') - self.assertEqual(data['attributes'], {'region': 'EU'}) + self.assertEqual(data['attributes'], {'environment': 'Infrastructure', 'region': 'EU'}) @requests_mock.mock() def test_whoami_cmd(self, m): From de699ea04712ff1df342d9322225c577f832bde3 Mon Sep 17 00:00:00 2001 From: sbgap Date: Fri, 30 Jan 2026 09:01:14 +0100 Subject: [PATCH 88/90] style: fix style and remove duplicate code from rebase/merge --- alertaclient/api.py | 3 -- alertaclient/commands/cmd_heartbeats.py | 3 +- tests/unit/test_commands.py | 56 ++++++++++++------------- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/alertaclient/api.py b/alertaclient/api.py index 351fad0..a6dda7c 100644 --- a/alertaclient/api.py +++ b/alertaclient/api.py @@ -107,9 +107,6 @@ def update_attributes(self, id, attributes): def delete_alert(self, id): return self.http.delete('/alert/%s' % id) - - def delete_alerts(self, ids): - return self.http.delete('/alerts?%s' % '&'.join([f'id={id}'for id in ids])) def delete_alerts(self, ids): return self.http.delete('/alerts?%s' % '&'.join([f'id={id}'for id in ids])) diff --git a/alertaclient/commands/cmd_heartbeats.py b/alertaclient/commands/cmd_heartbeats.py index 5be8bae..9003b89 100644 --- a/alertaclient/commands/cmd_heartbeats.py +++ b/alertaclient/commands/cmd_heartbeats.py @@ -122,8 +122,7 @@ def cli(obj, alert, severity, timeout, purge): for b in bar: client.send_alerts(b) - - alerts = [alert.id for alert in alerts] + alerts = [alert.id for alert in alerts] number_of_deletes = len(alerts) number_of_delete_sends = number_of_deletes // number_of_co delete_groups = [alerts[i * number_of_co:(i + 1) * number_of_co] for i in range(number_of_delete_sends)] diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py index 2de26ca..dfd0a60 100644 --- a/tests/unit/test_commands.py +++ b/tests/unit/test_commands.py @@ -85,32 +85,32 @@ def test_heartbeat_cmd(self, m): def test_heartbeats_cmd(self, m): heartbeats_response = { - "heartbeats": [ - { - "attributes": { - "environment": "Infrastructure", - "severity": "major", - "service": ["Internal"], - "group": "Heartbeats", - "region": "EU" - }, - "createTime": "2020-03-10T20:25:54.541Z", - "customer": None, - "href": "http://127.0.0.1/heartbeat/52c202e8-d949-45ed-91e0-cdad4f37de73", - "id": "52c202e8-d949-45ed-91e0-cdad4f37de73", - "latency": 0, - "maxLatency": 2000, - "origin": "monitoring-01", - "receiveTime": "2020-03-10T20:25:54.541Z", - "since": 204, - "status": "expired", - "tags": [], - "timeout": 90, - "type": "Heartbeat" - } - ], - "status": "ok", - "total": 1 + 'heartbeats': [ + { + 'attributes': { + 'environment': 'Infrastructure', + 'severity': 'major', + 'service': ['Internal'], + 'group': 'Heartbeats', + 'region': 'EU' + }, + 'createTime': '2020-03-10T20:25:54.541Z', + 'customer': None, + 'href': 'http://127.0.0.1/heartbeat/52c202e8-d949-45ed-91e0-cdad4f37de73', + 'id': '52c202e8-d949-45ed-91e0-cdad4f37de73', + 'latency': 0, + 'maxLatency': 2000, + 'origin': 'monitoring-01', + 'receiveTime': '2020-03-10T20:25:54.541Z', + 'since': 204, + 'status': 'expired', + 'tags': [], + 'timeout': 90, + 'type': 'Heartbeat' + } + ], + 'status': 'ok', + 'total': 1 } heartbeat_alert_response = """ @@ -169,7 +169,7 @@ def test_heartbeats_cmd(self, m): } """ - empty_alerts_response=""" + empty_alerts_response = """ { "alerts":[], "status": "ok" @@ -182,7 +182,7 @@ def test_heartbeats_cmd(self, m): result = self.runner.invoke(heartbeats_cmd, ['--alert'], obj=self.obj) self.assertEqual(result.exit_code, 0, result.exception) self.assertIn('monitoring-01', result.output) - + data = m.last_request.json()[0] self.assertEqual(data['environment'], 'Heartbeats') self.assertEqual(data['severity'], 'major') From b6e1ff89e152c7010a43f282e0e477a40c98c0dc Mon Sep 17 00:00:00 2001 From: sbgap Date: Fri, 30 Jan 2026 09:19:26 +0100 Subject: [PATCH 89/90] ci: update docker-compose to docker compose --- .github/workflows/tests.yml | 6 +++--- Makefile | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c1beb7..bdecb5d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,9 +47,9 @@ jobs: - name: Integration Test id: integration-test run: | - docker-compose -f docker-compose.ci.yaml build sut - docker-compose -f docker-compose.ci.yaml up --exit-code-from sut - docker-compose -f docker-compose.ci.yaml rm --stop --force + docker compose -f docker-compose.ci.yaml build sut + docker compose -f docker-compose.ci.yaml up --exit-code-from sut + docker compose -f docker-compose.ci.yaml rm --stop --force - uses: act10ns/slack@v2 with: status: ${{ job.status }} diff --git a/Makefile b/Makefile index e089f89..d3b23f7 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ FLAKE8=$(VENV)/bin/flake8 MYPY=$(VENV)/bin/mypy TOX=$(VENV)/bin/tox PYTEST=$(VENV)/bin/pytest -DOCKER_COMPOSE=docker-compose +DOCKER_COMPOSE=docker compose PRE_COMMIT=$(VENV)/bin/pre-commit BUILD=$(VENV)/bin/build WHEEL=$(VENV)/bin/wheel From dc873940b010450ddcfc48e15d4cb96b171a3b13 Mon Sep 17 00:00:00 2001 From: sbgap Date: Fri, 30 Jan 2026 09:19:51 +0100 Subject: [PATCH 90/90] ci: add zip files to releases --- .github/workflows/release.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df4293c..8234696 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,6 +56,20 @@ jobs: run: | python3 -m pip install --upgrade build python3 -m build + zip alerta-api.zip -r dist/* + tar cvfz alerta-api.tar.gz dist/* + - name: Release + id: create-release + uses: softprops/action-gh-release@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag_name: ${{ github.ref }} + name: Release ${{ github.ref }} + draft: false + prerelease: ${{ contains(github.ref_name, '-') }} + files: | + ./alerta-api.zip + ./alerta-api.tar.gz - uses: act10ns/slack@v2 with: