The Policy Engine lets users define custom validation and modification rules using TypeScript. It helps ensure compliance, security, and consistency by enforcing rules and dynamically updating manifests before deployment.

Validation Policies

Validation policies ensure that only compliant configurations are deployed. These policies evaluate Truefoundry manifests and prevent deployments that do not meet specific conditions. Some common use cases include:

  • Enforcing readiness and liveness probes for all production services.
  • Enforcing on-demand instance on production environments for better reliability
  • Enforcing auto shutdown for all dev services.

Here is a sample policy which enforces auto-shutdown for all services deployed in dev environments

import { ValidationInput, ValidationError } from '@src/types';
export function validate(validationInput: ValidationInput): void {
  const { manifest, context } = validationInput;
  const environment = context.environment;
  const isProduction = environment?.manifest.isProduction;

  if (isProduction) return;
  
  if(manifest.type !== 'service') return;
  
  if (!manifest.auto_shutdown) {
    throw new ValidationError(
      'Auto shutdown is required for non-production services'
    );
  }
}

Mutation Policies

Mutation policies let you automatically modify Kubernetes manifests before they’re applied to the cluster. You can define custom rules by writing code to change these manifests based on your needs—no need to use Kustomize.

Common use cases include:

  • Setting node affinity for certain workloads like SSH servers or notebooks
  • Adding default secrets, volume mounts, or environment variables to services and jobs
  • Updating image prefixes to match internal repository setups

These policies run one after another, in a specific order. Policies with lower order values are applied first.

Here is a sample mutation policy which mutates the registry for the images to private jfrog repository.

TypeScript
import { MutationInput, MutationOutput } from '@src/types';

export function mutate(mutationInput: MutationInput): MutationOutput {
  const { generatedK8sManifests } = mutationInput;
  if (mutationInput.context.inputManifest.type !== 'service') {
    return { generatedK8sManifests };
  }

  if (generatedK8sManifests) {
    for (const manifest of generatedK8sManifests) {
      if (
        manifest.kind === 'Deployment'
      ) {
        manifest.spec.template.spec.containers.forEach((container: any) => {
          if (container.image.startsWith('tfy.jfrog.io')) {
            container.image = container.image.replace(
              'tfy.jfrog.io',
              'private.tfy.jfrog.io'
            );
          }
        });
      } 
    }
  }

  return { generatedK8sManifests };
}

Both validation and mutation policies are executed in a sandbox environment before deployment to ensure secure and isolated execution.

Policy Context

The policy context provides additional information that can be used to enforce more sophisticated policies.

Environment

The environment field contains the deployment environment manifest, which includes information like whether it’s a production environment or whether it’s optimized for Cost or Availability This allows you to enforce different policies based on the environment type.

Created By User

The createdByUser field in the context provides information about who initiated the deployment. This is useful for enforcing policies based on the deployment source.

For example, you might want to ensure that production deployments are only made through service accounts for better security and audit trails.

Here’s an example policy that enforces this:

TypeScript
import { ValidationInput, ValidationError } from '@src/types';

export function validate(validationInput: ValidationInput): void {
  const { context } = validationInput;
  const environment = context.environment;
  const isProduction = environment?.manifest.isProduction;

  if (!isProduction) return;

  if (context.createdByUser.subjectType !== 'serviceaccount') {
    throw new ValidationError(
      'Production deployments must be made using a service account'
    );
  }
}

Active Deployment

The activeDeployment field contains the manifest of the last successful deployment.

This is useful for policies that need to compare new deployments with the last known good state. For example, you might want to prevent changes to critical fields like image URIs after a successful deployment to maintain stability.

Creating a policy

Policies must be written in TypeScript. To write and test your policy code, refer to this repository: https://github.com/truefoundry/tfy-typescript-policy

  • Write your policy logic in src/policy.ts.
  • Import required models from src/models.ts.

Use type definitions from src/types.ts to ensure type-safe code.

Test your policy locally using:

npx ts-node local_run.ts

You can find policy code examples for common use cases in this folder: https://github.com/truefoundry/tfy-typescript-policy/tree/main/examples

Note: When creating/updating your policy, only provide the contents of policy.ts in the Policy Code input field.

Registering Your Policy on Truefoundry

After writing your policy code, you can register your policy on Truefoundry by following these steps:

1. Enter Policy Details

Provide the name and description of the policy.

2. Select Policy Action

  • Validate: Ensures manifests meet certain conditions.

  • Mutate: Modifies manifests before applying them.

    Mutate policies have an order associated that determines their execution order.

3. Choose Policy Mode

  • Audit: Logs policy executions but does not block deployments. Ideally when creating a policy, users should put it in
  • Enforce: Blocks deployments if the policy evaluation fails.
  • Disabled: The policy is ignored.

4. TypeScript Code:

Paste the typescript code that you wrote in previous step.

5. Define Entities

Specify the resource types the policy should apply to. (service, job, ssh-server etc)

6. Set Filters

Use Clusters, Environments, and Workspaces to filter applicable manifests. [by default it applies to all manifests]

Filters expect the name (not FQN) of workspaces, environments, and clusters.


Here is how it looks on UI:

You can find all Policies registered and see their specific runs:

You can also check the diff for a mutation policy also: