diff --git a/.gitignore b/.gitignore index 9f11b75..68729d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea/ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index b5d8d7b..0e70be1 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ You can see the published documentation at https://terraform-ibm-modules.github. - [Porting an older module ](https://terraform-ibm-modules.github.io/documentation/#/migrate-module.md) - [Validation tests](https://terraform-ibm-modules.github.io/documentation/#/tests.md) - [GitHub Actions workflows](https://terraform-ibm-modules.github.io/documentation/#/gh-actions.md) + - [Pulumi and IBMCloud](https://terraform-ibm-modules.github.io/documentation/#/pulumi.md) - [Adding your module to IBM Cloud](https://terraform-ibm-modules.github.io/documentation/#/onboard-ibm-cloud.md) - Maintaining the GitHub project - [Maintainers contribution process](https://terraform-ibm-modules.github.io/documentation/#/maintain-module.md) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 4b99a59..95ca2f3 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -7,6 +7,7 @@ - [Porting an older module ](migrate-module.md) - [Validation tests](tests.md) - [GitHub Actions workflows](gh-actions.md) + - [Pulumi and IBMCloud](pulumi.md) - [Adding your module to IBM Cloud](onboard-ibm-cloud.md) - Maintaining the GitHub project - [Maintainers contribution process](maintain-module.md) diff --git a/docs/pulumi.md b/docs/pulumi.md new file mode 100644 index 0000000..fdcbd28 --- /dev/null +++ b/docs/pulumi.md @@ -0,0 +1,889 @@ +# Use your favorite programming language with Terraform IBM Modules and Pulumi + +[Pulumi](https://www.pulumi.com/docs/iac/get-started/) is an open-source Infrastructure as Code (IaC) platform that lets you automate, secure, and manage cloud resources, configurations, and secrets — all using real programming languages like Python, Go, .NET, TypeScript, C# etc. + +With Pulumi, you can define cloud infrastructure using familiar coding practices such as loops, functions, and classes, making your cloud automation more powerful and maintainable than traditional declarative tools. + +When used with IBM Cloud, Pulumi allows you to provision and manage a wide range of IBM Cloud services — such as VPCs, Kubernetes clusters, Object Storage, and Databases — using Python or any supported language. + +This enables developers to: + +- Write infrastructure code in a language they already know. +- Automate IBM Cloud environments using version control and CI/CD pipelines. +- Integrate IBM Terraform modules to reuse existing templates and best practices. + +## Table of Contents + +- [Introduction to Pulumi with Python](#introduction) +- [Installation](#installation) +- [Key Concepts](#concepts) +- [Pulumi Configuration](#pulumi-configuration) +- [Pulumi Commands](#commands) +- [Usage](#usage) +- [Python Examples](#python-examples) +- [Best Practices](#best-practices) +- [Troubleshooting Common Issues](#troubleshooting-common-issues) +- [References](#references) + +## Introduction + +Pulumi is an Infrastructure as Code (IaC) platform that allows you to define cloud infrastructure using various programming languages. This tutorial focuses on using Pulumi with IBM Cloud through Python. + +**Why Pulumi with Python?** + +- Familiar syntax: Use Python, one of the most popular programming languages. +- Rich ecosystem: Leverage Python's extensive library ecosystem. +- Strong typing: Benefit from type hints and IDE support. +- Reusable components: Create classes and modules for infrastructure. +- Testing: Write unit tests for your infrastructure code. + + +## Installation + +Before starting, ensure you have the following: + +1. **IBM Cloud Account** + - Sign up at [https://cloud.ibm.com](https://cloud.ibm.com). + - Create and download the IBMCloud API key. + +2. **Installed Tools** + + | Tool | Description | Installation | + |------|--------------|---------------| + | Pulumi | Infrastructure as Code tool | [Install Pulumi](https://www.pulumi.com/docs/install/) | + | Python | Runtime for your Pulumi program | [Install Python 3.8+](https://www.python.org/downloads/) | + | IBM Cloud CLI | To manage cloud resources | [Install IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cli-getting-started) | + | Terraform (optional) | For using IBM Terraform modules | [Install Terraform](https://developer.hashicorp.com/terraform/downloads) | + +3. **Set Environment Variables** + + ```bash + export IBMCLOUD_API_KEY= + export PULUMI_CONFIG_PASSPHRASE= + + # Install Pulumi + curl -fsSL https://get.pulumi.com | sh + + # Install dependencies: + pip install pulumi pulumi_ibm + + # Verify installation + pulumi version + python --version + + # Configure IBM Cloud: + + ibmcloud login + pulumi config set ibm:region us-south + + # Create a new Pulumi Project: + + mkdir pulumi-ibmcloud-demo && cd pulumi-ibmcloud-demo + pulumi new python -y + ``` + +## Concepts + +**Stack** - A Stack is an isolated instance of your Pulumi program, typically representing an environment (dev, staging, prod). + +```py +import pulumi + +# Get current stack name +stack = pulumi.get_stack() +print(f"Current stack: {stack}") +``` + +**Project** - A Project is a single Pulumi program containing your infrastructure definitions. + +**Configuration** - Stack-specific settings that can be changed without modifying code. + +**State** - Pulumi maintains state to track current infrastructure and manage updates. + + +## Pulumi Configuration + +Configuration Files - Configure IBM Cloud Provider in Pulumi + +In `Pulumi.yaml`, add: + +```yaml +name: pulumi-ibmcloud-demo +runtime: python +description: IBM Cloud infrastructure example using Python +``` + +In `Pulumi.dev.yaml` add the Stack-specific configuration: + +```py +config: + pulumi-ibmcloud-demo::apiKey: ${IBMCLOUD_API_KEY} + pulumi-ibmcloud-demo:region: us-south + pulumi-ibmcloud-demo:resource-group: default + pulumi-ibmcloud-demo:cos-instance-name: my-dev-cos +``` + +The IBM Cloud provider must be installed as a Local Package. Use below command to successfully generate a Python SDK for the ibm package: + +```bash +pulumi package add terraform-provider ibm-cloud/ibm +``` + +Setting Configuration + +```bash +# Set configuration values +pulumi config set region us-south +pulumi config set resource-group default --secret +pulumi config set --path tags '["env:dev", "team:platform"]' +``` + +Accessing Configuration in Python + +```py +import pulumi +import json + +config = pulumi.Config() + +# Get configuration values +region = config.require("region") +resource_group = config.get("resource-group") or "default" +tags = config.get_object("tags") or ["environment:dev"] + +# Type-safe configuration with classes +class AppConfig: + def __init__(self): + self.region = config.require("region") + self.resource_group = config.get("resource-group") or "default" + self.environment = pulumi.get_stack() + +app_config = AppConfig() +``` + +## Commands + +```bash +# Initialize a new Pulumi project with Python +pulumi new python + +# Create a new stack +pulumi stack init dev + +# Install dependencies +pip install -r requirements.txt + +# Preview infrastructure changes +pulumi preview + +# Deploy infrastructure +pulumi up + +# Destroy infrastructure +pulumi destroy + +# View stack outputs +pulumi stack output + +# View stack configuration +pulumi config + +# Refresh state +pulumi refresh +``` + +## Usage + +**Using Terraform IBM Modules (TIM) with Python** + +While Pulumi doesn't directly use Terraform modules, you can achieve similar patterns using Pulumi components and the IBM Cloud provider. + +Environment Setup + +```bash +# Set IBM Cloud API key +export IBMCLOUD_API_KEY=your_actual_api_key + +# Or configure via Pulumi config +pulumi config set ibm:apiKey --secret +``` + +Setting up IBM Cloud Provider in Python + +```py +import pulumi +import pulumi_ibm as ibm + +# Configure IBM Cloud provider +provider = ibm.Provider( + "ibm-provider", + region="us-south" + # Credentials are automatically picked from: + # 1. IBMCLOUD_API_KEY environment variable + # 2. IBM Cloud CLI configuration + # 3. Pulumi configuration +) +``` + +## Python Examples + +To grasp the core concepts and workflow of Pulumi, here are a few foundational examples to help you get started. + +
+ Example 1: Simple Cloud Object Storage Instance +
+ +```py +import pulumi +import pulumi_ibm as ibm + +config = pulumi.Config() +resource_group = config.get("resource-group") or "Default" + +# Create a Cloud Object Storage instance +cos_instance = ibm.ResourceInstance( + "my-cos-instance", + name="my-dev-cos-instance", + service="cloud-object-storage", + plan="lite", + location="global", + resource_group_id=resource_group, + tags=["environment:dev", "managed-by:pulumi"] +) + +# Create a COS bucket +cos_bucket = ibm.CosBucket( + "my-bucket", + bucket_name="my-unique-bucket-name", + resource_instance_id=cos_instance.id, + region_location="us-south", + storage_class="standard", + endpoint_type="public" +) + +# Output the details +pulumi.export("bucket_name", cos_bucket.bucket_name) +pulumi.export("cos_instance_id", cos_instance.id) + +``` + +
+ +
+ +
+ Example 2: VPC with Subnets and Security Groups +
+ +```py +import pulumi +import pulumi_ibm as ibm + +config = pulumi.Config() + +# Create a VPC +vpc = ibm.IsVpc( + "my-vpc", + name="my-development-vpc", + resource_group=config.get("resource-group"), + tags=["environment:dev"] +) + +# Create an address prefix +vpc_address_prefix = ibm.IsVpcAddressPrefix( + "my-address-prefix", + cidr="10.0.1.0/24", + vpc=vpc.is_vpc_id, + zone="us-south-1" +) + +# Create a subnet +vpc_subnet = ibm.IsSubnet( + "my-subnet", + ipv4_cidr_block="10.0.1.0/24", + vpc=vpc.is_vpc_id, + zone="us-south-1", + opts = pulumi.ResourceOptions( + depends_on=[vpc_address_prefix] + ) +) + +# Create a security group +security_group = ibm.IsSecurityGroup( + "my-security-group", + vpc=vpc.is_vpc_id, +) + +# Create Security Group Rules + +sg_rule_1 = ibm.IsSecurityGroupRule("my-sg-rule1", + group=security_group.is_security_group_id, + direction="inbound", + remote="127.0.0.1", + icmp={ + "code": 20, + "type": 30, + }) + +sg_rule_2 = ibm.IsSecurityGroupRule("my-sg-rule2", + group=security_group.is_security_group_id, + direction="inbound", + remote="127.0.0.1", + udp={ + "port_min": 805, + "port_max": 807, + }) + +sg_rule_3 = ibm.IsSecurityGroupRule("my-sg-rule3", + group=security_group.is_security_group_id, + direction="outbound", + remote="127.0.0.1", + tcp={ + "port_min": 8080, + "port_max": 8080, + }) + +# Output VPC details +pulumi.export("vpc_id", vpc.id) +pulumi.export("vpc_name", vpc.name) +pulumi.export("address_prefix", vpc_address_prefix.id) +pulumi.export("subnet_id", vpc_subnet.id) +pulumi.export("security_group_id", security_group.id) + +``` + +
+ +
+ +
+ Example 3: Red Hat OpenShift Cluster with a default worker pool with one worker node. +
+ +```py +import pulumi +import pulumi_ibm as ibm + +config = pulumi.Config() + +# Create a VPC + +vpc = ibm.IsVpc( + "my-vpc", + name="gen2-vpc", + resource_group=config.get("resource-group"), + tags=["environment:dev"] +) + +# Create an address prefix +vpc_address_prefix = ibm.IsVpcAddressPrefix( + "my-address-prefix", + cidr="10.0.1.0/24", + vpc=vpc.is_vpc_id, + zone="us-south-1" +) + +# Create a subnet +vpc_subnet = ibm.IsSubnet( + "my-subnet", + ipv4_cidr_block="10.0.1.0/24", + vpc=vpc.is_vpc_id, + zone="us-south-1", + opts = pulumi.ResourceOptions( + depends_on=[vpc_address_prefix] + ) +) + +# Create a COS instance +cos_instance = ibm.ResourceInstance("cosInstance", + service="cloud-object-storage", + plan="standard", + location="global" +) + +# Create an OCP Cluster +cluster = ibm.ContainerVpcCluster("my-ocp-cluster", + vpc_id=vpc.is_vpc_id, + kube_version="4.18.24_openshift", + flavor="bx2.16x64", + worker_count=2, + entitlement="cloud_pak", + cos_instance_crn=cos_instance.resource_instance_id, + resource_group_id=config.get("resource-group"), + zones=[{ + "subnet_id": vpc_subnet.id, + "name": "us-south-1", + }] +) + +# Outputs +pulumi.export("vpc_id", vpc.id) +pulumi.export("vpc_name", vpc.name) +pulumi.export("subnet_id", vpc_subnet.id) +pulumi.export("ocp_cluster_id", cluster.id) + +``` + +
+ +
+ +### Terraform IBM Module examples + +
+ Example 4: Use existing Terraform IBM Modules (TIM) to create Watson Discovery instance +
+ +In order to use a Terraform module in Pulumi, first add it to your project using the `pulumi package add` command. Refer [here](https://www.pulumi.com/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module/) for more information. + +```sh + pulumi package add terraform-module [] +``` + +This will generate a local SDK which can be imported in the Pulumi program. + +So, the Watson discovery package can be added as: + +```sh +pulumi package add terraform-module terraform-ibm-modules/watsonx-discovery/ibm 1.11.1 wx_discovery +``` + +```py +import pulumi +import pulumi_ibm as ibm +import pulumi_wx_discovery as wxd_mod + + +config = pulumi.Config() + +wxd = wxd_mod.Module( + "my-wxd-resource", + resource_group_id=config.get("resource-group"), + watson_discovery_name = "my-wxd", +) + +# Show outputs +pulumi.export("account_id", wxd.account_id) +pulumi.export("wxd_id", wxd.id) +pulumi.export("crn", wxd.crn) +pulumi.export("plan_id", wxd.plan_id) +pulumi.export("dashboard_url", wxd.dashboard_url) + +``` + +
+ +
+ Example 5: Create Object Storage instance and buckets + +
+ +As shown in Example 4, a local SDK will be imported in the Pulumi program. + +So, the cloud object storage module can be used as: + +```sh +pulumi package add terraform-module terraform-ibm-modules/cos/ibm 10.1.14 ibm_cos_module +pulumi package add terraform-module terraform-ibm-modules/resource-group/ibm 1.2.1 ibm_rg_module +``` + +```py + +import pulumi +import pulumi_ibm_rg_module as rgmod +import pulumi_ibm_cos_module as cosmod +import pulumi_ibm as ibm +import os +import random +import string +import glob +from pulumi_ibm import IamAccessGroup, IamAccessGroupPolicy +from pulumi_ibm import CosBucketWebsiteConfiguration + +REGION = "us-south" +PREFIX = "pulumi2" +API_KEY = os.getenv("IBMCLOUD_API_KEY") + +# Set this to an existing resource group name to use it, or None to create a new one +EXISTING_RESOURCE_GROUP = "Default" # or e.g. "Default" + +# Resource Group +if EXISTING_RESOURCE_GROUP: + rg = rgmod.Module( + "resource_group", + existing_resource_group_name=EXISTING_RESOURCE_GROUP + ) + resource_group_id = None # Will be looked up by name in COS module +else: + rg = rgmod.Module( + "resource_group", + resource_group_name=f"{PREFIX}-resource-group" + ) + resource_group_id = rg.resource_group_id + +# Random 4-character suffix for bucket name +BUCKET_SUFFIX = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4)) +BUCKET_NAME = f"{PREFIX}-web-bucket" +COS_INSTANCE_NAME = f"{PREFIX}-cos" + +# COS Instance and Bucket +cos = cosmod.Module( + "cos_instance_bucket", + resource_group_id=rg.resource_group_id, + region=REGION, + cos_instance_name=COS_INSTANCE_NAME, + bucket_name=BUCKET_NAME, + create_cos_instance=True, + create_cos_bucket=True, + bucket_storage_class="standard", + kms_encryption_enabled=False, + retention_enabled=False, + object_versioning_enabled=False, + archive_days=None, + expire_days=None, + add_bucket_name_suffix=False # We already add our own +) + +# Upload files from static directory +STATIC_DIR = os.path.join(os.path.dirname(__file__), "static") +if os.path.isdir(STATIC_DIR): + files = [os.path.basename(f) for f in glob.glob(os.path.join(STATIC_DIR, "*")) if os.path.isfile(f)] + for fname in files: + ibm.cos_bucket_object.CosBucketObject( + f"file-{fname}", + bucket_crn=cos.bucket_crn.apply(lambda crns: crns[0] if isinstance(crns, list) else crns), + bucket_location=REGION, + content_file=os.path.join(STATIC_DIR, fname), + key=fname + ) +else: + pulumi.log.warn(f"Static directory not found: {STATIC_DIR}") + +# Lookup the 'Public Access' IAM access group +public_access_group = ibm.get_iam_access_group( + access_group_name="Public Access" +) + +# Grant public read access to the bucket +ibm.IamAccessGroupPolicy( + "cos-public-access-policy", + access_group_id=public_access_group.groups[0].id, + roles=["Object Reader"], + resources={ + "service": "cloud-object-storage", + "resource_type": "bucket", + "resource_instance_id": cos.cos_instance_guid.apply(lambda x: x[0] if isinstance(x, list) else x), + "resource": cos.bucket_name.apply(lambda x: x[0] if isinstance(x, list) else x), + } +) + +# Configure the COS bucket for static web hosting +ibm.CosBucketWebsiteConfiguration( + "website-config", + bucket_crn=cos.bucket_crn.apply(lambda x: x[0] if isinstance(x, list) else x), + bucket_location=REGION, + website_configuration={ + "index_document": {"suffix": "index.html"}, + "error_document": {"key": "error.html"}, + } +) + +# Export outputs +pulumi.export("bucket_name", cos.bucket_name) +pulumi.export("cos_instance_name", cos.cos_instance_name) +pulumi.export("website_endpoint", cos.bucket_crn.apply(lambda crn: f"https://{BUCKET_NAME}.s3.{REGION}.cloud-object-storage.appdomain.cloud")) + +``` + +
+ Sharing the `Pulumi.yaml` configuration for this example +
+ +```yaml +name: pulumi-start-ibmcloud +description: A minimal IBM Cloud Python Pulumi program +runtime: + name: python + options: + toolchain: pip + virtualenv: venv +config: + pulumi:tags: + value: + pulumi:template: python +packages: + ibm_cos_module: + source: terraform-module + version: 0.1.8 + parameters: + - terraform-ibm-modules/cos/ibm + - 10.1.14 + - ibm_cos_module + ibm_rg_module: + source: terraform-module + version: 0.1.8 + parameters: + - terraform-ibm-modules/resource-group/ibm + - 1.2.1 + - ibm_rg_module + ibm: + source: terraform-provider + version: 0.12.0 + parameters: + - ibm-cloud/ibm + ibm_cos_replication_module: + source: terraform-module + version: 0.1.8 + parameters: + - terraform-ibm-modules/cos/ibm//examples/replication + - 10.1.14 + - ibm_cos_replication_module +``` + +
+ +
+ +## Best Practices + +**(a) Configuration Management with Dataclasses** + +Instead of scattering configuration access throughout your code, create dedicated configuration classes that centralize all your configuration needs. This provides several benefits: + +- Type Safety: Catch configuration errors at development time rather than deployment time. +- Documentation: The configuration structure serves as documentation for what settings are available +- Validation: You can add validation logic to ensure configuration values are correct +- Reusability: The same configuration pattern can be used across multiple projects + +This dataclass example shows how to create a clean, typed configuration interface that validates required settings and provides sensible defaults for optional ones. + +```py +from dataclasses import dataclass +from typing import List, Optional +import pulumi + +@dataclass +class AppConfig: + region: str + resource_group: str + environment: str + tags: List[str] + + @classmethod + def from_pulumi_config(cls): + config = pulumi.Config() + return cls( + region=config.require("region"), + resource_group=config.get("resource-group") or "default", + environment=pulumi.get_stack(), + tags=config.get_object("tags") or ["managed-by:pulumi"] + ) + +app_config = AppConfig.from_pulumi_config() +``` + +**(b) Error Handling and Validation** + +Resource naming constraints vary across cloud providers, and IBM Cloud has specific requirements for resource names. Instead of discovering naming issues during deployment, implement proactive validation: + + - Consistency: Ensure all resources follow a consistent naming pattern across your organization + - Compliance: Enforce naming conventions that include environment, project, and team information + - Uniqueness: Prevent naming collisions by incorporating stack names and timestamps + - Readability: Make resource names meaningful for operations teams + +The validation function below demonstrates how to programmatically enforce naming standards, ensuring your infrastructure remains organized and maintainable. + + +```py +import re +import pulumi + +def validate_resource_name(name: str) -> str: + """Validate and format resource names.""" + stack = pulumi.get_stack() + formatted_name = f"{name}-{stack}".lower() + # Remove invalid characters + cleaned_name = re.sub(r'[^a-z0-9-]', '-', formatted_name) + # Ensure it starts with a letter + if not cleaned_name[0].isalpha(): + cleaned_name = f"res-{cleaned_name}" + return cleaned_name + +# Usage +cos_instance = ibm.resource.Instance( + "cos", + name=validate_resource_name("my-app-cos"), + # ... other properties +) +``` + +**(c) Tagging Strategy** + +Tags are crucial for cost management, operations, and security in cloud environments. A well-designed tagging strategy helps: + +- Cost Allocation: Track spending by department, project, or environment. +- Operations: Filter and search resources during incident response. +- Automation: Enable automated policies based on tag values. +- Compliance: Demonstrate resource ownership and purpose. + +The tagging utility functions below shows how to implement a consistent tagging approach that combines base tags (applied to all resources) with resource-specific tags, making your infrastructure self-documenting. + +```py +from typing import Dict +import pulumi + +def get_base_tags() -> Dict[str, str]: + """Get base tags for all resources.""" + return { + "Environment": pulumi.get_stack(), + "ManagedBy": "pulumi", + "Project": "my-project", + "Created": "2024-01-01" # In real code, use datetime + } + +def apply_tags(tags: Dict[str, str]) -> pulumi.Input[list]: + """Convert tag dictionary to IBM Cloud tag format.""" + base_tags = get_base_tags() + all_tags = {**base_tags, **tags} + return [f"{k}:{v}" for k, v in all_tags.items()] + +# Usage +cos_instance = ibm.resource.Instance( + "cos", + name="my-cos", + service="cloud-object-storage", + plan="standard", + tags=apply_tags({"Purpose": "application-data"}) +) +``` + +**(d) Resource Dependencies and Ordering** + +While Pulumi can often infer dependencies automatically, explicit dependency declaration is crucial for: + +- Predictable Deployments: Ensure resources are created in the correct order +- Error Prevention: Avoid race conditions where resources reference others that don't exist yet +- Clear Intent: Make the resource relationships obvious to other developers +- Performance: Optimize deployment parallelism where safe + +The dependency example below demonstrates how to explicitly declare relationships between resources, particularly important when dealing with networking components that must exist before dependent resources. + +```py +import pulumi +import pulumi_ibm as ibm + +# Explicit dependency management +vpc = ibm.is.Vpc("main-vpc", name="main-vpc") + +# Subnet depends on VPC +subnet = ibm.is.Subnet( + "main-subnet", + name="main-subnet", + vpc=vpc.id, + zone="us-south-1", + ipv4_cidr_block="10.240.0.0/24", + # Explicit dependency + opts=pulumi.ResourceOptions(depends_on=[vpc]) +) + +# Security group depends on VPC +security_group = ibm.is.SecurityGroup( + "main-sg", + name="main-security-group", + vpc=vpc.id, + opts=pulumi.ResourceOptions(depends_on=[vpc]) +) +``` + +**(e) Output Processing and Transformation** + +Pulumi's Output system handles the asynchronous nature of cloud resource creation. Proper output management enables: + +- Data Transformation: Process resource attributes into useful formats +- Cross-Resource References: Safely use outputs from one resource as inputs to another +- Runtime Configuration: Generate configuration files, connection strings, or URLs based on created resources +- Monitoring: Export meaningful information for operations teams + +The output processing examples show how to work with Pulumi's functional programming model to transform and combine resource outputs, creating valuable derived information from your infrastructure. + +```py +import pulumi +import pulumi_ibm as ibm + +cos_instance = ibm.resource.Instance("cos", ...) + +# Process outputs +cos_endpoint = cos_instance.id.apply( + lambda id: f"https://{id}.s3.us-south.cloud-object-storage.appdomain.cloud" +) + +# Combine multiple outputs +cluster_details = pulumi.Output.all( + cluster.id, + cluster.name, + cluster.kube_version +).apply(lambda args: { + "id": args[0], + "name": args[1], + "version": args[2] +}) + +pulumi.export("cos_endpoint", cos_endpoint) +pulumi.export("cluster_details", cluster_details) +``` + +**(f) Security Practices** + +When working with IBM Cloud IAM, follow security best practices: + +- **Secrets**: Store sensitive data using `pulumi config set --secret ` +- **State Management**: Use Pulumi Cloud or IBM COS bucket for secure state storage. +- Commit only code, not `.pulumi/` state directories. +- Follow Principle of Least Privilege. + +**(g) Cleanup: Implement automated cleanup of unused resources.** + +**(h) Wrap patterns in reusable functions or classes.** + +## Troubleshooting Common Issues + +a) Authentication Problems + +```bash +# Verify IBM Cloud login +ibmcloud account show + +# Check API key +echo ${IBMCLOUD_API_KEY} + +# Test with Pulumi preview +pulumi preview +``` + +b) Python Dependency Issues + +```bash +# Ensure you're in the virtual environment +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Update Pulumi +pulumi update +``` + +c) Resource Naming Conflicts + +```py +# Use unique names with stack suffix +def get_resource_name(base_name: str) -> str: + stack = pulumi.get_stack() + return f"{base_name}-{stack}" +```` + +## References + +- [Pulumi IBM Cloud Provider](https://www.pulumi.com/registry/packages/ibm/) +- [IBM Cloud Terraform Modules](https://github.com/terraform-ibm-modules) +- [Pulumi Terraform Conversion Guide](https://www.pulumi.com/docs/using-pulumi/adopting-pulumi/migrating/terraform/)