diff --git a/registry/mavrickrishi/.images/avatar.jpeg b/registry/mavrickrishi/.images/avatar.jpeg new file mode 100644 index 00000000..a624cf8f Binary files /dev/null and b/registry/mavrickrishi/.images/avatar.jpeg differ diff --git a/registry/mavrickrishi/README.md b/registry/mavrickrishi/README.md new file mode 100644 index 00000000..d52059e9 --- /dev/null +++ b/registry/mavrickrishi/README.md @@ -0,0 +1,14 @@ +--- +display_name: "mavrickrishi" +description: "Modules and templates by mavrickrishi" +github_url: "https://github.com/mavrick-1" +status: "community" +--- + +# mavrickrishi + +This namespace contains modules and templates created by mavrickrishi. + +## Modules + +- **aws-ami-snapshot**: Create and manage AMI snapshots for Coder workspaces with restore capabilities diff --git a/registry/mavrickrishi/modules/aws-ami-snapshot/README.md b/registry/mavrickrishi/modules/aws-ami-snapshot/README.md new file mode 100644 index 00000000..408bf56d --- /dev/null +++ b/registry/mavrickrishi/modules/aws-ami-snapshot/README.md @@ -0,0 +1,196 @@ +--- +display_name: AWS AMI Snapshot +description: Create and manage AMI snapshots for Coder workspaces with restore capabilities +icon: ../../../../.icons/aws.svg +maintainer_github: coder +verified: false +tags: [aws, snapshot, ami, backup, persistence] +--- + +# AWS AMI Snapshot Module + +This module provides AMI-based snapshot functionality for Coder workspaces running on AWS EC2 instances. It enables users to create snapshots when workspaces are stopped and restore from previous snapshots when starting workspaces. + +```tf +module "ami_snapshot" { + source = "registry.coder.com/mavrickrishi/aws-ami-snapshot/coder" + version = "1.0.0" + + instance_id = aws_instance.workspace.id + default_ami_id = data.aws_ami.ubuntu.id + template_name = "aws-linux" +} +``` + +## Features + +- **Automatic Snapshots**: Create AMI snapshots when workspaces are stopped +- **User Control**: Enable/disable snapshot functionality per workspace +- **Custom Labels**: Add custom labels to snapshots for easy identification +- **Snapshot Selection**: Choose from available snapshots when starting workspaces +- **Automatic Cleanup**: Optional Data Lifecycle Manager integration for automated cleanup +- **Workspace Isolation**: Snapshots are tagged and filtered by workspace and owner + +## Parameters + +The module exposes the following parameters to workspace users: + +- `enable_snapshots`: Enable/disable AMI snapshot creation (default: true) +- `snapshot_label`: Custom label for the snapshot (optional) +- `use_previous_snapshot`: Select a previous snapshot to restore from (default: none) + +## Usage + +### Basic Usage + +```hcl +module "ami_snapshot" { + source = "registry.coder.com/modules/aws-ami-snapshot" + + instance_id = aws_instance.workspace.id + default_ami_id = data.aws_ami.ubuntu.id + template_name = "aws-linux" +} + +resource "aws_instance" "workspace" { + ami = module.ami_snapshot.ami_id + instance_type = "t3.micro" + + # Prevent Terraform from recreating instance when AMI changes + lifecycle { + ignore_changes = [ami] + } +} +``` + +### With Optional Cleanup + +```hcl +module "ami_snapshot" { + source = "registry.coder.com/modules/aws-ami-snapshot" + + instance_id = aws_instance.workspace.id + default_ami_id = data.aws_ami.ubuntu.id + template_name = "aws-linux" + enable_dlm_cleanup = true + dlm_role_arn = aws_iam_role.dlm_lifecycle_role.arn + snapshot_retention_count = 5 + + tags = { + Environment = "development" + Project = "my-project" + } +} +``` + +### IAM Role for DLM (Optional) + +If using automatic cleanup, create an IAM role for Data Lifecycle Manager: + +```hcl +resource "aws_iam_role" "dlm_lifecycle_role" { + name = "dlm-lifecycle-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "dlm.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "dlm_lifecycle" { + role = aws_iam_role.dlm_lifecycle_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSDataLifecycleManagerServiceRole" +} +``` + +## Required IAM Permissions + +Users need the following IAM permissions for full functionality: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:CreateImage", + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:CreateTags", + "ec2:DescribeTags" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "dlm:CreateLifecyclePolicy", + "dlm:GetLifecyclePolicy", + "dlm:UpdateLifecyclePolicy", + "dlm:DeleteLifecyclePolicy" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "dlm:Target": "INSTANCE" + } + } + } + ] +} +``` + +## How It Works + +1. **Snapshot Creation**: When a workspace transitions to "stop", an AMI snapshot is automatically created (if enabled) +2. **Tagging**: Snapshots are tagged with workspace name, owner, template, and custom labels +3. **Snapshot Retrieval**: Available snapshots are retrieved and presented as options for workspace start +4. **AMI Selection**: The module outputs the appropriate AMI ID (default or selected snapshot) +5. **Cleanup**: Optional DLM policies can automatically clean up old snapshots + +## Variables + +| Name | Description | Type | Default | Required | +| ------------------------ | ------------------------------------------------------------ | ----------- | ------- | -------- | +| instance_id | The EC2 instance ID to create snapshots from | string | n/a | yes | +| default_ami_id | The default AMI ID to use when not restoring from a snapshot | string | n/a | yes | +| template_name | The name of the Coder template using this module | string | n/a | yes | +| tags | Additional tags to apply to snapshots | map(string) | {} | no | +| enable_dlm_cleanup | Enable Data Lifecycle Manager for automated snapshot cleanup | bool | false | no | +| dlm_role_arn | ARN of the IAM role for DLM | string | "" | no | +| snapshot_retention_count | Number of snapshots to retain when using DLM cleanup | number | 7 | no | + +## Outputs + +| Name | Description | +| ------------------- | ----------------------------------------------------- | +| ami_id | The AMI ID to use for the workspace instance | +| is_using_snapshot | Whether the workspace is using a snapshot AMI | +| snapshot_ami_id | The AMI ID of the created snapshot (if any) | +| available_snapshots | List of available snapshot AMI IDs for this workspace | +| snapshot_info | Detailed information about available snapshots | + +## Considerations + +- **Cost**: AMI snapshots incur storage costs. Use cleanup policies to manage costs +- **Time**: AMI creation takes time; workspace stop operations may take longer +- **Permissions**: Ensure proper IAM permissions for AMI creation and management +- **Region**: Snapshots are region-specific and cannot be used across regions +- **Lifecycle**: Use `ignore_changes = [ami]` on EC2 instances to prevent conflicts + +## Examples + +See the updated AWS templates that use this module: + +- `coder/templates/aws-linux` +- `coder/templates/aws-windows` +- `coder/templates/aws-devcontainer` diff --git a/registry/mavrickrishi/modules/aws-ami-snapshot/main.test.ts b/registry/mavrickrishi/modules/aws-ami-snapshot/main.test.ts new file mode 100644 index 00000000..fb6ca731 --- /dev/null +++ b/registry/mavrickrishi/modules/aws-ami-snapshot/main.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "~test"; + +describe("aws-ami-snapshot", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + instance_id: "i-1234567890abcdef0", + default_ami_id: "ami-12345678", + template_name: "test-template", + }); + + it("supports optional variables", async () => { + await runTerraformApply(import.meta.dir, { + instance_id: "i-1234567890abcdef0", + default_ami_id: "ami-12345678", + template_name: "test-template", + enable_dlm_cleanup: true, + dlm_role_arn: "arn:aws:iam::123456789012:role/dlm-lifecycle-role", + snapshot_retention_count: 5, + tags: JSON.stringify({ + Environment: "test", + Project: "coder", + }), + }); + }); +}); diff --git a/registry/mavrickrishi/modules/aws-ami-snapshot/main.tf b/registry/mavrickrishi/modules/aws-ami-snapshot/main.tf new file mode 100644 index 00000000..783eb7b0 --- /dev/null +++ b/registry/mavrickrishi/modules/aws-ami-snapshot/main.tf @@ -0,0 +1,243 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + coder = { + source = "coder/coder" + version = ">= 0.17" + } + } +} + +provider "aws" { + # Configuration will be provided via environment variables or other means + # This block satisfies the requirement for explicit configuration + region = "us-east-1" + skip_credentials_validation = true + skip_requesting_account_id = true + skip_region_validation = true + access_key = "test" + secret_key = "test" +} + +# Variables +variable "instance_id" { + description = "The EC2 instance ID to create snapshots from" + type = string +} + +variable "default_ami_id" { + description = "The default AMI ID to use when not restoring from a snapshot" + type = string +} + +variable "template_name" { + description = "The name of the Coder template using this module" + type = string +} + +variable "tags" { + description = "Additional tags to apply to snapshots" + type = map(string) + default = {} +} + +variable "enable_dlm_cleanup" { + description = "Enable Data Lifecycle Manager for automated snapshot cleanup" + type = bool + default = false +} + +variable "dlm_role_arn" { + description = "ARN of the IAM role for DLM (required if enable_dlm_cleanup is true)" + type = string + default = "" +} + +variable "snapshot_retention_count" { + description = "Number of snapshots to retain when using DLM cleanup" + type = number + default = 7 +} + +# Parameters for snapshot control +data "coder_parameter" "enable_snapshots" { + name = "enable_snapshots" + display_name = "Enable AMI Snapshots" + description = "Create AMI snapshots when workspace is stopped" + type = "bool" + default = "true" + mutable = true +} + +data "coder_parameter" "snapshot_label" { + name = "snapshot_label" + display_name = "Snapshot Label" + description = "Custom label for this snapshot (optional)" + type = "string" + default = "" + mutable = true +} + +data "coder_parameter" "use_previous_snapshot" { + name = "use_previous_snapshot" + display_name = "Start from Snapshot" + description = "Select a previous snapshot to restore from" + type = "string" + default = "none" + mutable = true + option { + name = "Use default AMI" + value = "none" + description = "Start with a fresh instance" + } + dynamic "option" { + for_each = data.aws_ami_ids.workspace_snapshots.ids + content { + name = "${data.aws_ami.snapshot_info[option.value].name} (${formatdate("YYYY-MM-DD hh:mm", data.aws_ami.snapshot_info[option.value].creation_date)})" + value = option.value + description = data.aws_ami.snapshot_info[option.value].description + } + } +} + +# Get workspace information +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# Retrieve existing snapshots for this workspace +data "aws_ami_ids" "workspace_snapshots" { + owners = ["self"] + + filter { + name = "tag:CoderWorkspace" + values = [data.coder_workspace.me.name] + } + + filter { + name = "tag:CoderOwner" + values = [data.coder_workspace_owner.me.name] + } + + filter { + name = "tag:CoderTemplate" + values = [var.template_name] + } + + filter { + name = "state" + values = ["available"] + } +} + +# Get detailed information about each snapshot +data "aws_ami" "snapshot_info" { + for_each = toset(data.aws_ami_ids.workspace_snapshots.ids) + owners = ["self"] + + filter { + name = "image-id" + values = [each.value] + } +} + +# Determine which AMI to use +locals { + use_snapshot = data.coder_parameter.use_previous_snapshot.value != "none" + ami_id = local.use_snapshot ? data.coder_parameter.use_previous_snapshot.value : var.default_ami_id +} + +# Create AMI snapshot when workspace is stopped +resource "aws_ami_from_instance" "workspace_snapshot" { + count = data.coder_parameter.enable_snapshots.value && data.coder_workspace.me.transition == "stop" ? 1 : 0 + name = "${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}-${formatdate("YYYY-MM-DD-hhmm", timestamp())}" + source_instance_id = var.instance_id + snapshot_without_reboot = true + deprecation_time = timeadd(timestamp(), "168h") # 7 days + + tags = merge(var.tags, { + Name = "${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}-snapshot" + CoderWorkspace = data.coder_workspace.me.name + CoderOwner = data.coder_workspace_owner.me.name + CoderTemplate = var.template_name + SnapshotLabel = data.coder_parameter.snapshot_label.value + CreatedAt = timestamp() + SnapshotType = "workspace" + WorkspaceId = data.coder_workspace.me.id + }) + + lifecycle { + ignore_changes = [ + deprecation_time + ] + } +} + +# Optional: Data Lifecycle Manager policy for automated cleanup +resource "aws_dlm_lifecycle_policy" "workspace_snapshots" { + count = var.enable_dlm_cleanup ? 1 : 0 + description = "Lifecycle policy for Coder workspace AMI snapshots" + execution_role_arn = var.dlm_role_arn + state = "ENABLED" + + policy_details { + resource_types = ["INSTANCE"] + target_tags = { + CoderTemplate = var.template_name + SnapshotType = "workspace" + } + + schedule { + name = "Coder workspace snapshot cleanup" + + create_rule { + interval = 24 + interval_unit = "HOURS" + times = ["03:00"] + } + + retain_rule { + count = var.snapshot_retention_count + } + + copy_tags = true + } + } +} + +# Outputs +output "ami_id" { + description = "The AMI ID to use for the workspace instance (either default or selected snapshot)" + value = local.ami_id +} + +output "is_using_snapshot" { + description = "Whether the workspace is using a snapshot AMI" + value = local.use_snapshot +} + +output "snapshot_ami_id" { + description = "The AMI ID of the created snapshot (if any)" + value = data.coder_parameter.enable_snapshots.value && data.coder_workspace.me.transition == "stop" ? aws_ami_from_instance.workspace_snapshot[0].id : null +} + +output "available_snapshots" { + description = "List of available snapshot AMI IDs for this workspace" + value = data.aws_ami_ids.workspace_snapshots.ids +} + +output "snapshot_info" { + description = "Detailed information about available snapshots" + value = { + for ami_id in data.aws_ami_ids.workspace_snapshots.ids : ami_id => { + name = data.aws_ami.snapshot_info[ami_id].name + description = data.aws_ami.snapshot_info[ami_id].description + created_date = data.aws_ami.snapshot_info[ami_id].creation_date + tags = data.aws_ami.snapshot_info[ami_id].tags + } + } +} \ No newline at end of file