functions/New-DiskImageFromContainerisedRootFS.ps1

# <copyright file="New-DiskImageFromContainerisedRootFS.ps1" company="Endjin Limited">
# Copyright (c) Endjin Limited. All rights reserved.
# </copyright>
function New-DiskImageFromContainerisedRootFS
{
    <#
    .SYNOPSIS
        Creates a new Linux raw disk image as a copy of a containerized filesystem.
 
    .DESCRIPTION
        Generates a new Linux raw disk image file by copying the contents of the filesystem of the specified container.
        This can be useful for creating virtual machine disk images from container-based templates.
 
    .PARAMETER SourceImageName
        The full repository, name and tag name of the container image from which the root filesystem will be used to
        create the disk image.
 
    .PARAMETER DestinationFolderPath
        The directory where the new disk image file will be saved.
 
    .PARAMETER DiskSizeMB
        The size in Megabtyes of the disk image to be created.
 
    .EXAMPLE
        New-DiskImageFromContainerisedRootFS -SourceImageName "mycontainer:latest" `
                                             -DestinationFolderPath "/tmp" `
                                             -DiskSizeMB 250
                                              
        This command creates a 250 MB Linux raw disk image called `/tmp/mycontainer-latest.img` that includes all the
        files from the `mycontainer:latest` docker image.
 
    .NOTES
        This function requires a Linux-like operating system with Docker installed and running.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $SourceImageName,

        [Parameter(Mandatory=$true)]
        [string] $DestinationFolderPath,

        [Parameter(Mandatory=$true)]
        [int] $DiskSizeMB
    )

    if ($IsWindows) {
        throw "This function is only supported on Linux-like operating systems"
    }

    @(
        "docker"
        "fallocate"
        "mkfs.ext4"
        "tar"
        "mktemp"
        "mount"
        "umount"
        "sudo"
    ) | ForEach-Object {
        if (!(Get-Command $_ -ErrorAction Ignore)) {
            throw "The '$_' command is required to run this function"
        }
    }

    $diskImageSize = "$($DiskSizeMB)M"

    $basePath = $DestinationFolderPath
    $imageName,$imageTag = $SourceImageName -split ":"
    $baseName = "$imageName-rootfs"

    # Extract a tarball representing the entire container filesystem
    $rootFsTar = Copy-FilesFromContainerImage -SourceImageName $SourceImageName -DestinationFolderPath $DestinationFolderPath -AllFilesAsTar -PassThru

    try {
        # Create a disk image file with the contents of the container image
        $rootFsDiskImage = Join-Path $basePath "$baseName.img"
        if (Test-Path $rootFsDiskImage) {
            Remove-Item $rootFsDiskImage -Force
        }
        
        # Ensure errors from external tools are propagated to the script - this overridden
        # value will fall out of scope when the function completes
        $PSNativeCommandUseErrorActionPreference = $true
    
        # Create & format a disk image file of the required size
        & fallocate -l $diskImageSize $rootFsDiskImage | Write-Host
        & mkfs.ext4 $rootFsDiskImage | Write-Host
    
        # Mount the empty disk image using a temporary mount point as a loopback device
        $TMP = & mktemp -d
        Write-Host "TempDir: $TMP"
        & sudo mount -o loop $rootFsDiskImage $TMP | Write-Host

        # Copy the contents the tarball to the mounted disk image
        & sudo tar -xf $rootFsTar -C $TMP | Write-Host

        # Unmount the disk image
        & sudo umount $TMP | Write-Host
    }
    finally {
        # Clean-up temporary resources
        Remove-Item $rootFsTar -Force
    }

    # Returns the path to the disk image file
    return $rootFsDiskImage
}