Skip to main content

Overview

Miru natively supports the use of JSON Schema to validate configurations. However, you may find it more convenient to validate configurations with your software stack instead of relying on JSON Schema. To support this, Miru provides webhooks and server-side APIs. The setup involves two primary steps:
  1. Deploy an endpoint that validates deployments for your application.
  2. Supply the endpoint’s URL to Miru and add Miru’s authentication to the endpoint.
Once the endpoint is integrated, validation occurs in two parts:
  1. Miru sends deployment.validate webhook events to your endpoint whenever deployments require validation.
  2. Your endpoint sends validation results back to Miru using the validate deployment API.
This section guides you through the process of integrating an endpoint with Miru. An example flask server, which leverages the Miru server-side SDK, is provided to get you started. You may host the endpoint however you’d like as long as it is accessible over HTTPS.

Enable Custom Validation

Custom validation is enabled on a per-release basis. Enabling it on one release has no impact on other releases. To enable custom validation for a release, navigate to the Releases page. Click the ellipses on the far right of the release you would like to enable and select Edit from the dropdown.
Toggle the Custom Validation setting and click Save.
Enabling custom validation requires deployments to be validated. If you do not have a validation server set up, deployments on this release will be unable to deploy.

Create an Endpoint

Next, you’ll need to supply the URL of the endpoint to Miru. If you haven’t deployed the endpoint yet, that’s okay. You can change the URL at any time, so feel free to follow along and deploy the endpoint after completing this section. Navigate to the webhooks page in Miru.
Click Add Endpoint in the top right of the page. Enter the full URL of your validation endpoint and subscribe to the deployment.validate event. We recommend using a route similar to /webhooks/miru, but you may use any route you prefer. URLs should be of the form https://<your-domain>/<route>. For instance, if your domain is my-domain.com and you want to use the route /hooks/miru, your URL would be https://my-domain.com/hooks/miru.
Your URL must use HTTPS. HTTP is not supported.
Finally, enter a description for the endpoint, select the deployment.validate event, and click Create.

Secrets

You’ll need two secrets to integrate your endpoint with Miru: a webhook secret and an API key. We recommend storing secrets in a secrets manager or other secure location. No matter where you choose to store them, be sure not to expose these secrets in publicly accessible areas such as GitHub, client-side code, and so forth.

Webhook Secret

To retrieve the webhook secret for an endpoint, click into the endpoint. Then, click the eye icon on the far right of the page to reveal the webhook secret.

API Key

To create an API key for your endpoint:
  1. Go to the API Keys page in the dashboard.
  2. Click New API Key.
  3. Provide a name and click Create.
Once an API key has been created, it can never be retrieved again. If you lose your API key, you must create a new one.

Example Server

The custom-validation repository contains an example server that demonstrates webhook verification and deployment validation using the Miru server-side SDK.

Secrets

For a plug-and-play experience with Webhook Secrets and API Keys, you may place the secrets in the .env file. However, for production use, we recommend storing the secrets in a secrets manager or other secure location.

Code Walkthrough

The flask server first verifies the webhook signature using the Webhook Secret.
headers = dict(request.headers)
payload = request.get_data()

try:
    # swap the get_env_var helper for your secrets manager if needed
    wh = Webhook(get_env_var("MIRU_WEBHOOK_SECRET"))
    payload = wh.verify(payload, headers)
except WebhookVerificationError as e:
    print(f"Webhook verification error: {e}")
    return jsonify({
        'valid': False,
        'message': str(e),
        'errors': []
    }), 400
It then checks if the event is the deployment.validate event, passing the event to the handle_validate_deployment function to handle the validation.
event = miru_client.webhooks.unwrap(payload)
if event.type == "deployment.validate":
    handle_validate_deployment(event)
# ignore events which aren't deployment.validate
else:
    return jsonify({'message': 'no action required'}), 200
The deployment and its config instances are then retrieved using the get deployment API, before being passed to the validate_deployment function.
deployment = miru_client.deployments.retrieve(
    event.data.deployment.id,
    expand=[
        "device",
        "release",
        "config_instances.content",  # type: ignore
    ],
)
The validate_deployment function is where you’ll insert your validation logic. It takes in the config instances for the deployment and returns the validation results.
def validate_deployment(
    config_instances: list[types.ConfigInstance],
) -> DeploymentValidation:

    cfg_inst_validations: list[ConfigInstanceValidation] = []

    for config_instance in config_instances:
        # retrieve the config instance content
        if config_instance.content is None:
            raise ValueError("Config instance content is None")

        # do some validation on the config instance
        # ...
        _ = config_instance.content

        cfg_inst_validations.append(ConfigInstanceValidation(
            id=config_instance.id,
            message=(
                "Error message shown on the config instance level in the UI"
            ),
            parameters=[
                ParameterValidation(
                    message=(
                        "Error message shown on the parameter level in the UI"
                    ),
                    path=["path", "to", "invalid", "parameter"],
                )
            ],
        ))

    return DeploymentValidation(
        is_valid=True,
        message="Error message shown on the deployment level in the UI",
        config_instances=cfg_inst_validations,
    )
Finally, the validation results are sent to Miru using the validate deployment API.
response = miru_client.deployments.validate(
    deployment.id, **result.to_dict()
)

Effects

The validate deployment API response contains an effect field, which returns the effect of validating the deployment. Depending on the validation results and the user’s intent, the deployment may be launched into one of several different states as a result. The effect field records what the deployment’s next state is, and the message field explains why. In general, the validation server does not concern itself with why the deployment needs to be validated or what action should come next. Miru handles these details accordingly. However, the effect and message fields provide useful system transparency to understand what the system is doing and maintain some level of auditability. Feel free to ignore the validation effect; although you may find it helpful to log validation effects for auditing and diagnostic purposes. Reject If your endpoint sets the is_valid field to be false in the validate deployment request, the deployment is ‘rejected’, and the effect field returns reject. Rejected deployments cannot be staged or deployed to devices but are maintained for historical reference. Error messages supplied in the request are displayed in the UI for convenience. Stage and Deploy If your endpoint sets the is_valid field to be true in the validate deployment request, the deployment is ‘approved’ and the effect field returns stage or deploy. Depending on the user’s original intent, the deployment may be staged for a future deployment or deployed immediately. None and Void In some cases, network latency or retries may cause the deployment to enter an invalid state. The effect field returns none or void in these exceptional cases to indicate the validation request was successfully received. However, the original intent of the user was not achieved due to errors unrelated to the validation endpoint.
I