Skip to main content

Overview

Miru natively supports the use of JSON Schema to validate configurations. However, you may find it simpler to validate configurations with your own 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 endpoint 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 later. 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. Webhook secrets are used to verify the authenticity of the webhook request while API keys are used to authenticate requests to the Miru API. 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 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, select the desired scopes, 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 flask 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 app.py file is the entry point for the flask server. Let’s walk through it step by step. The server accepts a POST request at the /webhooks/miru route. You may need to update this route to match the route you specified when creating the endpoint in Miru.
@app.route("/webhooks/miru", methods=["POST"])
def webhook_endpoint() -> tuple[Response, int]:
Verify the Webhook First, the server verifies the webhook signature using the Webhook Secret. Make sure you’ve set the Webhook Secret in the .env file or swapped out the get_env_var function for your secrets manager.
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
Handle the Validation The server then checks if the event is the deployment.validate event, passing the event to the handle_validate_deployment function while ignoring all other events.
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
Retrieve the Deployment The deployment and its config instances are then retrieved from Miru using the get deployment API. Here we use expansions to retrieve the deployment’s device, release, and config instances.
deployment = miru_client.deployments.retrieve(
    event.data.deployment.id,
    expand=[
        "device",
        "release",
        "config_instances.content",  # type: ignore
    ],
)
Validate the Deployment The deployment’s config instances are then passed to the validate_deployment function. This is where you’ll add your own validation logic. The function takes config instances as arguments 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,
    )
Send the Results 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 endpoint does not concern itself with why the deployment needs to be validated or what action should come next. Miru handles these details for you. 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