Skip to content

KRO Integration

TypeKro is built on top of Kubernetes Resource Orchestrator (KRO), which provides advanced resource orchestration with runtime dependencies and continuous reconciliation. This guide covers how to use TypeKro with KRO for production-grade infrastructure management.

What is KRO?

Kubernetes Resource Orchestrator (KRO) is an open-source project by AWS Labs, with contributions from Google, Microsoft, and the broader Kubernetes community. KRO enables:

  • Runtime Dependencies - Resources can reference each other's live cluster state
  • Continuous Reconciliation - Automatic updates when dependencies change
  • CEL Expressions - Dynamic resource configuration using Google's Common Expression Language
  • Custom Resource Types - Define your own Kubernetes resource types with TypeScript schemas
  • GitOps Native - Generates standard Kubernetes YAML for any GitOps workflow

KRO vs Direct Mode

FeatureDirect ModeKRO Mode
SetupNo dependenciesRequires KRO controller
Runtime DependenciesLimitedFull support
Continuous ReconciliationManualAutomatic
CEL ExpressionsBasicAdvanced
GitOpsYAML generationNative ResourceGraphDefinitions
Production UseDevelopment/TestingProduction recommended

Installing KRO

Prerequisites

  • Kubernetes cluster with admin access
  • kubectl configured for your cluster
  • Cluster version 1.20+ recommended
  • TypeKro installed in your project

TypeKro Bootstrap vs Manual Installation

ApproachProsCons
TypeKro Bootstrap• Type-safe installation
• Includes Flux CD for HelmRelease support
• Automatic readiness checking
• Consistent with TypeKro patterns
• Requires TypeKro dependency
• More opinionated setup
Manual kubectl• Direct control
• Minimal dependencies
• Official installation method
• Manual YAML management
• No readiness guarantees
• No Flux integration

Install KRO Controller

Use TypeKro's built-in bootstrap composition for a complete runtime setup:

typescript
import { typeKroRuntimeBootstrap } from 'typekro';

async function bootstrapKroEnvironment() {
  // Create bootstrap composition
  const bootstrap = typeKroRuntimeBootstrap({
    namespace: 'flux-system',
    fluxVersion: 'v2.4.0',
    kroVersion: '0.3.0'
  });

  // Deploy with direct factory
  const factory = await bootstrap.factory('direct', {
    namespace: 'flux-system',
    waitForReady: true,
    timeout: 300000 // 5 minutes
  });

  console.log('Installing Flux CD and KRO...');
  const result = await factory.deploy({
    namespace: 'flux-system'
  });

  console.log('KRO environment ready!', result.status);
}

bootstrapKroEnvironment().catch(console.error);

This approach:

  • ✅ Installs Flux CD controllers in flux-system namespace
  • ✅ Installs KRO via HelmRelease in kro namespace
  • ✅ Uses proper dependency management and readiness checking
  • ✅ Provides TypeScript-native installation experience

Option 2: Manual kubectl Installation

bash
# Install the latest KRO release
kubectl apply -f https://github.com/awslabs/kro/releases/latest/download/kro.yaml

# Verify installation
kubectl get pods -n kro-system
kubectl get crd | grep kro.run

Verify Installation

bash
# Check Flux controllers (if using TypeKro bootstrap)
kubectl get pods -n flux-system

# Check KRO controller is running
kubectl get pods -n kro

# Verify ResourceGraphDefinition CRD is installed
kubectl explain resourcegraphdefinition

# Check HelmRelease status (if using TypeKro bootstrap)
kubectl get helmrelease -n kro

Basic KRO Integration

Simple Resource Graph with KRO

typescript
import { type } from 'arktype';
import { toResourceGraph, simpleDeployment, simpleService, Cel } from 'typekro';

const WebAppSpec = type({
  name: 'string',
  image: 'string',
  replicas: 'number',
  environment: '"development" | "staging" | "production"'
});

const WebAppStatus = type({
  url: 'string',
  phase: 'string',
  readyReplicas: 'number',
  healthy: 'boolean'
});

const kroWebApp = toResourceGraph(
  {
    name: 'kro-webapp',
    apiVersion: 'example.com/v1alpha1',
    kind: 'KroWebApp',
    spec: WebAppSpec,
    status: WebAppStatus,
  },
  (schema) => ({
    deployment: simpleDeployment({
      name: schema.spec.name,
      image: schema.spec.image,
      replicas: schema.spec.replicas,
      ports: [{ containerPort: 3000 }]
    }),
    
    service: simpleService({
      name: Cel.expr(schema.spec.name, '-service'),
      selector: { app: schema.spec.name },
      ports: [{ port: 80, targetPort: 3000 }],
      type: schema.spec.environment === 'production' ? 'LoadBalancer' : 'ClusterIP'
    })
  }),
  (schema, resources) => ({
    // KRO will automatically evaluate these CEL expressions
    url: Cel.expr(
      schema.spec.environment, 
      '== "production" ? "http://" + ',
      resources.service.status.loadBalancer.ingress[0].ip,
      ': "http://" + ',
      resources.service.spec.clusterIP
    ),
    
    phase: resources.deployment.status.phase,
    readyReplicas: resources.deployment.status.readyReplicas,
    
    healthy: Cel.expr(
      resources.deployment.status.readyReplicas,
      '== ',
      resources.deployment.spec.replicas,
      '&& ',
      resources.deployment.status.phase,
      '== "Running"'
    )
  })
);

Deploy with KRO

typescript
// Option 1: Use KRO factory for direct deployment
const kroFactory = await kroWebApp.factory('kro', {
  namespace: 'production'
});

await kroFactory.deploy({
  name: 'production-webapp',
  image: 'myapp:v1.0.0',
  replicas: 5,
  environment: 'production'
});

// Option 2: Generate YAML for GitOps
const rgdYaml = kroWebApp.toYaml();
const instanceYaml = kroWebApp.toYaml({
  name: 'production-webapp',
  image: 'myapp:v1.0.0',
  replicas: 5,
  environment: 'production'
});

// Apply with kubectl
// kubectl apply -f webapp-rgd.yaml
// kubectl apply -f webapp-instance.yaml

Advanced KRO Features

Runtime Dependencies

KRO excels at handling complex runtime dependencies between resources:

typescript
const databaseStack = toResourceGraph(
  {
    name: 'database-stack',
    apiVersion: 'data.example.com/v1alpha1',
    kind: 'DatabaseStack',
    spec: DatabaseStackSpec,
    status: DatabaseStackStatus,
  },
  (schema) => ({
    // Database deployment
    database: simpleDeployment({
      name: Cel.expr(schema.spec.name, '-db'),
      image: 'postgres:15',
      env: {
        POSTGRES_DB: schema.spec.database.name,
        POSTGRES_USER: schema.spec.database.user,
        POSTGRES_PASSWORD: schema.spec.database.password
      },
      ports: [{ containerPort: 5432 }]
    }),
    
    // Database service
    databaseService: simpleService({
      name: Cel.expr(schema.spec.name, '-db-service'),
      selector: { app: Cel.expr(schema.spec.name, '-db') },
      ports: [{ port: 5432, targetPort: 5432 }]
    }),
    
    // Application waits for database to be ready
    app: simpleDeployment({
      name: schema.spec.name,
      image: schema.spec.image,
      
      // KRO ensures database is ready before starting app
      env: {
        DATABASE_URL: Cel.template(
          'postgresql://%s:%s@%s:5432/%s',
          schema.spec.database.user,
          schema.spec.database.password,
          databaseService.status.clusterIP,  // Runtime resolution
          schema.spec.database.name
        ),
        
        // Only start when database is ready
        WAIT_FOR_DB: Cel.expr(
          database.status.readyReplicas, '> 0 ? "ready" : "waiting"'
        )
      },
      
      // Readiness probe that checks database connection
      readinessProbe: {
        exec: {
          command: ['sh', '-c', 'pg_isready -h $DATABASE_HOST -p 5432']
        },
        initialDelaySeconds: 10,
        periodSeconds: 5
      }
    }),
    
    appService: simpleService({
      name: Cel.expr(schema.spec.name, '-service'),
      selector: { app: schema.spec.name },
      ports: [{ port: 80, targetPort: 3000 }]
    })
  }),
  (schema, resources) => ({
    // Status is automatically updated by KRO
    databaseReady: Cel.expr(resources.database.status.readyReplicas, '> 0'),
    appReady: Cel.expr(resources.app.status.readyReplicas, '> 0'),
    
    databaseEndpoint: Cel.template(
      '%s:5432',
      resources.databaseService.status.clusterIP
    ),
    
    appEndpoint: Cel.template(
      'http://%s',
      resources.appService.status.clusterIP
    ),
    
    // Complex dependency logic
    fullyOperational: Cel.expr(
      resources.database.status.readyReplicas, '> 0 && ',
      resources.app.status.readyReplicas, '> 0 && ',
      resources.databaseService.status.clusterIP, '!= ""'
    )
  })
);

Dynamic Scaling with KRO

typescript
const autoScalingStack = toResourceGraph(
  { name: 'autoscaling-stack', schema: { spec: AutoScalingSpec, status: AutoScalingStatus } },
  (schema) => ({
    app: simpleDeployment({
      name: schema.spec.name,
      image: schema.spec.image,
      
      // Dynamic replica count based on conditions
      replicas: Cel.expr(
        schema.spec.environment, 
        '== "production" ? ',
        schema.spec.scaling.maxReplicas,
        ': ',
        schema.spec.scaling.minReplicas
      ),
      
      resources: {
        requests: {
          cpu: schema.spec.resources.cpu,
          memory: schema.spec.resources.memory
        },
        limits: {
          cpu: Cel.expr(schema.spec.resources.cpu, '+ "00m"'),  // Add 00m to base CPU
          memory: Cel.expr(schema.spec.resources.memory, '+ "256Mi"')  // Add 256Mi to base memory
        }
      }
    }),
    
    service: simpleService({
      name: Cel.expr(schema.spec.name, '-service'),
      selector: { app: schema.spec.name },
      ports: [{ port: 80, targetPort: 3000 }]
    }),
    
    hpa: simpleHpa({
      name: Cel.expr(schema.spec.name, '-hpa'),
      scaleTargetRef: {
        apiVersion: 'apps/v1',
        kind: 'Deployment',
        name: schema.spec.name
      },
      minReplicas: schema.spec.scaling.minReplicas,
      maxReplicas: schema.spec.scaling.maxReplicas,
      
      // Dynamic target based on environment
      targetCPUUtilizationPercentage: Cel.expr(
        schema.spec.environment,
        '== "production" ? 70 : 90'
      )
    })
  }),
  (schema, resources) => ({
    currentReplicas: resources.app.status.readyReplicas,
    desiredReplicas: resources.app.spec.replicas,
    maxReplicas: resources.hpa.spec.maxReplicas,
    
    scalingActive: Cel.expr(
      resources.hpa.status.currentReplicas,
      '!= ',
      resources.hpa.status.desiredReplicas
    ),
    
    cpuUtilization: resources.hpa.status.currentCPUUtilizationPercentage || 0
  })
);

Multi-Environment Configuration

KRO makes it easy to deploy the same application across different environments using the factory pattern:

typescript
import { type } from 'arktype';
import { toResourceGraph, simpleDeployment, simpleService, Cel } from 'typekro';

const WebAppSpec = type({
  name: 'string',
  image: 'string',
  environment: '"development" | "staging" | "production"'
});

const WebAppStatus = type({
  ready: 'boolean',
  url: 'string',
  environment: 'string'
});

const multiEnvApp = toResourceGraph(
  {
    name: 'multi-env-app',
    apiVersion: 'apps.example.com/v1',
    kind: 'MultiEnvApp',
    spec: WebAppSpec,
    status: WebAppStatus
  },
  (schema) => ({
    // Simple deployment with environment-aware configuration
    app: simpleDeployment({
      name: schema.spec.name,
      image: schema.spec.image,
      replicas: Cel.conditional(
        schema.spec.environment === 'production',
        5,
        Cel.conditional(schema.spec.environment === 'staging', 3, 1)
      ),
      ports: [8080],
      env: {
        NODE_ENV: schema.spec.environment,
        LOG_LEVEL: Cel.conditional(
          schema.spec.environment === 'production',
          'warn',
          'debug'
        )
      }
    }),
    
    // Service with environment-appropriate type
    service: simpleService({
      name: schema.spec.name,
      selector: { app: schema.spec.name },
      ports: [{ port: 80, targetPort: 8080 }],
      type: Cel.conditional(
        schema.spec.environment === 'production',
        'LoadBalancer',
        'ClusterIP'
      )
    })
  }),
  (schema, resources) => ({
    ready: Cel.expr(resources.app.status.readyReplicas, ' > 0'),
    url: Cel.conditional(
      schema.spec.environment === 'production',
      Cel.template('https://%s.example.com', schema.spec.name),
      Cel.template('http://%s', resources.service.spec.clusterIP)
    ),
    environment: schema.spec.environment
  })
);

// Deploy to different environments using the same code
async function deployToEnvironments() {
  // Development
  const devFactory = await multiEnvApp.factory('direct', { namespace: 'dev' });
  await devFactory.deploy({
    name: 'myapp-dev',
    image: 'myapp:latest',
    environment: 'development'
  });
  
  // Staging  
  const stagingFactory = await multiEnvApp.factory('kro', { namespace: 'staging' });
  await stagingFactory.deploy({
    name: 'myapp-staging',
    image: 'myapp:v1.2.0',
    environment: 'staging'
  });
  
  // Production
  const prodFactory = await multiEnvApp.factory('kro', { namespace: 'production' });
  await prodFactory.deploy({
    name: 'myapp',
    image: 'myapp:v1.1.5',
    environment: 'production'
  });
}
);

KRO Resource Graph Lifecycle

1. ResourceGraphDefinition Creation

When you use KRO mode, TypeKro generates a ResourceGraphDefinition:

yaml
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
  name: webapp
spec:
  schema:
    spec:
      type: object
      properties:
        name: { type: string }
        image: { type: string }
        replicas: { type: integer }
        environment: { type: string, enum: ["development", "staging", "production"] }
    status:
      type: object
      properties:
        url: { type: string }
        phase: { type: string }
        readyReplicas: { type: integer }
        healthy: { type: boolean }
  
  graph:
    nodes:
      deployment:
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: ${spec.name}
        spec:
          replicas: ${spec.replicas}
          # ... rest of deployment spec
      
      service:
        apiVersion: v1
        kind: Service
        metadata:
          name: ${spec.name}-service
        spec:
          selector:
            app: ${spec.name}
          # ... rest of service spec
    
    statusMappings:
      url: |
        spec.environment == "production" ? 
        "http://" + nodes.service.status.loadBalancer.ingress[0].ip :
        "http://" + nodes.service.spec.clusterIP
      phase: nodes.deployment.status.phase
      readyReplicas: nodes.deployment.status.readyReplicas
      healthy: |
        nodes.deployment.status.readyReplicas == nodes.deployment.spec.replicas &&
        nodes.deployment.status.phase == "Running"

2. Instance Deployment

Create an instance of your ResourceGraphDefinition:

yaml
apiVersion: example.com/v1alpha1
kind: WebApp
metadata:
  name: production-webapp
  namespace: production
spec:
  name: production-webapp
  image: myapp:v1.0.0
  replicas: 5
  environment: production

3. KRO Controller Processing

The KRO controller:

  1. Reads the instance and matches it to the ResourceGraphDefinition
  2. Evaluates CEL expressions to generate concrete resource specifications
  3. Creates resources in the correct dependency order
  4. Monitors status and updates the instance status automatically
  5. Handles updates by re-evaluating expressions and updating resources

Monitoring KRO Deployments

Check ResourceGraphDefinition Status

bash
# List all ResourceGraphDefinitions
kubectl get resourcegraphdefinition

# Get detailed information
kubectl describe resourcegraphdefinition webapp

# Check if RGD is ready
kubectl get rgd webapp -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'

Monitor Instance Status

bash
# Check instance status
kubectl get webapp production-webapp -o yaml

# Watch status updates in real-time
kubectl get webapp production-webapp -w

# Get specific status fields
kubectl get webapp production-webapp -o jsonpath='{.status.url}'
kubectl get webapp production-webapp -o jsonpath='{.status.healthy}'

Debug KRO Issues

bash
# Check KRO controller logs
kubectl logs -n kro-system deployment/kro-controller-manager

# Check resource events
kubectl describe webapp production-webapp

# Validate CEL expressions
kubectl get rgd webapp -o jsonpath='{.spec.statusMappings}'

Advanced KRO Patterns

Conditional Resource Creation

typescript
const conditionalStack = toResourceGraph(
  { name: 'conditional-stack', schema: { spec: ConditionalSpec } },
  (schema) => ({
    app: simpleDeployment({
      name: schema.spec.name,
      image: schema.spec.image
    }),
    
    service: simpleService({
      name: Cel.expr(schema.spec.name, '-service'),
      selector: { app: schema.spec.name },
      ports: [{ port: 80, targetPort: 3000 }]
    }),
    
    // Only create monitoring resources in production
    ...(schema.spec.environment === 'production' && {
      serviceMonitor: {
        apiVersion: 'monitoring.coreos.com/v1',
        kind: 'ServiceMonitor',
        metadata: {
          name: Cel.expr(schema.spec.name, '-monitor')
        },
        spec: {
          selector: {
            matchLabels: { app: schema.spec.name }
          },
          endpoints: [{
            port: 'metrics',
            path: '/metrics'
          }]
        }
      }
    }),
    
    // Only create ingress for external environments
    ...(schema.spec.external && {
      ingress: simpleIngress({
        name: Cel.expr(schema.spec.name, '-ingress'),
        rules: [{
          host: schema.spec.hostname,
          http: {
            paths: [{
              path: '/',
              pathType: 'Prefix',
              backend: {
                service: {
                  name: service.metadata.name,
                  port: { number: 80 }
                }
              }
            }]
          }
        }]
      })
    })
  }),
  (schema, resources) => ({
    hasMonitoring: schema.spec.environment === 'production',
    hasIngress: schema.spec.external,
    
    accessUrl: Cel.expr(
      schema.spec.external,
      '? "https://" + ',
      schema.spec.hostname,
      ': "http://" + ',
      resources.service.spec.clusterIP
    )
  })
);

Cross-Graph Dependencies

typescript
// Shared infrastructure graph
const infraGraph = toResourceGraph(
  { name: 'infrastructure', schema: { spec: InfraSpec, status: InfraStatus } },
  (schema) => ({
    database: simpleDeployment({
      name: 'shared-database',
      image: 'postgres:15'
    }),
    
    databaseService: simpleService({
      name: 'shared-database-service',
      selector: { app: 'shared-database' },
      ports: [{ port: 5432, targetPort: 5432 }]
    }),
    
    redis: simpleDeployment({
      name: 'shared-redis',
      image: 'redis:7'
    }),
    
    redisService: simpleService({
      name: 'shared-redis-service',
      selector: { app: 'shared-redis' },
      ports: [{ port: 6379, targetPort: 6379 }]
    })
  }),
  (schema, resources) => ({
    databaseEndpoint: Cel.template(
      '%s:5432',
      resources.databaseService.status.clusterIP
    ),
    
    redisEndpoint: Cel.template(
      '%s:6379',
      resources.redisService.status.clusterIP
    ),
    
    ready: Cel.expr(
      resources.database.status.readyReplicas, '> 0 && ',
      resources.redis.status.readyReplicas, '> 0'
    )
  })
);

// Application that depends on shared infrastructure
const appWithInfra = toResourceGraph(
  { name: 'app-with-infra', schema: { spec: AppWithInfraSpec } },
  (schema) => ({
    app: simpleDeployment({
      name: schema.spec.name,
      image: schema.spec.image,
      env: {
        // Reference shared infrastructure endpoints
        DATABASE_URL: Cel.template('postgresql://user:pass@%s/myapp', schema.spec.infrastructure.databaseEndpoint),
        REDIS_URL: Cel.template('redis://%s/0', schema.spec.infrastructure.redisEndpoint),
        
        // Wait for infrastructure to be ready
        WAIT_FOR_INFRA: Cel.expr(
          schema.spec.infrastructure.ready,
          '? "ready" : "waiting"'
        )
      }
    })
  }),
  (schema, resources) => ({
    appReady: Cel.expr(resources.app.status.readyReplicas, '> 0'),
    
    infraReady: schema.spec.infrastructure.ready,
    
    fullyReady: Cel.expr(
      resources.app.status.readyReplicas, '> 0 && ',
      schema.spec.infrastructure.ready
    )
  })
);

KRO Best Practices

1. Design for Reconciliation

typescript
// ✅ Design resources that can be safely reconciled
const reconcilableStack = toResourceGraph(
  definition,
  (schema) => ({
    app: simpleDeployment({
      name: schema.spec.name,
      image: schema.spec.image,
      
      // Use declarative configuration
      replicas: schema.spec.replicas,
      
      // Idempotent environment variables
      env: {
        APP_VERSION: schema.spec.version,
        DEPLOYMENT_TIME: Cel.expr('string(now())')  // Updated on each reconciliation
      }
    })
  }),
  statusBuilder
);

2. Use Meaningful Status

typescript
// ✅ Provide actionable status information
const statusBuilder = (schema, resources) => ({
  // High-level state
  phase: resources.app.status.phase,
  
  // Operational metrics
  replicas: {
    desired: resources.app.spec.replicas,
    ready: resources.app.status.readyReplicas,
    available: resources.app.status.availableReplicas
  },
  
  // Service information
  endpoints: {
    internal: Cel.template('http://%s:80', resources.service.spec.clusterIP),
    external: Cel.expr(
      resources.service.status.loadBalancer.ingress,
      '.size() > 0 ? "http://" + ',
      resources.service.status.loadBalancer.ingress[0].ip,
      ': null'
    )
  },
  
  // Health indicators
  healthy: Cel.expr(
    resources.app.status.readyReplicas,
    '== ',
    resources.app.spec.replicas
  ),
  
  // Timestamp for tracking
  lastUpdated: Cel.expr('string(now())')
});

3. Handle Dependencies Gracefully

typescript
// ✅ Design robust dependency chains
const dependentStack = toResourceGraph(
  definition,
  (schema) => ({
    database: simpleDeployment({
      name: 'database',
      image: 'postgres:15'
    }),
    
    app: simpleDeployment({
      name: 'app',
      image: schema.spec.image,
      
      // Safe dependency reference with fallback
      env: {
        DATABASE_HOST: Cel.expr(
          database.status.readyReplicas,
          '> 0 ? ',
          database.status.podIP,
          ': "localhost"'  // Fallback value
        )
      },
      
      // Readiness probe that waits for database
      readinessProbe: {
        exec: {
          command: ['sh', '-c', 'nc -z $DATABASE_HOST 5432']
        },
        initialDelaySeconds: 30,
        periodSeconds: 10
      }
    })
  }),
  statusBuilder
);

Troubleshooting KRO Integration

Common Issues

ResourceGraphDefinition not found:

bash
# Check if RGD was created
kubectl get rgd
kubectl describe rgd webapp

# Verify KRO controller is running
kubectl get pods -n kro-system

CEL expression evaluation errors:

bash
# Check instance status for CEL errors
kubectl describe webapp my-app

# Validate CEL expressions manually
kubectl get rgd webapp -o jsonpath='{.spec.statusMappings.url}'

Resource creation failures:

bash
# Check resource events
kubectl get events --sort-by=.metadata.creationTimestamp

# Check individual resource status
kubectl get deployment,service,configmap -l app=my-app

Status not updating:

bash
# Check KRO controller logs
kubectl logs -n kro-system deployment/kro-controller-manager -f

# Verify resource status in cluster
kubectl get deployment my-app -o jsonpath='{.status}'

Migration from Direct to KRO Mode

1. Test with Dual Deployment

typescript
// Deploy the same graph in both modes for comparison
const directFactory = await graph.factory('direct', { namespace: 'test-direct' });
const kroFactory = await graph.factory('kro', { namespace: 'test-kro' });

// Deploy to both environments
await Promise.all([
  directFactory.deploy(spec),
  kroFactory.deploy(spec)
]);

// Compare results
const directStatus = await directFactory.getStatus();
const kroStatus = await kroFactory.getStatus();

2. Gradual Migration

typescript
// Start with non-critical applications
const testApps = ['test-app-1', 'test-app-2'];
for (const app of testApps) {
  const kroFactory = await graph.factory('kro');
  await kroFactory.deploy({ name: app, /* ... */ });
}

// Monitor and validate before migrating production

3. Rollback Strategy

typescript
// Keep direct deployment as backup
const rollbackPlan = {
  async rollback() {
    // Delete KRO resources
    await kubectl('delete', 'webapp', 'my-app');
    
    // Deploy with direct mode
    const directFactory = await graph.factory('direct');
    await directFactory.deploy(lastKnownGoodSpec);
  }
};

Next Steps

Released under the Apache 2.0 License.