LogoLogo
HomeBlogGet a Demo
  • Introduction
  • Install Lunar
  • Learn the basics
  • 📖Docs
    • Key concepts
    • Component JSON
    • Catalog JSON
    • Lunar CLI Reference
  • 📝Configuration
    • lunar-config.yml
      • catalogers
      • catalogers/hooks
      • domains
      • components
      • collectors
      • collectors/hooks
      • policies
    • lunar.yml
  • 🛠️Plugin SDKs
    • Plugins configuration
      • lunar-cataloger.yml
      • lunar-collector.yml
      • lunar-policy.yml
    • Bash SDK
      • Cataloger
      • Collector
    • Python SDK
      • Collector
      • Policy
        • Check
        • Path
        • ComponentData
        • NoDataError
        • Utility Functions
  • ⚙️SQL API
    • Overview
    • Views
      • domains
      • components
      • component_deltas
      • initiatives
      • policies
      • checks
      • prs
      • catalog
Powered by GitBook

©️ Earthly Technologies

On this page
  • Installation
  • Policy environment
  • Core Components
  • Check
  • Path
  • ComponentData
  • NoDataError
  • Automatic Data Loading
  • Executing Policies Locally
  • Handling Missing Data
  • Best Practices for Handling Missing Data
  • Writing Unit Tests for Policies
  1. Plugin SDKs
  2. Python SDK

Policy

PreviousCollectorNextCheck

Last updated 17 hours ago

The lunar_policy Python package provides utilities for working with Lunar policies, allowing you to load, query, and make assertions about component metadata, such as the .

For the reference documentation check out:

Installation

The package is available through pip:

pip install lunar-policy

Policy environment

Earthly Lunar executes policies in an environment set up with the following variables:

  • LUNAR_HUB_HOST: The host of the Lunar Hub.

  • LUNAR_HUB_INSECURE: Whether to skip SSL verification of the Lunar Hub.

  • LUNAR_POLICY_NAME: The name of the policy being executed.

  • LUNAR_INITIATIVE_NAME: The name of the initiative the policy belongs to.

  • LUNAR_POLICY_OWNER: The owner of the policy.

  • LUNAR_COMPONENT_ID: The ID of the component being checked in github.com/.../... format.

  • LUNAR_COMPONENT_DOMAIN: The domain of the component.

  • LUNAR_COMPONENT_OWNER: The owner of the component.

  • LUNAR_COMPONENT_PR: The PR number of the component, if applicable.

  • LUNAR_COMPONENT_GIT_SHA: The Git SHA of the component that the policy is being executed for.

  • LUNAR_COMPONENT_TAGS: The tags of the component.

  • LUNAR_COMPONENT_META: The metadata of the component as a JSON object.

  • Any secrets set in the Lunar Hub for the policy, via LUNAR_POLICY_SECRETS=<name>=<value>;....

Important

Note that since policies are re-evaluated frequently as each piece of data becomes available, it is strongly recommended to design your policy execution to be fast (no external API calls, no heavy processing, etc.). If you need to perform any expensive operations, consider using a collector instead, and passing the necessary data via the component JSON.

Core Components

The Policy SDK provides several key classes to help you write policies:

Check

from lunar_policy import Check, Path

# Create a check with a name and optional description
with Check("my-check", "Validates important properties") as check:
    # Get data using JSONPath
    value = check.get(".path.to.data")
    
    # Make assertions
    check.assert_equals(Path(".api.endpoints[0].method"), "GET")
    check.assert_greater_or_equal(value, 50)
    check.assert_contains(Path(".api.endpoints[0].path"), "/")

Path

ComponentData

from lunar_policy import Check, ComponentData
# Create ComponentData from JSON
component_data = ComponentData.from_json(component_json)
# Create ComponentData from a file
component_data = ComponentData.from_file("path/to/component.json")
# Use with Check
Check("my-check", component_data=component_data)

NoDataError

Automatic Data Loading

Executing Policies Locally

To execute a policy locally for testing purposes, you can use the lunar CLI.

lunar policy dev --component-json path/to/component.json ./path/to/policy.py

If you would like to use the real component JSON of one of your components, you can do so via the command:

lunar policy dev --component github.com/my-org/my-repo ./path/to/policy.py

Handling Missing Data

Lunar makes a clear distinction between missing component JSON data and failing component JSON data. This is needed under the hood to be able to provide partial policy results while collectors are still running. Policies that have enough data will provide accurate results, while policies that don't have enough data will report a "no-data" status.

When some or all of the component JSON data required for assertions is not present, policies use the NoDataError exception mechanism to report this status. The Check API handles this scenario automatically by catching this exception in the with context manager, setting the check status to "no-data".

from lunar_policy import Check, NoDataError, Path

# NoDataError can be explicitly raised
with Check("my-check") as check:
    if some_condition:
        raise NoDataError()
        
# The `get` method and Path-based assertions automatically raise NoDataError
# when a JSONPath doesn't exist in the component data
with Check("my-check") as check:
    # This will raise NoDataError if the path doesn't exist
    value = check.get(".path.to.data")
    # This will also raise NoDataError if the path doesn't exist
    check.assert_true(Path(".api.requires_auth"))

When writing policies, you should:

  1. Be aware that your policy might run against components where the expected data is not present

  2. Know that the Check context manager will catch NoDataError exceptions and set the status to "no-data"

  3. Multiple checks in the same block will continue to execute even if previous checks raise NoDataError

This approach ensures that policies don't incorrectly fail when run against components that legitimately don't have specific data (for example, because a collector has not had the chance to run yet).

Best Practices for Handling Missing Data

Below are examples demonstrating different approaches to handling missing data in policies:

Bad Approach: Assuming Data Exists

This approach incorrectly assumes all fields exist after retrieving an object, which can lead to errors when data is missing:

with Check("api-security-check") as check:
    # Bad: Gets the entire API object once and assumes all fields exist
    api = check.get(".api")
    
    # These will cause errors if api is None or missing expected fields
    check.assert_true(api["requires_auth"], "API should require authentication")
    rate_limit = api["rate_limit"]
    check.assert_equals(rate_limit, 100, f"API rate limit should be 100, but found {rate_limit}")
    check.assert_contains(api["security_headers"], "Content-Security-Policy")

Better Approach: Using NoData Explicitly

This approach explicitly raises NoData when fields don't exist:

with Check("api-security-check") as check:
    # Gets the API object
    api = check.get(".api")
    # You can explicitly raise NoDataError if needed for custom conditions
    if api is None or "requires_auth" not in api:
        raise NoDataError()
        
    # Normal assertions
    check.assert_true(api["requires_auth"], "API should require authentication")

Best Approach: Using Path for Automatic NoDataError Handling

This approach uses targeted field access and relies on the Check API to handle missing data automatically:

with Check("api-security-check") as check:
    # These assertions automatically raise NoDataError if paths don't exist:
    check.assert_true(Path(".api.requires_auth"), "API should require authentication")
    
    # get also raises NoDataError if the path doesn't exist
    rate_limit = check.get(".api.rate_limit") 
    check.assert_equals(rate_limit, 100, f"API rate limit should be 100, but found {rate_limit}")
    check.assert_contains(Path(".api.security_headers"), "Content-Security-Policy")

Writing Unit Tests for Policies

Let's assume that we have the following policy:

from lunar_policy import Check, ComponentData, Path

def verify_readme(component_data=None):
    with Check("readme-exists", component_data=component_data) as check:
        check.assert_false(Path(".readme.missing"), "README.md should exist")
    with Check("readme-long-enough", component_data=component_data) as check:
        lines = check.get('.readme.lines')
        check.assert_greater_or_equal(
            lines, 50,
            f'README.md should have at least 50 lines. Current count: {lines}'
        )

if __name__ == "__main__":
    verify_readme()

Here's an example showing how to write unit tests:

import unittest
from lunar_policy import Check, ComponentData, Path, get_last_check

class TestReadmePolicy(unittest.TestCase):
    def test_not_long_enough(self):
        component_json = {
            "readme": {
                "lines": 49,
                "missing": False
            }
        }
        verify_readme(ComponentData.from_json(component_json))
        
        # Verify the check failed because the README isn't long enough
        check = get_last_check()
        assertions = check.get_assertions()
        self.assertFalse(check.passed)
        self.assertTrue(assertions[0].passed)
        self.assertFalse(assertions[1].passed)

if __name__ == "__main__":
    unittest.main()

You can run the test with:

python -m unittest test_readme_policy.py

The Check class provides a fluent interface for making assertions about policy data. It tracks accessed data within the component JSON for traceability purposes. For detailed reference, see the .

The Path class is used to pass JSONPath expressions to assertion methods of Check. For detailed reference, see the .

The ComponentData class is used to initialize a Check instance with component metadata from different sources. For detailed reference, see the .

The NoDataError exception is used to indicate that required data for a policy check is missing. For detailed reference, see the .

When a policy is executed through Lunar Hub, Lunar passes in the relevant context via some environment variables. This context includes the for the component that is being checked. When you create a Check instance, the library automatically loads this data under the hood.

🛠️
component JSON
Check
Path
ComponentData
NoDataError
Utils
Check reference documentation
Path reference documentation
ComponentData reference documentation
NoDataError reference documentation
component JSON