From 47e620ed2e4480ae96de910c2256fbf954a3e024 Mon Sep 17 00:00:00 2001 From: Erik Zilber Date: Thu, 2 Apr 2026 08:58:29 -0400 Subject: [PATCH 1/8] TPT-4298: Added PR title checking to lint workflow and clean up release notes workflow (#678) * Added PR title checking to lint workflow and clean up release notes * Bumped actions/github-script from v7 to v8 --- .github/workflows/ci.yml | 28 +++++++++++++++++ .github/workflows/clean-release-notes.yml | 37 +++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 .github/workflows/clean-release-notes.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9b5e4336..565cad0c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,35 @@ on: jobs: lint: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read steps: + # Enforce TPT-1234: prefix on PR titles, with the following exemptions: + # - PRs labeled 'dependencies' (e.g. Dependabot PRs) + # - PRs labeled 'hotfix' (urgent fixes that may not have a ticket) + # - PRs labeled 'community-contribution' (external contributors without TPT tickets) + # - PRs labeled 'ignore-for-release' (release PRs that don't need a ticket prefix) + - name: Validate PR Title + if: github.event_name == 'pull_request' + uses: amannn/action-semantic-pull-request@v6 + with: + types: | + TPT-\d+ + requireScope: false + # Override the default header pattern to allow hyphens and digits in the type + # (e.g. "TPT-4298: Description"). The default pattern only matches word + # characters (\w) which excludes hyphens. + headerPattern: '^([\w-]+):\s?(.*)$' + headerPatternCorrespondence: type, subject + ignoreLabels: | + dependencies + hotfix + community-contribution + ignore-for-release + env: + GITHUB_TOKEN: ${{ github.token }} + - name: checkout repo uses: actions/checkout@v6 diff --git a/.github/workflows/clean-release-notes.yml b/.github/workflows/clean-release-notes.yml new file mode 100644 index 000000000..9006a5daf --- /dev/null +++ b/.github/workflows/clean-release-notes.yml @@ -0,0 +1,37 @@ +name: Clean Release Notes + +on: + release: + types: [published] + +jobs: + clean-release-notes: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Remove ticket prefixes from release notes + uses: actions/github-script@v8 + with: + script: | + const release = context.payload.release; + + let body = release.body; + + if (!body) { + console.log("Release body empty, nothing to clean."); + return; + } + + // Remove ticket prefixes like "TPT-1234: " or "TPT-1234:" + body = body.replace(/TPT-\d+:\s*/g, ''); + + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id, + body: body + }); + + console.log("Release notes cleaned."); From c3a77de3f7afa4524efc96065bbc1f520cffb3a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:23:20 -0400 Subject: [PATCH 2/8] build(deps): bump actions/upload-artifact from 6 to 7 (#658) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' 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> --- .github/workflows/e2e-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 1298af319..50ee9205a 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -105,7 +105,7 @@ jobs: - name: Upload Test Report as Artifact if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: test-report-file if-no-files-found: ignore From fc2e33ce0f6b0f9d4fcd7360166fe73fc66d3b20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:21:38 -0400 Subject: [PATCH 3/8] build(deps): bump slackapi/slack-github-action from 2.1.1 to 3 (#668) * build(deps): bump slackapi/slack-github-action from 2.1.1 to 3.0.1 Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 2.1.1 to 3.0.1. - [Release notes](https://github.com/slackapi/slack-github-action/releases) - [Commits](https://github.com/slackapi/slack-github-action/compare/v2.1.1...v3.0.1) --- updated-dependencies: - dependency-name: slackapi/slack-github-action dependency-version: 3.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Apply suggestions from code review Co-authored-by: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> --- .github/workflows/e2e-test.yml | 4 ++-- .github/workflows/nightly-smoke-tests.yml | 2 +- .github/workflows/release-notify-slack.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 50ee9205a..8a02599cc 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -241,7 +241,7 @@ jobs: steps: - name: Notify Slack id: main_message - uses: slackapi/slack-github-action@v2.1.1 + uses: slackapi/slack-github-action@v3 with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} @@ -273,7 +273,7 @@ jobs: - name: Test summary thread if: success() - uses: slackapi/slack-github-action@v2.1.1 + uses: slackapi/slack-github-action@v3 with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/nightly-smoke-tests.yml b/.github/workflows/nightly-smoke-tests.yml index 644ea9ce4..c6697dc14 100644 --- a/.github/workflows/nightly-smoke-tests.yml +++ b/.github/workflows/nightly-smoke-tests.yml @@ -45,7 +45,7 @@ jobs: - name: Notify Slack if: always() && github.repository == 'linode/linode_api4-python' - uses: slackapi/slack-github-action@v2.1.1 + uses: slackapi/slack-github-action@v3 with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/release-notify-slack.yml b/.github/workflows/release-notify-slack.yml index 4b01f094b..aa21d80e5 100644 --- a/.github/workflows/release-notify-slack.yml +++ b/.github/workflows/release-notify-slack.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Notify Slack - Main Message id: main_message - uses: slackapi/slack-github-action@v2.1.1 + uses: slackapi/slack-github-action@v3 with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} From 02cd38353720e5ef63bc8da7074ccae10aa6b6ec Mon Sep 17 00:00:00 2001 From: shkaruna Date: Wed, 8 Apr 2026 23:38:38 +0530 Subject: [PATCH 4/8] Remove content field from list alert channels response (#675) Co-authored-by: Ye Chen <127243817+yec-akamai@users.noreply.github.com> --- linode_api4/groups/monitor.py | 2 +- linode_api4/objects/monitor.py | 22 +--------------- test/fixtures/monitor_alert-channels.json | 31 +++++++++++++++++++++++ test/unit/objects/monitor_test.py | 25 +++++++++++++++++- 4 files changed, 57 insertions(+), 23 deletions(-) create mode 100644 test/fixtures/monitor_alert-channels.json diff --git a/linode_api4/groups/monitor.py b/linode_api4/groups/monitor.py index 66943ade5..4c36f2e9c 100644 --- a/linode_api4/groups/monitor.py +++ b/linode_api4/groups/monitor.py @@ -202,7 +202,7 @@ def alert_channels(self, *filters) -> PaginatedList: .. note:: This endpoint is in beta and requires using the v4beta base URL. - API Documentation: https://techdocs.akamai.com/linode-api/reference/get-alert-channels + API Documentation: https://techdocs.akamai.com/linode-api/reference/get-notification-channels :param filters: Optional filter expressions to apply to the collection. See :doc:`Filtering Collections` for details. diff --git a/linode_api4/objects/monitor.py b/linode_api4/objects/monitor.py index ca8f83921..fea81c211 100644 --- a/linode_api4/objects/monitor.py +++ b/linode_api4/objects/monitor.py @@ -422,25 +422,6 @@ class AlertDefinition(DerivedBase): } -@dataclass -class EmailChannelContent(JSONObject): - """ - Represents the content for an email alert channel. - """ - - email_addresses: Optional[List[str]] = None - - -@dataclass -class ChannelContent(JSONObject): - """ - Represents the content block for an AlertChannel, which varies by channel type. - """ - - email: Optional[EmailChannelContent] = None - # Other channel types like 'webhook', 'slack' could be added here as Optional fields. - - @dataclass class EmailDetails(JSONObject): """ @@ -481,7 +462,7 @@ class AlertChannel(Base): fire. Alert channels define a destination and configuration for notifications (for example: email lists, webhooks, PagerDuty, Slack, etc.). - API Documentation: https://techdocs.akamai.com/linode-api/reference/get-alert-channels + API Documentation: https://techdocs.akamai.com/linode-api/reference/get-notification-channels This class maps to the Monitor API's `/monitor/alert-channels` resource and is used by the SDK to list, load, and inspect channels. @@ -499,7 +480,6 @@ class AlertChannel(Base): "channel_type": Property(), "details": Property(mutable=False, json_object=ChannelDetails), "alerts": Property(mutable=False, json_object=AlertInfo), - "content": Property(mutable=False, json_object=ChannelContent), "created": Property(is_datetime=True), "updated": Property(is_datetime=True), "created_by": Property(), diff --git a/test/fixtures/monitor_alert-channels.json b/test/fixtures/monitor_alert-channels.json new file mode 100644 index 000000000..753c53431 --- /dev/null +++ b/test/fixtures/monitor_alert-channels.json @@ -0,0 +1,31 @@ +{ + "data": [ + { + "id": 123, + "label": "alert notification channel", + "type": "user", + "channel_type": "email", + "details": { + "email": { + "usernames": [ + "admin-user1", + "admin-user2" + ], + "recipient_type": "user" + } + }, + "alerts": { + "url": "/monitor/alert-channels/123/alerts", + "type": "alerts-definitions", + "alert_count": 0 + }, + "created": "2024-01-01T00:00:00", + "updated": "2024-01-01T00:00:00", + "created_by": "tester", + "updated_by": "tester" + } + ], + "page": 1, + "pages": 1, + "results": 1 +} \ No newline at end of file diff --git a/test/unit/objects/monitor_test.py b/test/unit/objects/monitor_test.py index 329a09063..5913b3b28 100644 --- a/test/unit/objects/monitor_test.py +++ b/test/unit/objects/monitor_test.py @@ -1,7 +1,7 @@ import datetime from test.unit.base import ClientBaseCase -from linode_api4.objects import MonitorDashboard, MonitorService +from linode_api4.objects import AlertChannel, MonitorDashboard, MonitorService class MonitorTest(ClientBaseCase): @@ -146,3 +146,26 @@ def test_create_token(self): service_type="linode", entity_ids=["compute-instance-1"] ) self.assertEqual(m.return_dct["token"], "abcdefhjigkfghh") + + def test_alert_channels(self): + channels = self.client.monitor.alert_channels() + + self.assertEqual(len(channels), 1) + self.assertIsInstance(channels[0], AlertChannel) + self.assertEqual(channels[0].id, 123) + self.assertEqual(channels[0].label, "alert notification channel") + self.assertEqual(channels[0].type, "user") + self.assertEqual(channels[0].channel_type, "email") + self.assertIsNotNone(channels[0].details) + self.assertIsNotNone(channels[0].details.email) + self.assertEqual( + channels[0].details.email.usernames, + ["admin-user1", "admin-user2"], + ) + self.assertEqual(channels[0].details.email.recipient_type, "user") + self.assertIsNotNone(channels[0].alerts) + self.assertEqual( + channels[0].alerts.url, + "/monitor/alert-channels/123/alerts", + ) + self.assertEqual(channels[0].alerts.alert_count, 0) From ecb5c3cc4a59b2fde64a1b436c76b24c5259acc1 Mon Sep 17 00:00:00 2001 From: shkaruna Date: Thu, 9 Apr 2026 19:04:29 +0530 Subject: [PATCH 5/8] feat: add ACLP list entities method (#674) * feat: add ACLP list entities method Add entities envelope in AlertDefinition. Add list entities GET API method. Add tests. * reorder scope and regions check * update alert definition object --- linode_api4/groups/monitor.py | 54 +++++++++++++- linode_api4/objects/monitor.py | 74 +++++++++++++------ test/fixtures/monitor_alert-definitions.json | 17 ++++- ...itor_services_dbaas_alert-definitions.json | 17 ++++- ...ervices_dbaas_alert-definitions_12345.json | 16 +++- ...baas_alert-definitions_12345_entities.json | 25 +++++++ .../models/monitor/test_monitor.py | 38 +++++++++- test/unit/groups/monitor_api_test.py | 64 ++++++++++++++++ 8 files changed, 274 insertions(+), 31 deletions(-) create mode 100644 test/fixtures/monitor_services_dbaas_alert-definitions_12345_entities.json diff --git a/linode_api4/groups/monitor.py b/linode_api4/groups/monitor.py index 4c36f2e9c..0d7f19ce8 100644 --- a/linode_api4/groups/monitor.py +++ b/linode_api4/groups/monitor.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any, Optional, Union from linode_api4 import PaginatedList from linode_api4.errors import UnexpectedResponseError @@ -6,6 +6,8 @@ from linode_api4.objects import ( AlertChannel, AlertDefinition, + AlertDefinitionEntity, + AlertScope, MonitorDashboard, MonitorMetricsDefinition, MonitorService, @@ -221,6 +223,8 @@ def create_alert_definition( trigger_conditions: dict, entity_ids: Optional[list[str]] = None, description: Optional[str] = None, + scope: Optional[Union[AlertScope, str]] = None, + regions: Optional[list[str]] = None, ) -> AlertDefinition: """ Create a new alert definition for a given service type. @@ -252,6 +256,10 @@ def create_alert_definition( :type entity_ids: Optional[list[str]] :param description: (Optional) Longer description for the alert definition. :type description: Optional[str] + :param scope: (Optional) Alert scope (for example: `account`, `entity`, or `region`). Defaults to `entity`. + :type scope: Optional[Union[AlertScope, str]] + :param regions: (Optional) Regions to monitor. + :type regions: Optional[list[str]] :returns: The newly created :class:`AlertDefinition`. :rtype: AlertDefinition @@ -267,10 +275,15 @@ def create_alert_definition( "rule_criteria": rule_criteria, "trigger_conditions": trigger_conditions, } - if description is not None: - params["description"] = description + if entity_ids is not None: params["entity_ids"] = entity_ids + if description is not None: + params["description"] = description + if scope is not None: + params["scope"] = scope + if regions is not None: + params["regions"] = regions # API will validate service_type and return an error if missing result = self.client.post( @@ -284,3 +297,38 @@ def create_alert_definition( ) return AlertDefinition(self.client, result["id"], service_type, result) + + def alert_definition_entities( + self, + service_type: str, + id: int, + *filters, + ) -> PaginatedList: + """ + List entities associated with a specific alert definition. + + This endpoint supports pagination fields (`page`, `page_size`) in the API. + + .. note:: This endpoint is in beta and requires using the v4beta base URL. + + API Documentation: TODO + + :param service_type: Service type for the alert definition (e.g. `dbaas`). + :type service_type: str + :param id: Alert definition identifier. + :type id: int + :param filters: Optional filter expressions to apply to the collection. + See :doc:`Filtering Collections`. + + :returns: A paginated list of entities associated with the alert definition. + :rtype: PaginatedList[AlertDefinitionEntity] + """ + + endpoint = ( + f"/monitor/services/{service_type}/alert-definitions/{id}/entities" + ) + return self.client._get_and_filter( + AlertDefinitionEntity, + *filters, + endpoint=endpoint, + ) diff --git a/linode_api4/objects/monitor.py b/linode_api4/objects/monitor.py index fea81c211..1a83b59d6 100644 --- a/linode_api4/objects/monitor.py +++ b/linode_api4/objects/monitor.py @@ -7,11 +7,13 @@ __all__ = [ "AggregateFunction", - "Alert", "AlertChannel", "AlertDefinition", + "AlertDefinitionChannel", + "AlertDefinitionEntity", + "AlertEntities", + "AlertScope", "AlertType", - "Alerts", "MonitorDashboard", "MonitorMetricsDefinition", "MonitorService", @@ -341,15 +343,15 @@ class RuleCriteria(JSONObject): @dataclass -class Alert(JSONObject): +class AlertDefinitionChannel(JSONObject): """ - Represents an alert definition reference within an AlertChannel. + Represents the notification channel set up for use with an alert. Fields: - - id: int - Unique identifier of the alert definition. - - label: str - Human-readable name for the alert definition. - - type: str - Type of the alert (e.g., 'alerts-definitions'). - - url: str - API URL for the alert definition. + - id: int - Unique identifier for this notification channel. + - label: str - Human-readable name for the alert channel. + - type: str - Type of notification used with the channel. For a user alert definition, only `email` is supported. + - url: str - URL for the channel that ends in the channel's id. """ id: int = 0 @@ -358,18 +360,6 @@ class Alert(JSONObject): url: str = "" -@dataclass -class Alerts(JSONObject): - """ - Represents a collection of alert definitions within an AlertChannel. - - Fields: - - items: List[Alert] - List of alert definitions. - """ - - items: List[Alert] = field(default_factory=list) - - class AlertType(StrEnum): """ Enumeration of alert origin types used by alert definitions. @@ -387,6 +377,43 @@ class AlertType(StrEnum): user = "user" +class AlertScope(StrEnum): + """ + Scope values supported for alert definitions. + """ + + entity = "entity" + region = "region" + account = "account" + + +@dataclass +class AlertEntities(JSONObject): + """ + Represents entity metadata for an alert definition. + + For entity scoped alerts, `entities` envelope contains the URL to list entities, + a count, and a has_more_resources flag. + For region/account scoped alerts, the `entities` are returned as an empty object. + """ + + url: str = "" + count: int = 0 + has_more_resources: bool = False + + +@dataclass +class AlertDefinitionEntity(JSONObject): + """ + Represents an entity associated with an alert definition. + """ + + id: str = "" + label: str = "" + url: str = "" + _type: str = field(default="", metadata={"json_key": "type"}) + + class AlertDefinition(DerivedBase): """ Represents an alert definition for a monitor service. @@ -406,12 +433,12 @@ class AlertDefinition(DerivedBase): "severity": Property(mutable=True), "type": Property(mutable=True), "status": Property(mutable=True), - "has_more_resources": Property(mutable=True), + "has_more_resources": Property(), # Deprecated; use entities.has_more_resources. "rule_criteria": Property(mutable=True, json_object=RuleCriteria), "trigger_conditions": Property( mutable=True, json_object=TriggerConditions ), - "alert_channels": Property(mutable=True, json_object=Alerts), + "alert_channels": Property(json_object=AlertDefinitionChannel), "created": Property(is_datetime=True), "updated": Property(is_datetime=True), "updated_by": Property(), @@ -419,6 +446,9 @@ class AlertDefinition(DerivedBase): "entity_ids": Property(mutable=True), "description": Property(mutable=True), "service_class": Property(alias_of="class"), + "scope": Property(AlertScope), + "regions": Property(mutable=True), + "entities": Property(json_object=AlertEntities), } diff --git a/test/fixtures/monitor_alert-definitions.json b/test/fixtures/monitor_alert-definitions.json index 92b6e0e4c..e500354d1 100644 --- a/test/fixtures/monitor_alert-definitions.json +++ b/test/fixtures/monitor_alert-definitions.json @@ -7,13 +7,26 @@ "severity": 1, "type": "user", "description": "A test alert for dbaas service", + "scope": "entity", + "regions": [], "entity_ids": ["13217"], - "alert_channels": [], + "entities": { + "url": "/monitor/services/dbaas/alert-definitions/12345/entities", + "count": 1, + "has_more_resources": false + }, + "alert_channels": [ + { + "id": 10000, + "label": "Read-Write Channel", + "type": "email", + "url": "/monitor/alert-channels/10000" + } + ], "has_more_resources": false, "rule_criteria": null, "trigger_conditions": null, "class": "alert", - "notification_groups": [], "status": "active", "created": "2024-01-01T00:00:00", "updated": "2024-01-01T00:00:00", diff --git a/test/fixtures/monitor_services_dbaas_alert-definitions.json b/test/fixtures/monitor_services_dbaas_alert-definitions.json index 0c7067a8a..494b407d4 100644 --- a/test/fixtures/monitor_services_dbaas_alert-definitions.json +++ b/test/fixtures/monitor_services_dbaas_alert-definitions.json @@ -7,10 +7,24 @@ "severity": 1, "type": "user", "description": "A test alert for dbaas service", + "scope": "entity", + "regions": [], "entity_ids": [ "13217" ], - "alert_channels": [], + "entities": { + "url": "/monitor/services/dbaas/alert-definitions/12345/entities", + "count": 1, + "has_more_resources": false + }, + "alert_channels": [ + { + "id": 10000, + "label": "Read-Write Channel", + "type": "email", + "url": "/monitor/alert-channels/10000" + } + ], "has_more_resources": false, "rule_criteria": { "rules": [ @@ -39,7 +53,6 @@ "trigger_occurrences": 3 }, "class": "alert", - "notification_groups": [], "status": "active", "created": "2024-01-01T00:00:00", "updated": "2024-01-01T00:00:00", diff --git a/test/fixtures/monitor_services_dbaas_alert-definitions_12345.json b/test/fixtures/monitor_services_dbaas_alert-definitions_12345.json index 822e18b24..4b6a76272 100644 --- a/test/fixtures/monitor_services_dbaas_alert-definitions_12345.json +++ b/test/fixtures/monitor_services_dbaas_alert-definitions_12345.json @@ -5,10 +5,24 @@ "severity": 1, "type": "user", "description": "A test alert for dbaas service", + "scope": "entity", + "regions": [], "entity_ids": [ "13217" ], - "alert_channels": [], + "entities": { + "url": "/monitor/services/dbaas/alert-definitions/12345/entities", + "count": 1, + "has_more_resources": false + }, + "alert_channels": [ + { + "id": 10000, + "label": "Read-Write Channel", + "type": "email", + "url": "/monitor/alert-channels/10000" + } + ], "has_more_resources": false, "rule_criteria": { "rules": [ diff --git a/test/fixtures/monitor_services_dbaas_alert-definitions_12345_entities.json b/test/fixtures/monitor_services_dbaas_alert-definitions_12345_entities.json new file mode 100644 index 000000000..16dad4b7c --- /dev/null +++ b/test/fixtures/monitor_services_dbaas_alert-definitions_12345_entities.json @@ -0,0 +1,25 @@ +{ + "data": [ + { + "id": "1", + "label": "mydatabase-1", + "url": "/v4/databases/mysql/instances/1", + "type": "dbaas" + }, + { + "id": "2", + "label": "mydatabase-2", + "url": "/v4/databases/mysql/instances/2", + "type": "dbaas" + }, + { + "id": "3", + "label": "mydatabase-3", + "url": "/v4/databases/mysql/instances/3", + "type": "dbaas" + } + ], + "page": 1, + "pages": 1, + "results": 3 +} diff --git a/test/integration/models/monitor/test_monitor.py b/test/integration/models/monitor/test_monitor.py index 908ac1a44..ceb9fdc3a 100644 --- a/test/integration/models/monitor/test_monitor.py +++ b/test/integration/models/monitor/test_monitor.py @@ -7,9 +7,10 @@ import pytest -from linode_api4 import LinodeClient +from linode_api4 import LinodeClient, PaginatedList from linode_api4.objects import ( AlertDefinition, + AlertDefinitionEntity, ApiError, MonitorDashboard, MonitorMetricsDefinition, @@ -256,12 +257,14 @@ def wait_for_alert_ready(alert_id, service_type: str): assert created.id assert getattr(created, "label", None) == label + assert getattr(created, "entities", None) is not None created = wait_for_alert_ready(created.id, service_type) updated = client.load(AlertDefinition, created.id, service_type) updated.label = f"{label}-updated" updated.save() + assert getattr(updated, "entities", None) is not None updated = wait_for_alert_ready(updated.id, service_type) @@ -275,3 +278,36 @@ def wait_for_alert_ready(alert_id, service_type: str): AlertDefinition, created.id, service_type ) delete_alert.delete() + + +def test_alert_definition_entities(test_linode_client): + """Test listing entities associated with an alert definition. + + This test first retrieves alert definitions for a service type, then lists entities for the first alert definition. + It asserts that the returned entities have expected fields. + """ + client = test_linode_client + service_type = "dbaas" + + alert_definitions = client.monitor.alert_definitions( + service_type=service_type + ) + + if len(alert_definitions) == 0: + pytest.fail("No alert definitions available for dbaas service type") + + assert getattr(alert_definitions[0], "entities", None) is not None + + alert_def = alert_definitions[0] + entities = client.monitor.alert_definition_entities( + service_type, alert_def.id + ) + + assert isinstance(entities, PaginatedList) + if len(entities) > 0: + entity = entities[0] + assert isinstance(entity, AlertDefinitionEntity) + assert entity.id + assert entity.label + assert entity.url + assert entity._type == service_type diff --git a/test/unit/groups/monitor_api_test.py b/test/unit/groups/monitor_api_test.py index 9515895ae..fdc93060c 100644 --- a/test/unit/groups/monitor_api_test.py +++ b/test/unit/groups/monitor_api_test.py @@ -4,6 +4,8 @@ from linode_api4.objects import ( AggregateFunction, AlertDefinition, + AlertDefinitionChannel, + AlertDefinitionEntity, EntityMetricOptions, ) @@ -71,6 +73,20 @@ def test_alert_definition(self): # assert collection and element types assert isinstance(alert, PaginatedList) assert isinstance(alert[0], AlertDefinition) + assert alert[0].scope == "entity" + assert alert[0].regions == [] + assert alert[0].entities.url.endswith( + "/alert-definitions/12345/entities" + ) + assert alert[0].entities.count == 1 + assert alert[0].entities.has_more_resources is False + assert isinstance(alert[0].alert_channels, list) + assert len(alert[0].alert_channels) == 1 + assert isinstance( + alert[0].alert_channels[0], AlertDefinitionChannel + ) + assert alert[0].alert_channels[0].id == 10000 + assert alert[0].alert_channels[0]._type == "email" # fetch the raw JSON from the client and assert its fields raw = self.client.get(url) @@ -90,6 +106,11 @@ def test_create_alert_definition(self): "service_type": service_type, "severity": 1, "status": "active", + "entities": { + "url": f"/monitor/services/dbaas/alert-definitions/67890/entities", + "count": 1, + "has_more_resources": False, + }, } with self.mock_post(result) as mock_post: @@ -100,6 +121,8 @@ def test_create_alert_definition(self): channel_ids=[1, 2], rule_criteria={"rules": []}, trigger_conditions={"criteria_condition": "ALL"}, + scope="entity", + regions=[], entity_ids=["13217"], description="created via test", ) @@ -109,10 +132,51 @@ def test_create_alert_definition(self): assert mock_post.call_data["label"] == "Created Alert" assert mock_post.call_data["severity"] == 1 assert "channel_ids" in mock_post.call_data + assert mock_post.call_data["scope"] == "entity" + assert mock_post.call_data["regions"] == [] assert isinstance(alert, AlertDefinition) assert alert.id == 67890 + assert alert.entities.url.endswith( + "/alert-definitions/67890/entities" + ) + assert alert.entities.count == 1 + assert alert.entities.has_more_resources is False # fetch the same response from the client and assert resp = self.client.post(url, data={}) assert resp["label"] == "Created Alert" + + def test_alert_definition_entities(self): + service_type = "dbaas" + id = 12345 + url = ( + f"/monitor/services/{service_type}/alert-definitions/{id}/entities" + ) + + with self.mock_get(url) as mock_get: + entities = self.client.monitor.alert_definition_entities( + service_type, id + ) + + assert mock_get.call_url == url + assert isinstance(entities, PaginatedList) + assert len(entities) == 3 + + assert isinstance(entities[0], AlertDefinitionEntity) + assert entities[0].id == "1" + assert entities[0].label == "mydatabase-1" + assert entities[0].url == "/v4/databases/mysql/instances/1" + assert entities[0]._type == "dbaas" + + assert isinstance(entities[1], AlertDefinitionEntity) + assert entities[1].id == "2" + assert entities[1].label == "mydatabase-2" + assert entities[1].url == "/v4/databases/mysql/instances/2" + assert entities[1]._type == "dbaas" + + assert isinstance(entities[2], AlertDefinitionEntity) + assert entities[2].id == "3" + assert entities[2].label == "mydatabase-3" + assert entities[2].url == "/v4/databases/mysql/instances/3" + assert entities[2]._type == "dbaas" From 206a792289272ae8027182d627989dcff56df019 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:25:28 -0400 Subject: [PATCH 6/8] Cleanup block storage encryption LA notice (#655) --- linode_api4/groups/linode.py | 1 - linode_api4/groups/volume.py | 1 - linode_api4/objects/linode.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/linode_api4/groups/linode.py b/linode_api4/groups/linode.py index 2bd51fa97..a433a0dcf 100644 --- a/linode_api4/groups/linode.py +++ b/linode_api4/groups/linode.py @@ -322,7 +322,6 @@ def instance_create( :param firewall: The firewall to attach this Linode to. :type firewall: int or Firewall :param disk_encryption: The disk encryption policy for this Linode. - NOTE: Disk encryption may not currently be available to all users. :type disk_encryption: InstanceDiskEncryptionType or str :param interfaces: An array of Network Interfaces to add to this Linode’s Configuration Profile. At least one and up to three Interface objects can exist in this array. diff --git a/linode_api4/groups/volume.py b/linode_api4/groups/volume.py index 39d0aeaaa..847c3030c 100644 --- a/linode_api4/groups/volume.py +++ b/linode_api4/groups/volume.py @@ -48,7 +48,6 @@ def create(self, label, region=None, linode=None, size=20, **kwargs): :type tags: list[str] :param encryption: Whether the new Volume should opt in or out of disk encryption. :type encryption: str - Note: Block Storage Disk Encryption is not currently available to all users. :returns: The new Volume. :rtype: Volume """ diff --git a/linode_api4/objects/linode.py b/linode_api4/objects/linode.py index 9b76668e3..b8b0fc9dc 100644 --- a/linode_api4/objects/linode.py +++ b/linode_api4/objects/linode.py @@ -1448,7 +1448,6 @@ def disk_create( should already be set up, see :any:`ProfileGroup.ssh_keys` for details. :param disk_encryption: The disk encryption policy for this Linode. - NOTE: Disk encryption may not currently be available to all users. :type disk_encryption: InstanceDiskEncryptionType or str :param stackscript: A StackScript object, or the ID of one, to deploy to this disk. Requires deploying a compatible image. @@ -1642,7 +1641,6 @@ def rebuild( the key. :type authorized_keys: list or str :param disk_encryption: The disk encryption policy for this Linode. - NOTE: Disk encryption may not currently be available to all users. :type disk_encryption: InstanceDiskEncryptionType or str :returns: The newly generated password, if one was not provided From c39c86850e132997ab3012a8475c34274cfb0f8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:54:14 -0400 Subject: [PATCH 7/8] build(deps): bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0 (#681) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e...cef221092ed1bacb1cc03d23a2d87d1d172e277b) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.14.0 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> --- .github/workflows/publish-pypi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-pypi.yaml b/.github/workflows/publish-pypi.yaml index a791be4c9..86d854455 100644 --- a/.github/workflows/publish-pypi.yaml +++ b/.github/workflows/publish-pypi.yaml @@ -28,4 +28,4 @@ jobs: LINODE_SDK_VERSION: ${{ github.event.release.tag_name }} - name: Publish the release artifacts to PyPI - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # pin@release/v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # pin@release/v1.14.0 From 9535d635d19325db2511b2388b528c818e72b1b9 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:56:39 -0400 Subject: [PATCH 8/8] Use e2e firewall in all linode interface tests (#685) --- test/integration/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/conftest.py b/test/integration/conftest.py index a5c832f4f..0058dfcec 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -16,7 +16,6 @@ from requests.exceptions import ConnectionError, RequestException from linode_api4 import ( - ExplicitNullValue, InterfaceGeneration, LinodeInterfaceDefaultRouteOptions, LinodeInterfaceOptions, @@ -645,7 +644,7 @@ def linode_with_linode_interfaces( public=LinodeInterfacePublicOptions(), ), LinodeInterfaceOptions( - firewall_id=ExplicitNullValue, + firewall_id=e2e_test_firewall.id, vpc=LinodeInterfaceVPCOptions( subnet_id=subnet.id, ),