# Packer Plugin for Gridscale The Packer Plugin for Gridscale enables automated creation of custom OS templates on the gridscale.io cloud infrastructure platform. This plugin integrates with HashiCorp Packer (v1.7.0+) to provision, configure, and snapshot virtual machines into reusable templates that can be deployed across gridscale's infrastructure. The plugin supports multiple installation sources including base templates, ISO images from URLs, or existing ISO images from gridscale's storage, providing flexibility for various template creation workflows. Built in Go using the Packer Plugin SDK, this tool orchestrates the complete lifecycle of template creation through a multi-step process: server provisioning, storage management, network configuration, SSH key deployment, boot command execution via VNC, provisioning with standard Packer provisioners, snapshot creation, and final template generation. It leverages the gridscale Go client library (gsclient-go/v3) to interact with gridscale's REST API, handling authentication, resource management, and cleanup operations automatically. ## Installation ### Installing the Plugin ```hcl packer { required_plugins { gridscale = { version = ">= 1.0.0" source = "github.com/gridscale/gridscale" } } } ``` ```bash # Initialize and install plugins packer init config.pkr.hcl # Alternative: Direct installation packer plugins install github.com/gridscale/gridscale ``` ## Builder Configuration ### Basic Template from Base Template ```hcl source "gridscale" "ubuntu_custom" { # Authentication - can also use environment variables api_key = "${env("GRIDSCALE_UUID")}" # or set via GRIDSCALE_UUID api_token = "${env("GRIDSCALE_TOKEN")}" # or set via GRIDSCALE_TOKEN api_url = "https://api.gridscale.io" # optional, defaults to gridscale API # Template source - use existing gridscale template base_template_uuid = "fd65f8ce-e2c6-40af-8fc3-92efa0d4eecb" # Ubuntu 20.04 # Server configuration server_name = "packer-ubuntu-builder" server_cores = 2 server_memory = 4 # GB hostname = "ubuntu-vm" # Storage configuration storage_capacity = 10 # GB # Template output template_name = "ubuntu-20.04-custom-${timestamp()}" # SSH configuration ssh_username = "root" ssh_password = "initialPassword123!" # used for template initialization } build { sources = ["source.gridscale.ubuntu_custom"] provisioner "shell" { inline = [ "apt-get update", "apt-get install -y nginx docker.io", "systemctl enable nginx" ] } } ``` ### Template from ISO URL with Boot Commands ```hcl source "gridscale" "alpine_iso" { api_key = "${env("GRIDSCALE_UUID")}" api_token = "${env("GRIDSCALE_TOKEN")}" # ISO source - download from URL isoimage_url = "https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-virt-3.18.0-x86_64.iso" # Server resources server_cores = 2 server_memory = 2 storage_capacity = 5 hostname = "alpine-vm" # Boot automation boot_wait = "10s" boot_command = [ "root", "ifconfig eth0 up && udhcpc -i eth0", "wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/setup-alpine.txt", "setup-alpine -f setup-alpine.txt", ] # Serve local files over HTTP during boot files = [ "setup-alpine.txt", "answers.txt" ] boot_key_interval = "10ms" template_name = "alpine-3.18-minimal" ssh_username = "root" ssh_password = "alpine123" } build { sources = ["source.gridscale.alpine_iso"] provisioner "file" { source = "scripts/configure.sh" destination = "/tmp/configure.sh" } provisioner "shell" { inline = ["chmod +x /tmp/configure.sh && /tmp/configure.sh"] } } ``` ### Template with Secondary Storage ```hcl source "gridscale" "data_template" { api_key = "${env("GRIDSCALE_UUID")}" api_token = "${env("GRIDSCALE_TOKEN")}" base_template_uuid = "fd65f8ce-e2c6-40af-8fc3-92efa0d4eecb" server_cores = 4 server_memory = 8 storage_capacity = 20 # Enable secondary storage - template will be created from this storage secondary_storage = true template_name = "data-processing-template" hostname = "data-server" ssh_username = "root" ssh_password = "securePass456" } build { sources = ["source.gridscale.data_template"] # All provisioning happens on the secondary storage provisioner "shell" { inline = [ "mkfs.ext4 /dev/sdb", "mkdir -p /data", "mount /dev/sdb /data", "echo '/dev/sdb /data ext4 defaults 0 2' >> /etc/fstab" ] } } ``` ### Using Existing ISO Image UUID ```hcl source "gridscale" "custom_iso" { api_key = "${env("GRIDSCALE_UUID")}" api_token = "${env("GRIDSCALE_TOKEN")}" # Use pre-uploaded ISO image from gridscale isoimage_uuid = "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d" server_cores = 2 server_memory = 4 storage_capacity = 15 boot_wait = "5s" boot_command = [ "", "installer", ] template_name = "custom-os-template" ssh_username = "admin" ssh_password = "installer" } build { sources = ["source.gridscale.custom_iso"] } ``` ### Template with User Data (Cloud-Init) ```hcl source "gridscale" "cloud_init" { api_key = "${env("GRIDSCALE_UUID")}" api_token = "${env("GRIDSCALE_TOKEN")}" base_template_uuid = "fd65f8ce-e2c6-40af-8fc3-92efa0d4eecb" server_cores = 2 server_memory = 4 storage_capacity = 10 hostname = "cloud-init-vm" # Cloud-init user data user_data = <<-EOF #cloud-config packages: - docker.io - git - vim runcmd: - systemctl start docker - systemctl enable docker EOF template_name = "ubuntu-docker-ready" ssh_username = "root" ssh_password = "tempPass789" } build { sources = ["source.gridscale.cloud_init"] } ``` ## Configuration Reference ### Authentication Parameters ```hcl source "gridscale" "example" { # Required: API credentials api_key = "your-uuid" # env: GRIDSCALE_UUID api_token = "your-token" # env: GRIDSCALE_TOKEN api_url = "https://api.gridscale.io" # env: GRIDSCALE_URL (optional) # Debug headers (optional) api_request_headers = "X-Debug:true,X-Request-ID:12345" # env: GRIDSCALE_PACKER_HEADERS } ``` ### Server Configuration Parameters ```hcl source "gridscale" "example" { # Required server_cores = 2 # CPU cores server_memory = 4 # RAM in GB storage_capacity = 10 # Boot storage in GB # Optional server_name = "packer-builder-${timestamp()}" # auto-generated if not set hostname = "vm-hostname" # defaults to "packer-hostname" user_data = "cloud-init configuration" # for cloud-init templates secondary_storage = false # create secondary storage, template from it } ``` ### Source Parameters (one required) ```hcl source "gridscale" "example" { # Option 1: Base template UUID (existing gridscale template) base_template_uuid = "fd65f8ce-e2c6-40af-8fc3-92efa0d4eecb" # Option 2: ISO image URL (downloads from external URL) isoimage_url = "https://example.com/os.iso" # Option 3: ISO image UUID (existing gridscale ISO) isoimage_uuid = "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d" } ``` ### Boot Configuration Parameters ```hcl source "gridscale" "example" { boot_wait = "10s" # wait before typing boot_command (default: 10s) boot_key_interval = "10ms" # delay between keystrokes (default: 0) boot_command = [ "line1", "", # wait 5 seconds "line2", ] # Serve files via HTTP during boot (accessible via {{.HTTPIP}}:{{.HTTPPort}}) files = [ "preseed.cfg", "scripts/setup.sh" ] } ``` ### Output Configuration ```hcl source "gridscale" "example" { template_name = "my-template-${timestamp()}" # defaults to "packer-" } ``` ## Running Packer ### Basic Build Commands ```bash # Set authentication environment variables export GRIDSCALE_UUID="your-uuid" export GRIDSCALE_TOKEN="your-token" # Validate configuration packer validate config.pkr.hcl # Build template packer build config.pkr.hcl # Build with variables packer build -var 'template_name=custom-name' config.pkr.hcl # Build with debug mode PACKER_LOG=1 packer build config.pkr.hcl ``` ### Acceptance Testing ```bash # Set required environment variables export GRIDSCALE_UUID="your-uuid" export GRIDSCALE_TOKEN="your-token" # Run acceptance tests make acctest # Run specific test go test -v ./builder/gridscale -run TestBuilderAcc_basic ``` ## Builder Artifact Output ### Artifact Structure ```go // The builder returns an Artifact with template information type Artifact struct { TemplateName string // Name of created template TemplateUUID string // UUID of created template LocationName string // gridscale location name LocationUUID string // gridscale location UUID } // Example artifact output // "A template was created: 'ubuntu-20.04-custom-1234567890' (ID: a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d)" ``` ### Using Artifact in Post-Processors ```hcl build { sources = ["source.gridscale.example"] post-processor "manifest" { output = "manifest.json" strip_path = true } } ``` ```json { "builds": [{ "artifact_id": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d", "builder_type": "gridscale", "custom_data": { "template_name": "ubuntu-20.04-custom", "location_name": "de/fra", "location_uuid": "45ed677b-3702-4b36-be2a-a2eab9827950" } }] } ``` ## Go API Usage ### Creating a Builder Instance ```go package main import ( "context" "github.com/gridscale/packer-plugin-gridscale/builder/gridscale" "github.com/hashicorp/packer-plugin-sdk/packer" ) func main() { // Create builder configuration config := map[string]interface{}{ "api_key": "your-uuid", "api_token": "your-token", "base_template_uuid": "fd65f8ce-e2c6-40af-8fc3-92efa0d4eecb", "server_cores": 2, "server_memory": 4, "storage_capacity": 10, "template_name": "custom-template", "ssh_username": "root", "ssh_password": "password", } // Initialize builder builder := new(gridscale.Builder) warnings, generatedVars, err := builder.Prepare(config) if err != nil { panic(err) } // Run build ui := &packer.BasicUi{ Reader: os.Stdin, Writer: os.Stdout, } artifact, err := builder.Run(context.Background(), ui, nil) if err != nil { panic(err) } // Access artifact information fmt.Printf("Template ID: %s\n", artifact.Id()) fmt.Printf("Details: %s\n", artifact.String()) } ``` ### Configuration Validation ```go package main import ( "github.com/gridscale/packer-plugin-gridscale/builder/gridscale" ) func validateConfig() error { // Configuration with required fields rawConfig := map[string]interface{}{ "api_key": "your-uuid", "api_token": "your-token", "isoimage_url": "https://example.com/os.iso", "server_cores": 2, "server_memory": 4, "storage_capacity": 10, } // Parse and validate configuration config, warnings, err := gridscale.NewConfig(rawConfig) if err != nil { return fmt.Errorf("configuration error: %w", err) } // Check warnings for _, warning := range warnings { fmt.Printf("Warning: %s\n", warning) } // Configuration is valid fmt.Printf("Template will be named: %s\n", config.TemplateName) fmt.Printf("Server will be named: %s\n", config.ServerName) return nil } ``` ## Build Process Workflow The Packer gridscale builder executes a comprehensive multi-step workflow to create OS templates from various sources (base templates, ISO images, or ISO URLs). The process begins by establishing network connectivity through gridscale's public network infrastructure, then provisions a virtual server with specified resources (CPU, memory, storage). For base template installations, the builder configures boot storage with SSH credentials and optional hostname settings, while ISO-based installations prepare secondary storage and mount ISO images via VNC connections. After the server infrastructure is ready, the builder handles the installation phase using boot commands executed through VNC console access for ISO installations, or directly provisions base template installations via SSH. Once the OS is installed and configured using standard Packer provisioners (shell scripts, file uploads, configuration management tools), the builder cleanly shuts down the server, creates a storage snapshot, and generates a reusable template from that snapshot. Throughout the entire process, the builder implements automatic cleanup handlers that remove all temporary resources (servers, storages, IP addresses, ISO images, SSH keys) if the build fails or is interrupted, ensuring no orphaned resources remain in the gridscale account.