Repository: kyma-project/test-infra
Pull request #12913: Rewrite validate message to return bool and not raise error
Author: login: KacperMalachowski
Labels: name: add-or-update
Created: 2025-04-08T05:35:11Z | Merged: 2025-04-15T20:27:31Z

<!--   Thank you for your contribution. Before you submit the pull request:
1. Follow contributing guidelines, templates, the recommended Git workflow, and any related documentation.
2. Read and submit the required Contributor Licence Agreements (https://github.com/kyma-project/community/blob/main/docs/contributing/02-contributing.md).
3. Test your changes and attach their results to the pull request.
4. Update the relevant documentation.
-->

**Description**

Changes proposed in this pull request:

- Add unit tests
- Rewrite `validate_message` so it log reason when image is invalid and then returns False
- Stop throwing error when we should ismply ignore the message

**Related issue(s)**
<!-- If you refer to a particular issue, provide its number. For example, `Resolves #123`, `Fixes #43`, or `See also #33`. -->
See: #12904 

Linked issue #123: Upgrade deprecated image in provision-vm script
**Description**
In the `provision-vm.sh` script we are using a hardcoded debian image `debian-9-stretch-v20181011`, which as for now is deprecated:

```
WARNING: Some requests generated warnings:
 - The resource 'projects/debian-cloud/global/images/debian-9-stretch-v20181011' is deprecated. A suggested replacement is 'projects/debian-cloud/global/images/debian-9-stretch-v20181113'.
```

We should use a custom made image, or use a floating tag (like `latest` in docker) to guard from a problem like this.  
Linked issue comments: user: login: suleymanakbas91
created_at: 2018-11-16T15:12:59Z
body: This issue will be solved once we have a custom image. 
author_association: CONTRIBUTOR
user: login: LudovicTOURMAN
created_at: 2018-11-28T18:07:18Z
body: You could also use the family tag `debian-9` which point to the latest non-obsolete image.
author_association: NONE

Diff:
diff --git a/.gitignore b/.gitignore
index c942f9e13fe7..6f198ccaa7d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,6 @@ __debug_bin
 
 # Ignore generated credentials from google-github-actions/auth
 gha-creds-*.json
+
+# Python
+__pycache__/
diff --git a/cmd/cloud-run/signifysecretrotator/messagevalidator.py b/cmd/cloud-run/signifysecretrotator/messagevalidator.py
new file mode 100644
index 000000000000..150f135da991
--- /dev/null
+++ b/cmd/cloud-run/signifysecretrotator/messagevalidator.py
@@ -0,0 +1,53 @@
+"""Package that contains the message validator for the Signify Secret Rotator."""
+
+
+class SecretTypeError(Exception):
+    """Custom exception for invalid secret type."""
+
+    def __init__(self, message: str, received_type: str) -> None:
+        super().__init__(message)
+        self.message = message
+        self.received_type = received_type
+
+
+class EventTypeError(Exception):
+    """Custom exception for invalid event type."""
+
+    def __init__(self, message: str, received_event_type: str) -> None:
+        super().__init__(message)
+        self.message = message
+        self.received_event_type = received_event_type
+
+
+# MessageValidator class verifies the message type and event type
+# it don't require few public methods, one is sufficient
+# Ignoring pylint error for too few public methods
+# pylint: disable=too-few-public-methods
+class MessageValidator:
+    """Class that validates the message received from Pub/Sub.
+    It checks if the secret type is valid based on the provided secret type."""
+
+    def __init__(self, valid_secret_type: str) -> None:
+        self.valid_secret_type = valid_secret_type
+
+    def validate(self, message: dict) -> None:
+        """
+        Validates the message received from Pub/Sub.
+        Raises an exception when message is invalid
+        """
+
+        self._verify_secret_type(message)
+
+    def _verify_secret_type(self, message: dict) -> None:
+        """
+        Verifies if the secret type is valid based on provied secret type.
+        Raises an exception when secret type is invalid.
+        Secret type is defined as a type label in the secret manager
+        """
+
+        secret_type = message.get("labels", {}).get("type")
+        if secret_type != self.valid_secret_type:
+            raise SecretTypeError(
+                f"Secret type {secret_type} is not valid. Expected {self.valid_secret_type}",
+                secret_type,
+            )
diff --git a/cmd/cloud-run/signifysecretrotator/requirements.txt b/cmd/cloud-run/signifysecretrotator/requirements.txt
index 04388a70940d..c181a6d336f6 100644
--- a/cmd/cloud-run/signifysecretrotator/requirements.txt
+++ b/cmd/cloud-run/signifysecretrotator/requirements.txt
@@ -1,5 +1,4 @@
 flask==3.1.0
-cloudevents==1.11.0
 gunicorn==23.0.0
 google-cloud-secret-manager==2.23.2
 cryptography==44.0.2
\ No newline at end of file
diff --git a/cmd/cloud-run/signifysecretrotator/signifysecretrotator.py b/cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
index 177de4a0c570..209622de9639 100644
--- a/cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
+++ b/cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
@@ -1,11 +1,11 @@
 """Simple PubSub handler to rotate signify certificates"""
 
 import datetime
-import logging
+import http
 import os
 import base64
 import json
-import sys
+
 from typing import Any, Dict, List
 from flask import Flask, Response, request, make_response
 from cryptography import x509
@@ -16,6 +16,7 @@
 from secretmanager.client import SecretManagerClient, SecretManagerError
 from pylogger.logger import create_logger
 from signify.client import SignifyClient
+from messagevalidator import EventTypeError, MessageValidator, SecretTypeError
 
 app = Flask(__name__)
 project_id: str = os.getenv("PROJECT_ID", "sap-kyma-prow")
@@ -42,7 +43,7 @@ def rotate_signify_secret() -> Response:
 
         secret_rotate_msg: Dict[str, Any] = extract_message_data(pubsub_message)
 
-        validate_message(secret_rotate_msg)
+        MessageValidator(secret_rotate_message_type).validate(secret_rotate_msg)
 
         secret_id: str = secret_rotate_msg["name"]
         secret_data: Dict[str, Any] = sm_client.get_secret(secret_id)
@@ -90,18 +91,23 @@ def rotate_signify_secret() -> Response:
 
         logger.info("Certificate rotated successfully at %s", created_at)
 
-        return "Certificate rotated successfully"
+        return prepare_response("Certificate rotated successfully", 200)
     except (HTTPError, ValueError, TypeError, SecretManagerError) as exc:
-        return prepare_error_response(exc, logger)
-
-
-def validate_message(message: dict[str, Any]) -> None:
-    """Raises error when received message struct is invalid"""
-
-    # Pub/Sub topic handle multiple secret rotator components
-    # verify if we should handle that message
-    if message.get("labels", {}).get("type") != secret_rotate_message_type:
-        raise TypeError("Invalid or unknown type value")
+        return prepare_response(str(exc), 500)
+    except EventTypeError as exc:
+        # We consider it as an error
+        # We can filter the event type on the Pub/Sub level,
+        # so we shouldn't receive it in the first place
+        logger.error("Received unsupported event type: %s", exc.received_event_type)
+        return prepare_response(
+            f"Unsupported event type received {exc.received_event_type}", 400
+        )
+    except SecretTypeError as exc:
+        # We do not consider it as an error
+        # We cannot filter the message on the Pub/Sub level,
+        # so we consider handling it as a future of signify secret rotator service
+        logger.info("Received unsupported secret type: %s", exc.received_type)
+        return prepare_response("Unsupported secret type received", 200)
 
 
 def prepare_new_secret(
@@ -137,8 +143,9 @@ def prepare_new_secret(
 
 def extract_message_data(pubsub_message: Any) -> Any:
     """Extracts secret rotation message from the Pub/Sub message."""
-    if pubsub_message.get("attributes", {}).get("eventType") != "SECRET_ROTATE":
-        raise ValueError("Unsupported event type")
+    event_type = pubsub_message.get("attributes", {}).get("eventType")
+    if event_type != "SECRET_ROTATE":
+        raise EventTypeError(f"Unsupported event type, got {event_type}", event_type)
 
     data = base64.b64decode(pubsub_message["data"])
     return json.loads(data)
@@ -168,11 +175,15 @@ def get_pubsub_message() -> Dict[str, Any]:
     return envelope["message"]
 
 
-# TODO(kacpermalachowski): Move it to common package
-def prepare_error_response(err: str, logger: logging.Logger) -> Response:
+def prepare_response(message: str, status: int) -> Response:
     """Prepares an error response with logging."""
-    logger.error(err, exc_info=sys.exc_info())
     resp = make_response()
     resp.content_type = "application/json"
-    resp.status_code = 500
+    resp.status_code = status
+    resp.data = json.dumps(
+        {
+            "message": message,
+            "status": "success" if http.HTTPStatus(status).is_success else "error",
+        }
+    )
     return resp
diff --git a/cmd/cloud-run/signifysecretrotator/test_messagevalidator.py b/cmd/cloud-run/signifysecretrotator/test_messagevalidator.py
new file mode 100644
index 000000000000..332245068777
--- /dev/null
+++ b/cmd/cloud-run/signifysecretrotator/test_messagevalidator.py
@@ -0,0 +1,35 @@
+"""Package for tests cases for message vlaidator class"""
+
+import unittest
+from messagevalidator import (
+    MessageValidator,
+    SecretTypeError,
+)
+
+
+class TestMessageValidator(unittest.TestCase):
+    """Test cases for the MessageValidator class."""
+
+    def test_raises_invalid_message_type_on_not_expected_message_type(self):
+        """Test that an InvalidMessageTypeError is raised when the message type is not expected."""
+
+        message = {"labels": {"type": "un"}}
+        validator = MessageValidator("expected_type")
+
+        with self.assertRaises(SecretTypeError):
+            validator.validate(message)
+
+    def test_pass_valid_message_type_without_error(self):
+        """Test that no error is raised when the message type is valid."""
+
+        message = {"labels": {"type": "expected_type"}}
+        validator = MessageValidator("expected_type")
+
+        try:
+            validator.validate(message)
+        except SecretTypeError:
+            self.fail("MessageTypeError was raised unexpectedly!")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/cmd/cloud-run/signifysecretrotator/test_signifysecretrotator.py b/cmd/cloud-run/signifysecretrotator/test_signifysecretrotator.py
new file mode 100644
index 000000000000..3ffff619a309
--- /dev/null
+++ b/cmd/cloud-run/signifysecretrotator/test_signifysecretrotator.py
@@ -0,0 +1,198 @@
+"""Package containing tests for the Signify Secret Rotator."""
+
+import base64
+import json
+import unittest
+from unittest.mock import patch
+
+
+from flask import Flask
+from cryptography import x509
+
+from signifysecretrotator import (
+    rotate_signify_secret,
+    SecretManagerClient,
+    SignifyClient,
+)
+
+
+class TestSignifySecretRotator(unittest.TestCase):
+    """Tests for the Signify Secret Rotator.
+    Tests the functionality of the Signify Secret Rotator, including the
+    validation of messages, the rotation of secrets, and the handling of
+    errors. It mocks the Google Cloud Secret Manager and Signify API to
+    simulate the behavior of these services.
+    """
+
+    def setUp(self):
+        """Set up the test case."""
+        # Mock the Google Cloud Secret Manager
+        access_secret_patcher = patch.object(SecretManagerClient, "get_secret")
+        add_secret_version_patcher = patch.object(
+            SecretManagerClient, "add_secret_version"
+        )
+        self.mock_access_secret = access_secret_patcher.start()
+        self.mock_add_secret_version = add_secret_version_patcher.start()
+
+        # Mock the Signify client
+        fetch_access_token_patcher = patch.object(SignifyClient, "fetch_access_token")
+        fetch_new_certificate_patcher = patch.object(
+            SignifyClient, "fetch_new_certificate"
+        )
+
+        self.mock_fetch_access_token = fetch_access_token_patcher.start()
+        self.mock_fetch_new_certificate = fetch_new_certificate_patcher.start()
+
+        # Mock the logger
+        self.logger = patch("pylogger.logger.create_logger", autospec=True).start()
+
+        # Mock the Flask request
+        self.app = Flask(__name__)
+        self.app.config["TESTING"] = True
+
+    def tearDown(self):
+        """Tear down the test case."""
+        patch.stopall()
+
+    def test_rotate_signify_secret_success(self):
+        """Test the rotation of a Signify secret."""
+        # Mock the response from the Secret Manager.
+        self.mock_access_secret.return_value = {
+            "name": "projects/test/secrets/test",
+            "tokenURL": "https://example.com/token",
+            "certServiceURL": "https://example.com/cert",
+            "clientID": "test-client-id",
+            "certData": base64.b64encode(b"test-cert").decode("utf-8"),
+            "privateKeyData": base64.b64encode(b"test-key").decode("utf-8"),
+        }
+
+        # Mock the response from the Signify client.
+        self.mock_fetch_access_token.return_value = "test-access-token"
+        self.mock_fetch_new_certificate.return_value = [
+            x509.load_pem_x509_certificate(
+                base64.b64decode(
+                    # pylint: disable=line-too-long
+                    "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY3ekNDQTllZ0F3SUJBZ0lVYUlqenlHUytCUFFJZ0thYlhoU2xhTTk0dGpNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZWXhDekFKQmdOVkJBWVRBbGhZTVJJd0VBWURWUVFJREFsVGRHRjBaVTVoYldVeEVUQVBCZ05WQkFjTQpDRU5wZEhsT1lXMWxNUlF3RWdZRFZRUUtEQXREYjIxd1lXNTVUbUZ0WlRFYk1Ca0dBMVVFQ3d3U1EyOXRjR0Z1CmVWTmxZM1JwYjI1T1lXMWxNUjB3R3dZRFZRUUREQlJEYjIxdGIyNU9ZVzFsVDNKSWIzTjBibUZ0WlRBZUZ3MHkKTkRBNU1USXdPRFUwTWpkYUZ3MHpOREE1TVRBd09EVTBNamRhTUlHR01Rc3dDUVlEVlFRR0V3SllXREVTTUJBRwpBMVVFQ0F3SlUzUmhkR1ZPWVcxbE1SRXdEd1lEVlFRSERBaERhWFI1VG1GdFpURVVNQklHQTFVRUNnd0xRMjl0CmNHRnVlVTVoYldVeEd6QVpCZ05WQkFzTUVrTnZiWEJoYm5sVFpXTjBhVzl1VG1GdFpURWRNQnNHQTFVRUF3d1UKUTI5dGJXOXVUbUZ0WlU5eVNHOXpkRzVoYldVd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJSwpBb0lDQVFEa01hSWJzREhKSXBmZ3lOeUp2UWUycXZTS1ZhOWhSWGQ3SFB2SG1nZjJxUmkyejN6YmVRc0xuWnk1CmhkOE1LZnVHR25kb0FwdC9SR3RYRmgzTlowcVU2R1pIL2UxNWZWQmpqZW8rd3NtWHRXVmUrMGZuTE5QWXByT0IKdnFhZmYybUZmaytucUJCWE9XRExHV2FEclNrR0gvZ202WnZVOWNhNDh6YlRiOE0rRVc4T3VuWTRyTTZJUEcxNQpxSGJlRjRhMFF2WFJmbSswMytCbEJlUHFjWjg1ZEpjNnkvd1Qyajc0ckEyS1RLdWdrczUyUitGc0hOYS9jbEpTCko5SmlsOEdOa1RObmtRaGErZDZRcVpyUzBuTW4vSXJvdDlBdDN2Z0FWV2Qrb29DVWNxTUFxdURiVE1jRzhYUi8KcHI4bkl5bk5KVFhzYnJsSVZQL1BOaGdhZ09ra0gzYVFzVTIzMy9RUTFCNWlaMUpqMjhaSi9ycjBKZDV4TlJncwpoS3YzNUZxdzJ3SWFqT0tuWFBYaElHS2J0b0c0N09FSEJabGJvTTV6bHpqSCtPVERLSXY2NGg1dTBoWEt5ckZ1CjU5RTBTUFZsMEprYjRITVdtMEtzbGRiSjIzYUVsbmNIdlVBZjVWek01STlhMVVEYThjRW9RdTUrb0thaWVoaW4KTGE2QmNxUHM0R3pRQUdiTEpLdStTYk83SDR4aFJRanN3blY3WWkyVHJFbytiWDFIZXBhZ3oyL05MMS9iSlNSTgpNVmlmOGhRMFRaaHlxY1VGSWFhYmFyVldGb2RZVWFxeFRuUHIzdUM5L2pZak9Vd0tOSG9ySWhqejBIR005cHY3CjRUT0NmR01TY1RYQWo4Y1RqR0RUTjladjRQMGMrOFVycEx0ZjJOVldNUGtIczh3QzF3SURBUUFCbzFNd1VUQWQKQmdOVkhRNEVGZ1FVRHhtQmdjSVJpbVRDaGRkMHBoYnhBcjhZM0s4d0h3WURWUjBqQkJnd0ZvQVVEeG1CZ2NJUgppbVRDaGRkMHBoYnhBcjhZM0s4d0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DCkFnRUFKazdkYXJISXRtUjVaK05qVk9WV1ZjNWtOMnVGRVdFbEkrazdHbjdEZDhRdE56VlBuQW5aK1IzSVdXQUEKSXdPQ2Z3YmJTZGRWakwxSnp3aVk5cFNhd3FEd3NlM1kzR3lzNUhHMXFqeFFCaFF3NXdVMTc4aHl6cGhwNkY1cwp5SmZxMCtvZ2hPdFNxOUp6bGFYNW5MdXJNZ3hWajBrZmNodXRoNjNFdnBwa29sNHcvb3Z3OTZvdEdUVmcvUm14Cm9yYnN4MVE5aERSalNNRUdaRHd2R0xONUx1K0JDQUFXcFp4N3c1VTl0bE9CaGxFSFF0cGl6cy9aZ1k5S0pabXAKc3dKMm0rTWp5K1RrMWt5ZW1nT3hmUDBZQkZDRnFHZU8xOWxqWjY1SmRTOUlSdnY5MEp5c1hHY0ZJbVkxRTVERQorTXovdkFaRVN4ZmRrSWFqN0xuZGZVNUJITVY0bDRDR0ZaRDdMYWpLcFJybzlybmU5QmRINHkrOVd0R1RHRlZCCmFzOVE3VGZDRTE1YkpYWE5QYzJXL0VQM1RjVTdlejI1WFcyR1JyVHAxVXA0MlhmRCtVUm9CUEdnU0lWSE4vdFoKbWxZbTlrQW8xajdKM2RnV3VlU1lFamxhRG1OSGlBbGtWOTJnSm55aDVvK3cxU1BudnZtMEkxejNyM0NVT25qYgp2UGFmVWE3TjdYcE1QREs5NW5rc2RZYkVlUWEzUDZ6V3VuL0VQUjdJblV1VVFBWFpPZUlEakVkTFZObzZqcDJuCjFpQUtlT3dNQU1FU00rWWZmSHVSN0FJY3ZHUXNBUU9hSlRjQVVjeldzSHFaR0RtRlVVU0tYSTBnL1ZyUFhsMVUKb0dBQ3pzNnZCbDZHaHo2RURhaURJVVlHTXM3em0yVjY0WTVRcGVJdTIvclJFWjQ9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
+                )
+            )
+        ]
+
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "signify",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "SECRET_ROTATE",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is successful.
+        self.assertEqual(response.status_code, 200)
+
+    def test_rotate_signify_secret_unsupported_event_type(self):
+        """Test the rotation of a Signify secret with an unsupported event type."""
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "signify",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "INVALID_EVENT",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 500)
+
+    def test_rotate_signify_secret_unsupported_incorrect_secret_type(self):
+        """Test the rotation of a Signify secret with an incorrect secret type."""
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "service-account",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "SECRET_ROTATE",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 200)
+
+    def test_rotate_signify_secret_invalid_message(self):
+        """Test the rotation of a Signify secret with an invalid message."""
+        # Mock the request data.
+        mock_pubsub_message = {}
+        event = {
+            "message": {
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 500)


Reviews:
user: login: dekiel
body: In my opinion we should distinguish two cases, the message is invalid, the secret to rotate is not supported.

The function `validate_message` checks if the message contains a secret type we support. I would avoid using an `invalid` or `incorrect` word for such message. This may suggest the message should be valid and processed but something is missing or malformed due to some problems. I would stress more that, the message is fine, we just not handle this secret type i this service.

I would think about renaming the function.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T10:31:59Z
user: login: KacperMalachowski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T10:57:06Z
user: login: dekiel
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T01:15:04Z
user: login: KacperMalachowski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T07:13:07Z
user: login: dekiel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T14:28:44Z
user: login: dekiel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T15:29:16Z
user: login: KacperMalachowski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T05:17:05Z
user: login: dekiel
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:32:02Z
user: login: dekiel
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T20:23:21Z

Review comments:
diff_hunk: @@ -42,7 +42,8 @@ def rotate_signify_secret() -> Response:
 
         secret_rotate_msg: Dict[str, Any] = extract_message_data(pubsub_message)
 
-        validate_message(secret_rotate_msg)
+        if not validate_message(logger, secret_rotate_msg):
+            return prepare_error_response("Invalid message", logger)
path: cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
user: login: dekiel
body: Can we respond with 500 error code if the message is correct but not supported in this service? Will using 500 response code cause pubsub try to resend the message again?

In this case should we respond with 200 to acknowledge the message?
created_at: 2025-04-09T10:27:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -95,13 +96,22 @@ def rotate_signify_secret() -> Response:
         return prepare_error_response(exc, logger)
 
 
-def validate_message(message: dict[str, Any]) -> None:
-    """Raises error when received message struct is invalid"""
+def validate_message(logger: logging.Logger, message: dict[str, Any]) -> bool:
+    """Returns false when received message struct is invalid
+    Logs the info why the message is invalid
+    """
 
     # Pub/Sub topic handle multiple secret rotator components
     # verify if we should handle that message
     if message.get("labels", {}).get("type") != secret_rotate_message_type:
-        raise TypeError("Invalid or unknown type value")
+        logger.info(
+            "Incorrect message type received. Expected %s, got %s. Ignoring message.",
path: cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
user: login: dekiel
body: This function does not implement code that ignores the message. This is done outside the function and we should not report that in log entry here.
created_at: 2025-04-09T10:30:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,7 +42,8 @@ def rotate_signify_secret() -> Response:
 
         secret_rotate_msg: Dict[str, Any] = extract_message_data(pubsub_message)
 
-        validate_message(secret_rotate_msg)
+        if not validate_message(logger, secret_rotate_msg):
+            return prepare_error_response("Invalid message", logger)
path: cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
user: login: KacperMalachowski
body: Yes, we should respond with 200
created_at: 2025-04-09T10:57:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+"""Package that contains the message validator for the Signify Secret Rotator."""
+
+
+class MessageTypeError(Exception):
+    """Custom exception for invalid message type."""
+
+    def __init__(self, message: str, received_type: str) -> None:
+        super().__init__(message)
+        self.message = message
+        self.received_type = received_type
+
+
+# MessageValidator class verifies the message type and event type
+# pylint: disable=too-few-public-methods
+class MessageValidator:
+    """Class that validates the message received from Pub/Sub."""
+
+    def __init__(self, valid_secret_type: str):
+        self.valid_secret_type = valid_secret_type
+
+    def validate(self, message: dict) -> None:
+        """
+        Validates the message received from Pub/Sub.
+        Raises an exception when message is invalid
+        """
+
+        self._verify_message_type(message)
+
+    def _verify_message_type(self, message: dict) -> None:
+        """
+        Verifies if the message type is valid.
+        """
+
+        message_type = message.get("labels", {}).get("type")
+        if message_type != self.valid_secret_type:
+            raise MessageTypeError(
+                "Incorrect message type received. "
+                + f"Expected {self.valid_secret_type}, got {message_type}",
path: cmd/cloud-run/signifysecretrotator/messagevalidator.py
user: login: dekiel
body: Should we compare message type with secret type? Is it correct to consider a type in a message payload to be a type of a message?
created_at: 2025-04-12T23:49:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,16 +161,20 @@ def get_pubsub_message() -> Dict[str, Any]:
         raise ValueError("No Pub/Sub message received")
 
     if not isinstance(envelope, dict) or "message" not in envelope:
-        raise ValueError("Invalid Pub/Sub message format")
+        raise TypeError("Invalid Pub/Sub message format")
path: cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
user: login: dekiel
body: In this function we return a TypeError because invalid pubsub message format. What's the difference from MessageTypeError? Should we use different type errors for message type errors?
created_at: 2025-04-13T00:20:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+"""Package containing tests for the Signify Secret Rotator."""
+
+import base64
+import json
+from typing import List
+import unittest
+from unittest.mock import patch
+
+
+from flask import Flask
+from cryptography import x509
+
+from signifysecretrotator import (
+    rotate_signify_secret,
+    SecretManagerClient,
+    SignifyClient,
+)
+
+
+class TestSignifySecretRotator(unittest.TestCase):
+    """Tests for the Signify Secret Rotator.
+    Tests the functionality of the Signify Secret Rotator, including the
+    validation of messages, the rotation of secrets, and the handling of
+    errors. It mocks the Google Cloud Secret Manager and Signify API to
+    simulate the behavior of these services.
+    """
+
+    def setUp(self):
+        """Set up the test case."""
+        # Mock the Google Cloud Secret Manager
+        access_secret_patcher = patch.object(SecretManagerClient, "get_secret")
+        add_secret_version_patcher = patch.object(
+            SecretManagerClient, "add_secret_version"
+        )
+        self.mock_access_secret = access_secret_patcher.start()
+        self.mock_add_secret_version = add_secret_version_patcher.start()
+
+        # Mock the Signify client
+        fetch_access_token_patcher = patch.object(SignifyClient, "fetch_access_token")
+        fetch_new_certificate_patcher = patch.object(
+            SignifyClient, "fetch_new_certificate"
+        )
+
+        self.mock_fetch_access_token = fetch_access_token_patcher.start()
+        self.mock_fetch_new_certificate = fetch_new_certificate_patcher.start()
+
+        # Mock the logger
+        self.logger = patch("pylogger.logger.create_logger", autospec=True).start()
+
+        # Mock the Flask request
+        self.app = Flask(__name__)
+        self.app.config["TESTING"] = True
+
+    def tearDown(self):
+        """Tear down the test case."""
+        patch.stopall()
+
+    def test_rotate_signify_secret_success(self):
+        """Test the rotation of a Signify secret."""
+        # Mock the response from the Secret Manager.
+        self.mock_access_secret.return_value = {
+            "name": "projects/test/secrets/test",
+            "tokenURL": "https://example.com/token",
+            "certServiceURL": "https://example.com/cert",
+            "clientID": "test-client-id",
+            "certData": base64.b64encode(b"test-cert").decode("utf-8"),
+            "privateKeyData": base64.b64encode(b"test-key").decode("utf-8"),
+        }
+
+        # Mock the response from the Signify client.
+        self.mock_fetch_access_token.return_value = "test-access-token"
+        self.mock_fetch_new_certificate.return_value = mocked_cert_create
+
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "signify",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "SECRET_ROTATE",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is successful.
+        self.assertEqual(response.status_code, 200)
+
+    def test_rotate_signify_secret_unsupported_event_type(self):
+        """Test the rotation of a Signify secret with an unsupported event type."""
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "signify",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "INVALID_EVENT",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 500)
+
+    def test_rotate_signify_secret_unsupported_incorrect_secret_type(self):
+        """Test the rotation of a Signify secret with an incorrect secret type."""
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "service-account",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "SECRET_ROTATE",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 200)
+
+    def test_rotate_signify_secret_invalid_message(self):
+        """Test the rotation of a Signify secret with an invalid message."""
+        # Mock the request data.
+        mock_pubsub_message = {}
+        event = {
+            "message": {
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 500)
+
+
+mocked_cert_create: List[x509.Certificate] = [
path: cmd/cloud-run/signifysecretrotator/test_signifysecretrotator.py
user: login: dekiel
body: This variable is used in one test only. Should we create it in the test that is using it? Same as other variables.
created_at: 2025-04-13T01:14:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,16 +161,20 @@ def get_pubsub_message() -> Dict[str, Any]:
         raise ValueError("No Pub/Sub message received")
 
     if not isinstance(envelope, dict) or "message" not in envelope:
-        raise ValueError("Invalid Pub/Sub message format")
+        raise TypeError("Invalid Pub/Sub message format")
path: cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
user: login: KacperMalachowski
body: Message Type Error is considered to be use when message type is incorrect (e.g. we receive something other than `SECRET_ROTATE` message), but the message format is correct. 
Type Error indicates that type of received data is invalid, for example we receive not a pubsub message at this point.
created_at: 2025-04-14T07:10:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+"""Package containing tests for the Signify Secret Rotator."""
+
+import base64
+import json
+from typing import List
+import unittest
+from unittest.mock import patch
+
+
+from flask import Flask
+from cryptography import x509
+
+from signifysecretrotator import (
+    rotate_signify_secret,
+    SecretManagerClient,
+    SignifyClient,
+)
+
+
+class TestSignifySecretRotator(unittest.TestCase):
+    """Tests for the Signify Secret Rotator.
+    Tests the functionality of the Signify Secret Rotator, including the
+    validation of messages, the rotation of secrets, and the handling of
+    errors. It mocks the Google Cloud Secret Manager and Signify API to
+    simulate the behavior of these services.
+    """
+
+    def setUp(self):
+        """Set up the test case."""
+        # Mock the Google Cloud Secret Manager
+        access_secret_patcher = patch.object(SecretManagerClient, "get_secret")
+        add_secret_version_patcher = patch.object(
+            SecretManagerClient, "add_secret_version"
+        )
+        self.mock_access_secret = access_secret_patcher.start()
+        self.mock_add_secret_version = add_secret_version_patcher.start()
+
+        # Mock the Signify client
+        fetch_access_token_patcher = patch.object(SignifyClient, "fetch_access_token")
+        fetch_new_certificate_patcher = patch.object(
+            SignifyClient, "fetch_new_certificate"
+        )
+
+        self.mock_fetch_access_token = fetch_access_token_patcher.start()
+        self.mock_fetch_new_certificate = fetch_new_certificate_patcher.start()
+
+        # Mock the logger
+        self.logger = patch("pylogger.logger.create_logger", autospec=True).start()
+
+        # Mock the Flask request
+        self.app = Flask(__name__)
+        self.app.config["TESTING"] = True
+
+    def tearDown(self):
+        """Tear down the test case."""
+        patch.stopall()
+
+    def test_rotate_signify_secret_success(self):
+        """Test the rotation of a Signify secret."""
+        # Mock the response from the Secret Manager.
+        self.mock_access_secret.return_value = {
+            "name": "projects/test/secrets/test",
+            "tokenURL": "https://example.com/token",
+            "certServiceURL": "https://example.com/cert",
+            "clientID": "test-client-id",
+            "certData": base64.b64encode(b"test-cert").decode("utf-8"),
+            "privateKeyData": base64.b64encode(b"test-key").decode("utf-8"),
+        }
+
+        # Mock the response from the Signify client.
+        self.mock_fetch_access_token.return_value = "test-access-token"
+        self.mock_fetch_new_certificate.return_value = mocked_cert_create
+
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "signify",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "SECRET_ROTATE",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is successful.
+        self.assertEqual(response.status_code, 200)
+
+    def test_rotate_signify_secret_unsupported_event_type(self):
+        """Test the rotation of a Signify secret with an unsupported event type."""
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "signify",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "INVALID_EVENT",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 500)
+
+    def test_rotate_signify_secret_unsupported_incorrect_secret_type(self):
+        """Test the rotation of a Signify secret with an incorrect secret type."""
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "service-account",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "SECRET_ROTATE",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 200)
+
+    def test_rotate_signify_secret_invalid_message(self):
+        """Test the rotation of a Signify secret with an invalid message."""
+        # Mock the request data.
+        mock_pubsub_message = {}
+        event = {
+            "message": {
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 500)
+
+
+mocked_cert_create: List[x509.Certificate] = [
path: cmd/cloud-run/signifysecretrotator/test_signifysecretrotator.py
user: login: KacperMalachowski
body: I placed it here simply for readibility, I don't like placing such long string inside the test itself. 
Do you htink it should be in test?
created_at: 2025-04-14T07:11:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+"""Package that contains the message validator for the Signify Secret Rotator."""
+
+
+class MessageTypeError(Exception):
+    """Custom exception for invalid message type."""
+
+    def __init__(self, message: str, received_type: str) -> None:
+        super().__init__(message)
+        self.message = message
+        self.received_type = received_type
+
+
+# MessageValidator class verifies the message type and event type
+# pylint: disable=too-few-public-methods
+class MessageValidator:
+    """Class that validates the message received from Pub/Sub."""
+
+    def __init__(self, valid_secret_type: str):
+        self.valid_secret_type = valid_secret_type
+
+    def validate(self, message: dict) -> None:
+        """
+        Validates the message received from Pub/Sub.
+        Raises an exception when message is invalid
+        """
+
+        self._verify_message_type(message)
+
+    def _verify_message_type(self, message: dict) -> None:
+        """
+        Verifies if the message type is valid.
+        """
+
+        message_type = message.get("labels", {}).get("type")
+        if message_type != self.valid_secret_type:
+            raise MessageTypeError(
+                "Incorrect message type received. "
+                + f"Expected {self.valid_secret_type}, got {message_type}",
path: cmd/cloud-run/signifysecretrotator/messagevalidator.py
user: login: KacperMalachowski
body: WIll rename it, because in reality `message_type` here is a secret type (`type` label in secret manager).
Also will rename the error to be `SecretTypeError` to better show the reason why it's thrown.
created_at: 2025-04-14T07:13:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,202 @@
+"""Package containing tests for the Signify Secret Rotator."""
+
+import base64
+import json
+from typing import List
+import unittest
+from unittest.mock import patch
+
+
+from flask import Flask
+from cryptography import x509
+
+from signifysecretrotator import (
+    rotate_signify_secret,
+    SecretManagerClient,
+    SignifyClient,
+)
+
+
+class TestSignifySecretRotator(unittest.TestCase):
+    """Tests for the Signify Secret Rotator.
+    Tests the functionality of the Signify Secret Rotator, including the
+    validation of messages, the rotation of secrets, and the handling of
+    errors. It mocks the Google Cloud Secret Manager and Signify API to
+    simulate the behavior of these services.
+    """
+
+    def setUp(self):
+        """Set up the test case."""
+        # Mock the Google Cloud Secret Manager
+        access_secret_patcher = patch.object(SecretManagerClient, "get_secret")
+        add_secret_version_patcher = patch.object(
+            SecretManagerClient, "add_secret_version"
+        )
+        self.mock_access_secret = access_secret_patcher.start()
+        self.mock_add_secret_version = add_secret_version_patcher.start()
+
+        # Mock the Signify client
+        fetch_access_token_patcher = patch.object(SignifyClient, "fetch_access_token")
+        fetch_new_certificate_patcher = patch.object(
+            SignifyClient, "fetch_new_certificate"
+        )
+
+        self.mock_fetch_access_token = fetch_access_token_patcher.start()
+        self.mock_fetch_new_certificate = fetch_new_certificate_patcher.start()
+
+        # Mock the logger
+        self.logger = patch("pylogger.logger.create_logger", autospec=True).start()
+
+        # Mock the Flask request
+        self.app = Flask(__name__)
+        self.app.config["TESTING"] = True
+
+    def tearDown(self):
+        """Tear down the test case."""
+        patch.stopall()
+
+    def test_rotate_signify_secret_success(self):
+        """Test the rotation of a Signify secret."""
+        # Mock the response from the Secret Manager.
+        self.mock_access_secret.return_value = {
+            "name": "projects/test/secrets/test",
+            "tokenURL": "https://example.com/token",
+            "certServiceURL": "https://example.com/cert",
+            "clientID": "test-client-id",
+            "certData": base64.b64encode(b"test-cert").decode("utf-8"),
+            "privateKeyData": base64.b64encode(b"test-key").decode("utf-8"),
+        }
+
+        # Mock the response from the Signify client.
+        self.mock_fetch_access_token.return_value = "test-access-token"
+        self.mock_fetch_new_certificate.return_value = mocked_cert_create
+
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "signify",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "SECRET_ROTATE",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is successful.
+        self.assertEqual(response.status_code, 200)
+
+    def test_rotate_signify_secret_unsupported_event_type(self):
+        """Test the rotation of a Signify secret with an unsupported event type."""
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "signify",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "INVALID_EVENT",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 500)
+
+    def test_rotate_signify_secret_unsupported_incorrect_secret_type(self):
+        """Test the rotation of a Signify secret with an incorrect secret type."""
+        # Mock the request data.
+        mock_pubsub_message = {
+            "name": "projects/test/secrets/test",
+            "labels": {
+                "type": "service-account",
+                "usage": "tests",
+                "owner": "test-owner",
+            },
+        }
+        event = {
+            "message": {
+                "attributes": {
+                    "eventType": "SECRET_ROTATE",
+                },
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 200)
+
+    def test_rotate_signify_secret_invalid_message(self):
+        """Test the rotation of a Signify secret with an invalid message."""
+        # Mock the request data.
+        mock_pubsub_message = {}
+        event = {
+            "message": {
+                "data": base64.b64encode(
+                    json.dumps(mock_pubsub_message).encode("utf-8")
+                ).decode(),
+            }
+        }
+
+        # Call the function to rotate the secret.
+        with self.app.test_request_context(
+            path="/",
+            method="POST",
+            json=event,
+        ):
+            response = rotate_signify_secret()
+
+        # Check that the response is an error.
+        self.assertEqual(response.status_code, 500)
+
+
+mocked_cert_create: List[x509.Certificate] = [
path: cmd/cloud-run/signifysecretrotator/test_signifysecretrotator.py
user: login: dekiel
body: I would move it to the test to be consistent with other variables. Especially this variable does not play any special role comparing to other variables. If you would like to avoid long string in test maybe a constant for a certificate base64 string could be a solution.
created_at: 2025-04-14T14:28:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,16 +161,20 @@ def get_pubsub_message() -> Dict[str, Any]:
         raise ValueError("No Pub/Sub message received")
 
     if not isinstance(envelope, dict) or "message" not in envelope:
-        raise ValueError("Invalid Pub/Sub message format")
+        raise TypeError("Invalid Pub/Sub message format")
path: cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
user: login: dekiel
body: The only usage of MessageTypeError is in this PR in cmd/cloud-run/signifysecretrotator/messagevalidator.py line 36. This usage will be replaced with `SecretTypeError` which is a good direction. In this context a MessageTypeError will be removed right? The example with `SECRET_ROTATE` I would consider rather an instance of `EventTypeError`
created_at: 2025-04-14T15:29:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,16 +161,20 @@ def get_pubsub_message() -> Dict[str, Any]:
         raise ValueError("No Pub/Sub message received")
 
     if not isinstance(envelope, dict) or "message" not in envelope:
-        raise ValueError("Invalid Pub/Sub message format")
+        raise TypeError("Invalid Pub/Sub message format")
path: cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
user: login: KacperMalachowski
body: Yes, `SecretTypeError` will replace `MessageTypeError`.
created_at: 2025-04-15T05:17:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -90,18 +91,23 @@ def rotate_signify_secret() -> Response:
 
         logger.info("Certificate rotated successfully at %s", created_at)
 
-        return "Certificate rotated successfully"
+        return prepare_response("Certificate rotated successfully", 200)
     except (HTTPError, ValueError, TypeError, SecretManagerError) as exc:
-        return prepare_error_response(exc, logger)
-
-
-def validate_message(message: dict[str, Any]) -> None:
-    """Raises error when received message struct is invalid"""
-
-    # Pub/Sub topic handle multiple secret rotator components
-    # verify if we should handle that message
-    if message.get("labels", {}).get("type") != secret_rotate_message_type:
-        raise TypeError("Invalid or unknown type value")
+        return prepare_response(str(exc), 500)
+    except EventTypeError as exc:
+        # We consider it as an error
+        # We can filter the event type on the Pub/Sub level,
+        # so we shouldn't receive it in the first place
+        logger.error("Received unsupported event type: %s", exc.received_event_type)
+        return prepare_response(
+            f"Unsupported event type received {exc.received_event_type}", 400
+        )
+    except SecretTypeError as exc:
+        # We do not consider it as an error
+        # We cannot filter the message on the Pub/Sub level,
+        # so we consider handling it as a future of signify secret rotator service
+        logger.info("Received unsupported message type: %s", exc.received_type)
+        return prepare_response("Unsupported messagte type received", 200)
path: cmd/cloud-run/signifysecretrotator/signifysecretrotator.py
user: login: dekiel
body: ```suggestion
        logger.info("Received unsupported secret type: %s", exc.received_type)
        return prepare_response("Unsupported secret type received", 200)
```
created_at: 2025-04-15T10:21:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: kyma-bot
created_at: 2025-04-08T05:59:39Z
body: ## Plan Result

[CI link](https://github.com/kyma-project/test-infra/actions/runs/14326231754)



<pre><code>Only Outputs will be changed.</code></pre>




<details><summary>Change Result (Click me)</summary>

```hcl
Changes to Outputs:
  ~ artifact_registry                              = {
      ~ modules-internal = {
          ~ artifact_registry_collection = {
                id                        = "projects/kyma-project/locations/europe/repositories/modules-internal"
                name                      = "modules-internal"
              ~ update_time               = "2025-04-02T14:22:24.286152Z" -> "2025-04-07T16:36:30.485318Z"
                # (18 unchanged attributes hidden)
            }
        }
    }

You can apply this plan to save these new output values to the OpenTofu
state, without changing any real infrastructure.
```

</details>



<details><summary>:information_source: Objects have changed outside of Terraform</summary>

_This feature was introduced from [Terraform v0.15.4](https://github.com/hashicorp/terraform/releases/tag/v0.15.4)._

```hcl

OpenTofu detected the following changes made outside of OpenTofu since the
last "tofu apply" which may have affected this plan:

  # module.artifact_registry["modules-internal"].google_artifact_registry_repository.artifact_registry has changed
  ~ resource "google_artifact_registry_repository" "artifact_registry" {
        id                     = "projects/kyma-project/locations/europe/repositories/modules-internal"
        name                   = "modules-internal"
      ~ update_time            = "2025-04-02T14:22:24.286152Z" -> "2025-04-07T16:36:30.485318Z"
        # (11 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }


Unless you have made equivalent changes to your configuration, or ignored the
```

</details>







<!-- github-comment: {"Command":"plan","JobName":"pull-plan-prod-terraform","PRNumber":12913,"Program":"tfcmt","SHA1":"f6b9ab62ecf23a8af9ee28bbc0e25c99d371fda7","Vars":{},"WorkflowName":"Pull Plan Prod Terraform"} -->
author_association: CONTRIBUTOR
user: login: kyma-bot
created_at: 2025-04-08T06:13:31Z
body: ## Plan Result

[CI link](https://github.com/kyma-project/test-infra/actions/runs/14326413792)



<pre><code>Only Outputs will be changed.</code></pre>




<details><summary>Change Result (Click me)</summary>

```hcl
Changes to Outputs:
  ~ artifact_registry                              = {
      ~ modules-internal = {
          ~ artifact_registry_collection = {
                id                        = "projects/kyma-project/locations/europe/repositories/modules-internal"
                name                      = "modules-internal"
              ~ update_time               = "2025-04-02T14:22:24.286152Z" -> "2025-04-07T16:36:30.485318Z"
                # (18 unchanged attributes hidden)
            }
        }
    }

You can apply this plan to save these new output values to the OpenTofu
state, without changing any real infrastructure.
```

</details>



<details><summary>:information_source: Objects have changed outside of Terraform</summary>

_This feature was introduced from [Terraform v0.15.4](https://github.com/hashicorp/terraform/releases/tag/v0.15.4)._

```hcl

OpenTofu detected the following changes made outside of OpenTofu since the
last "tofu apply" which may have affected this plan:

  # module.artifact_registry["modules-internal"].google_artifact_registry_repository.artifact_registry has changed
  ~ resource "google_artifact_registry_repository" "artifact_registry" {
        id                     = "projects/kyma-project/locations/europe/repositories/modules-internal"
        name                   = "modules-internal"
      ~ update_time            = "2025-04-02T14:22:24.286152Z" -> "2025-04-07T16:36:30.485318Z"
        # (11 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }


Unless you have made equivalent changes to your configuration, or ignored the
```

</details>







<!-- github-comment: {"Command":"plan","JobName":"pull-plan-prod-terraform","PRNumber":12913,"Program":"tfcmt","SHA1":"97f4899e1af0c8402f65f5a9753ad6ccbd475a30","Vars":{},"WorkflowName":"Pull Plan Prod Terraform"} -->
author_association: CONTRIBUTOR