CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pulumi--pulumi

Pulumi's Node.js SDK for infrastructure-as-code platform that allows you to create, deploy, and manage infrastructure using familiar programming languages and tools.

85

1.02x
Overview
Eval results
Files

provider-development.mddocs/

Provider Development

Pulumi's provider development framework enables building custom resource providers for new cloud services and infrastructure platforms, extending Pulumi's ecosystem beyond the built-in providers.

Core Provider Interface

interface Provider {
  check?(urn: string, olds: any, news: any, randomSeed?: Buffer): Promise<CheckResult>;
  create?(urn: string, inputs: any, timeout?: number, preview?: boolean): Promise<CreateResult>;
  read?(id: string, urn: string, props?: any): Promise<ReadResult>;
  update?(id: string, urn: string, olds: any, news: any, timeout?: number, ignoreChanges?: string[], preview?: boolean): Promise<UpdateResult>;
  delete?(id: string, urn: string, props: any, timeout?: number): Promise<void>;
  construct?(name: string, type: string, inputs: any, options: any): Promise<ConstructResult>;
  invoke?(token: string, inputs: any, options?: any): Promise<InvokeResult>;
  parameterize?(parameters: any): Promise<ParameterizeResult>;
  configure?(inputs: any): Promise<void>;
}

function main(provider: Provider, args?: string[]): Promise<void>;

Provider Result Types

interface CheckResult {
  inputs?: any;
  failures?: CheckFailure[];
}

interface CreateResult {
  id: string;
  properties?: any;
}

interface ReadResult {
  id?: string;
  properties?: any;
  inputs?: any;
}

interface UpdateResult {
  properties?: any;
}

interface ConstructResult {
  urn: string;
  state?: any;
}

interface InvokeResult {
  properties?: any;
  failures?: CheckFailure[];
}

interface ParameterizeResult {
  name: string;
  version: string;
}

interface CheckFailure {
  property: string;
  reason: string;
}

Utility Functions

function deserializeInputs(inputsStruct: any, inputDependencies: any): any;
function containsOutputs(input: any): boolean;
function serializeProperties(props: any, keepOutputValues?: boolean): any;
function deserializeProperties(props: any): any;

Usage Examples

Basic Provider Implementation

import * as pulumi from "@pulumi/pulumi";
import * as provider from "@pulumi/pulumi/provider";

interface MyServiceConfig {
  apiEndpoint?: string;
  apiToken?: string;
}

class MyProvider implements provider.Provider {
  private config: MyServiceConfig = {};

  async configure(inputs: any): Promise<void> {
    this.config = inputs;
  }

  async check(urn: string, olds: any, news: any): Promise<provider.CheckResult> {
    const failures: provider.CheckFailure[] = [];
    
    // Validate required properties
    if (!news.name) {
      failures.push({ property: "name", reason: "name is required" });
    }
    
    return { inputs: news, failures };
  }

  async create(urn: string, inputs: any): Promise<provider.CreateResult> {
    // Create resource via external API
    const response = await this.callApi('POST', '/resources', inputs);
    
    return {
      id: response.id,
      properties: {
        ...inputs,
        status: response.status,
        createdAt: response.createdAt,
      },
    };
  }

  async read(id: string, urn: string, props?: any): Promise<provider.ReadResult> {
    try {
      const response = await this.callApi('GET', `/resources/${id}`);
      
      return {
        id: response.id,
        properties: {
          name: response.name,
          status: response.status,
          createdAt: response.createdAt,
        },
      };
    } catch (error) {
      if (error.statusCode === 404) {
        return { id: undefined, properties: undefined };
      }
      throw error;
    }
  }

  async update(id: string, urn: string, olds: any, news: any): Promise<provider.UpdateResult> {
    const response = await this.callApi('PUT', `/resources/${id}`, news);
    
    return {
      properties: {
        ...news,
        status: response.status,
        updatedAt: response.updatedAt,
      },
    };
  }

  async delete(id: string, urn: string, props: any): Promise<void> {
    await this.callApi('DELETE', `/resources/${id}`);
  }

  async invoke(token: string, inputs: any): Promise<provider.InvokeResult> {
    switch (token) {
      case "myservice:index:getResource":
        const response = await this.callApi('GET', `/resources/${inputs.id}`);
        return { properties: response };
        
      default:
        throw new Error(`Unknown invoke token: ${token}`);
    }
  }

  private async callApi(method: string, path: string, body?: any): Promise<any> {
    const response = await fetch(`${this.config.apiEndpoint}${path}`, {
      method,
      headers: {
        'Authorization': `Bearer ${this.config.apiToken}`,
        'Content-Type': 'application/json',
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    if (!response.ok) {
      throw new Error(`API call failed: ${response.status} ${response.statusText}`);
    }

    return response.json();
  }
}

// Start the provider
provider.main(new MyProvider());

Component Resource Provider

import * as pulumi from "@pulumi/pulumi";
import * as provider from "@pulumi/pulumi/provider";

class ComponentProvider implements provider.Provider {
  async construct(name: string, type: string, inputs: any, options: any): Promise<provider.ConstructResult> {
    switch (type) {
      case "myservice:index:WebApp":
        return this.constructWebApp(name, inputs, options);
        
      default:
        throw new Error(`Unknown component type: ${type}`);
    }
  }

  private async constructWebApp(name: string, inputs: any, options: any): Promise<provider.ConstructResult> {
    // Create child resources programmatically
    const bucket = new pulumi.aws.s3.Bucket(`${name}-bucket`, {
      website: {
        indexDocument: "index.html",
      },
    });

    const distribution = new pulumi.aws.cloudfront.Distribution(`${name}-cdn`, {
      origins: [{
        domainName: bucket.websiteEndpoint,
        originId: "S3-Website",
        customOriginConfig: {
          httpPort: 80,
          httpsPort: 443,
          originProtocolPolicy: "http-only",
        },
      }],
      defaultCacheBehavior: {
        targetOriginId: "S3-Website",
        viewerProtocolPolicy: "redirect-to-https",
        compress: true,
        allowedMethods: ["GET", "HEAD"],
      },
      enabled: true,
    });

    // Return component state
    return {
      urn: `urn:pulumi:${pulumi.getStack()}::${pulumi.getProject()}::${type}::${name}`,
      state: {
        bucketName: bucket.id,
        distributionId: distribution.id,
        websiteUrl: pulumi.interpolate`https://${distribution.domainName}`,
      },
    };
  }
}

provider.main(new ComponentProvider());

Provider with Schema

import * as pulumi from "@pulumi/pulumi";
import * as provider from "@pulumi/pulumi/provider";

interface DatabaseResourceInputs {
  name: string;
  size?: "small" | "medium" | "large";
  backupRetention?: number;
  multiAZ?: boolean;
}

class DatabaseProvider implements provider.Provider {
  async check(urn: string, olds: any, news: DatabaseResourceInputs): Promise<provider.CheckResult> {
    const failures: provider.CheckFailure[] = [];
    
    // Validate name
    if (!news.name || news.name.length < 3) {
      failures.push({ 
        property: "name", 
        reason: "name must be at least 3 characters long" 
      });
    }
    
    // Validate size
    if (news.size && !["small", "medium", "large"].includes(news.size)) {
      failures.push({ 
        property: "size", 
        reason: "size must be one of: small, medium, large" 
      });
    }
    
    // Validate backup retention
    if (news.backupRetention !== undefined && 
        (news.backupRetention < 0 || news.backupRetention > 35)) {
      failures.push({ 
        property: "backupRetention", 
        reason: "backupRetention must be between 0 and 35 days" 
      });
    }
    
    return { inputs: news, failures };
  }

  async create(urn: string, inputs: DatabaseResourceInputs): Promise<provider.CreateResult> {
    // Map size to instance type
    const instanceTypeMap = {
      small: "db.t3.micro",
      medium: "db.t3.small", 
      large: "db.t3.medium",
    };
    
    const instanceType = instanceTypeMap[inputs.size || "small"];
    
    // Create RDS instance (pseudo-code)
    const dbInstance = new pulumi.aws.rds.Instance(`${inputs.name}-db`, {
      instanceClass: instanceType,
      engine: "postgres",
      dbName: inputs.name,
      multiAz: inputs.multiAZ || false,
      backupRetentionPeriod: inputs.backupRetention || 7,
      // ... other properties
    });
    
    return {
      id: inputs.name,
      properties: {
        name: inputs.name,
        size: inputs.size || "small",
        instanceType: instanceType,
        endpoint: dbInstance.endpoint,
        port: dbInstance.port,
      },
    };
  }

  async invoke(token: string, inputs: any): Promise<provider.InvokeResult> {
    switch (token) {
      case "mydb:index:getAvailableVersions":
        return { 
          properties: { 
            versions: ["13.7", "14.6", "15.2"] 
          } 
        };
        
      case "mydb:index:validateName":
        const isValid = /^[a-zA-Z][a-zA-Z0-9_]*$/.test(inputs.name);
        return { 
          properties: { 
            valid: isValid,
            message: isValid ? "Name is valid" : "Name must start with letter and contain only alphanumeric characters and underscores"
          } 
        };
        
      default:
        throw new Error(`Unknown invoke: ${token}`);
    }
  }
}

provider.main(new DatabaseProvider());

Provider with Configuration Schema

import * as pulumi from "@pulumi/pulumi";
import * as provider from "@pulumi/pulumi/provider";

interface ProviderConfig {
  endpoint: string;
  apiKey: string;
  region?: string;
  timeout?: number;
}

class ConfigurableProvider implements provider.Provider {
  private config: ProviderConfig;

  async configure(inputs: any): Promise<void> {
    // Validate configuration
    if (!inputs.endpoint) {
      throw new Error("endpoint is required");
    }
    
    if (!inputs.apiKey) {
      throw new Error("apiKey is required");
    }
    
    this.config = {
      endpoint: inputs.endpoint,
      apiKey: inputs.apiKey,
      region: inputs.region || "us-east-1",
      timeout: inputs.timeout || 30000,
    };
    
    // Test connection
    await this.testConnection();
  }

  private async testConnection(): Promise<void> {
    try {
      const response = await fetch(`${this.config.endpoint}/health`, {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${this.config.apiKey}`,
        },
        signal: AbortSignal.timeout(this.config.timeout!),
      });
      
      if (!response.ok) {
        throw new Error(`Health check failed: ${response.status}`);
      }
    } catch (error) {
      throw new Error(`Failed to connect to ${this.config.endpoint}: ${error.message}`);
    }
  }

  async check(urn: string, olds: any, news: any): Promise<provider.CheckResult> {
    // Implementation...
    return { inputs: news, failures: [] };
  }

  // Other provider methods...
}

provider.main(new ConfigurableProvider());

Best Practices

  • Implement comprehensive input validation in the check method
  • Handle all error cases gracefully with meaningful error messages
  • Use appropriate timeouts for external API calls
  • Implement proper retry logic for transient failures
  • Cache provider configuration after successful setup
  • Use structured logging for debugging provider issues
  • Implement proper cleanup in delete operations
  • Handle resource state drift in read operations
  • Use semantic versioning for provider releases
  • Provide comprehensive documentation and examples
  • Test providers thoroughly with various input combinations
  • Implement proper secret handling for sensitive data
  • Use type-safe interfaces for resource inputs and outputs
  • Consider implementing preview mode for destructive operations

Install with Tessl CLI

npx tessl i tessl/npm-pulumi--pulumi

docs

asset-management.md

automation.md

configuration.md

dynamic-resources.md

index.md

logging-diagnostics.md

output-system.md

provider-development.md

resource-management.md

runtime-operations.md

stack-references.md

utilities.md

tile.json