Skip to main content

CLI Implementation Guide

This document describes the CLI implementation architecture and how to extend it.

Architecture Overview​

The CLI tools follow a clean architecture pattern with three distinct layers:

┌─────────────────────────────────────────────────────┐
│ Entry Points │
│ (bin/ingestor, bin/backstage-export) │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ Adapters │
│ (CLIAdapter, RuntimeAdapter, ExportAdapter) │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ Core Engine │
│ (IngestionEngine, Validators, Builders) │
└─────────────────────────────────────────────────────┘

Key Design Principles​

1. Unified Core Logic​

The IngestionEngine contains all business logic for resource transformation. This ensures that whether a resource is processed via CLI or Backstage runtime, the output is identical.

2. Adapter Pattern​

Adapters handle environment-specific I/O:

  • CLIAdapter: File system operations, console output
  • RuntimeAdapter: Kubernetes API access, Backstage catalog integration
  • ExportAdapter: Backstage REST API communication

3. Dependency Injection​

The engine accepts validators and builders via constructor injection, making it easy to extend with new resource types.

Adding New Resource Types​

To support a new Kubernetes resource type:

1. Create a Builder​

// src/core/builders/MyResourceBuilder.ts
export class MyResourceBuilder implements IEntityBuilder {
canBuild(resource: Resource): boolean {
return resource.kind === 'MyResource';
}

async build(resource: Resource, config?: EntityBuilderConfig): Promise<Entity[]> {
// Transform resource to Backstage entities
return [{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: resource.metadata.name,
// ... more metadata
},
spec: {
// ... component spec
}
}];
}
}

2. Register the Builder​

// src/cli/ingestor-cli.ts
import { createMyResourceBuilder } from '../core/builders/MyResourceBuilder';

const builders = [
createXRDEntityBuilder(),
createMyResourceBuilder(), // Add your builder
];

3. Add Validation (Optional)​

// src/core/validators/ResourceValidator.ts
private validateMyResource(resource: Resource, errors: ValidationError[]): void {
// Add resource-specific validation
if (!resource.spec.requiredField) {
errors.push({
field: 'spec.requiredField',
message: 'Required field is missing',
severity: 'error'
});
}
}

CLI Development Workflow​

Building​

# Compile TypeScript
yarn build:cli

# Output goes to dist/ directory

Testing Locally​

# Use ts-node for development (no build needed)
yarn cli:ingestor test.yaml --preview

# Or run compiled version
node dist/cli/ingestor-cli.js test.yaml

Debugging​

# Enable Node.js debugging
NODE_OPTIONS="--inspect" yarn cli:ingestor test.yaml

# With VS Code debugger
# Add breakpoint in source and use "Debug: Attach to Node Process"

Export CLI Implementation​

The export CLI uses a different pattern since it doesn't share logic with runtime:

BackstageClient → ExportAdapter → File System

BackstageClient​

Handles REST API communication:

  • Bearer token authentication
  • Pagination support
  • Query parameter construction
  • Error handling with retries

ExportAdapter​

Orchestrates the export process:

  • Filter building
  • Entity organization
  • Manifest generation
  • Multiple output formats

Testing Strategy​

Unit Tests​

Test individual components in isolation:

// src/core/engine/IngestionEngine.test.ts
describe('IngestionEngine', () => {
it('should validate resources before ingestion', async () => {
const validator = createMockValidator();
const engine = new IngestionEngine(validator, []);

const results = await engine.validate([mockResource]);
expect(results.get(mockResource)).toHaveProperty('valid', true);
});
});

Integration Tests​

Test complete flows:

// src/cli/integration.test.ts
describe('CLI Integration', () => {
it('should ingest XRD and produce template', async () => {
const output = await runCLI(['test-xrd.yaml', '--format', 'json']);
const entities = JSON.parse(output);

expect(entities).toHaveLength(2); // Template + API
expect(entities[0].kind).toBe('Template');
});
});

E2E Tests​

Test with real Backstage instance:

# Start Backstage
yarn start

# Run export test
backstage-export --url http://localhost:7007 --list

# Verify output

Error Handling​

The CLI tools use consistent error handling:

  1. Validation Errors: Show field-level issues
  2. Connection Errors: Suggest troubleshooting steps
  3. File System Errors: Check permissions and paths
  4. API Errors: Include status codes and messages

Error Codes​

CodeMeaning
0Success
1General error
2Authentication failed
3Connection error
4No entities found
5Write error

Performance Considerations​

Memory Usage​

  • Stream large files instead of loading into memory
  • Process resources in batches
  • Clear caches between operations

Optimization Tips​

  • Use --quiet flag to reduce console output overhead
  • Process directories in parallel when possible
  • Cache validated resources to avoid re-validation

Future Enhancements​

Potential areas for improvement:

  1. Plugin System: Allow external builders/validators
  2. Streaming Output: Support for stdout piping
  3. Progress Indicators: Show progress for large operations
  4. Diff Mode: Compare before/after states
  5. Watch Mode: Auto-process on file changes
  6. Remote Sources: Support URLs and S3 buckets