Migration/AWS/AWS.psm1

Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath MigrationProfile | Join-Path -ChildPath AWSMigrationProfile)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath CloudAccount | Join-Path -ChildPath CloudAccount)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath MigrationProfile | Join-Path -ChildPath AWSMigrationProfile)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Preflight | Join-Path -ChildPath Preflight)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Util | Join-Path -ChildPath Util)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath RiverMeadow.Development.Source | Join-Path -ChildPath SourceUtil | Join-Path -ChildPath SourceUtil)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Common | Join-Path -ChildPath Common)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Common | Join-Path -ChildPath Wrappers | Join-Path -ChildPath Wrappers)
Import-Module -Name @(Join-Path $PSScriptRoot AWSUtil)
function Start-RMAWSOSBasedMigration {
    param(
        [Parameter(Mandatory)]
        [System.Object] $CloudAccount,
        [System.Object] $Entitlement
    )
    $Source = $null
    $SourceIP = Read-Host "Enter the IP address of the source machine to be migrated"
    if ("" -eq $SourceIP) {
        throw "Source IP address is required to start a migration."
    } else {
        $Source = Get-RMSourceByIP -IPAddress $SourceIP
    }
    $TargetVMName = Read-Host "Enter target VM Name"

    $MountPoints = Get-MountPoint -Source $Source
    if (0 -eq $MountPoints.count) {
        throw "Source has no mount points, cannot be migrated"
    }

    $MountPointsAsString = $MountPoints.values -join ", "
    Write-Output "Mount points to be migrated [$MountPointsAsString]" | Out-Host
    $ExcludedMountPoints = Get-RMExcludedMountPoint -OSType $Source.os_type -MountPoints $MountPoints.Values
    $SelectedMountPoints = $MountPoints
    if ("" -ne $ExcludedMountPoints) {
        $ExcludedList = $ExcludedMountPoints.Split(",").Trim()
        $SelectedMountPoints = Get-RMSelectedMount -MountPoints $MountPoints -DifferenceList $ExcludedList -IncludeEqual $false
    }

    $EncryptVolumes = $false
    $ReadValue = Read-Host "Enable EBS encryption on all volumes (true/false)[false]"
    if ("" -ne $ReadValue) {
        $EncryptVolumes = [System.Convert]::ToBoolean($ReadValue)
    }

    $VolumeType = "ssd2"
    $IOPS = $null
    $ReadValue = Read-Host "Enter volume type (Magnetic, GP2, GP3, IO1, IO2)[GP2]"
    if ("" -ne $ReadValue) {
        $VolumeType = Get-RMVolumeType -VolumeType $ReadValue
        if ("io1" -ieq $ReadValue) {
            $IOPS = Read-Host "Enter the IOPS value between 100 and 700 (50:1)"
        } elseif ("io2" -ieq $ReadValue) {
            $IOPS = Read-Host "Enter the IOPS value between 100 and 7000 (500:1)"
        }
    }

    $Region = Read-Host "Enter region"
    $VPCID = $CloudAccount.appliance.cloud_properties.vpc
    $ReadValue = Read-Host "Enter VPC ID [$VPCID]"
    if ("" -ne $ReadValue) {
        $VPCID = $ReadValue
    }

    $SubnetID = $CloudAccount.appliance.cloud_properties.network.interfaces.eth0.network_name
    $ReadValue = Read-Host "Enter subnet ID [$SubnetID]"
    if ("" -ne $ReadValue) {
        $SubnetID = $ReadValue
    }

    $AutoAssignPublicIP = $false
    $ReadValue = Read-Host "Auto assign public IP (true/false)[false]"
    if ("" -ne $ReadValue) {
        $AutoAssignPublicIP = [System.Convert]::ToBoolean($ReadValue)
    }

    $StaticPrivateIP = Read-Host "Enter static private IP to be assigned to target [None]"
    #TODO: Add tenancy.
    $InstanceType = Read-Host "Enter instance type"

    $EnforceTargetNetworkIsolation = $true
    $ReadValue = Read-Host "Enforce target network isolation (true/false)[true]"
    if ("" -ne $ReadValue) {
        $EnforceTargetNetworkIsolation = [System.Convert]::ToBoolean($ReadValue)
    }

    $SecurityGroups = @("RM-Migration-TargetWorker-" + $CloudAccount.appliance.cloud_properties.vpc)
    if (!$EnforceTargetNetworkIsolation) {
        $ReadValue = Read-Host "Enter one or more security groups separated by commas"
        if ("" -ne $ReadValue) {
            $SecurityGroups += $ReadValue.Split(",").Trim()
        }
    }

    $IAMRole = $null
    $IAMRole = Read-Host "Enter IAM role name to add [None]" #TODO: prepare proper ARN

    $ShouldCreateAMI = $false
    $ReadValue = Read-Host "Create an AMI from the target instance (true/false)[false]"
    if ("" -ne $ReadValue) {
        $ShouldCreateAMI = [System.Convert]::ToBoolean($ReadValue)
    }

    $ReadValue = Read-Host "Enter one or more instance tags in the format 'key=value' and separated by commas [None]"
    $InstanceTags = Get-RMStringAsHashtable -InputString $ReadValue

    $ShutdownSource = $false
    $ReadValue = Read-Host "Shutdown source after data is fully migrated (true/false)[false]"
    if ("" -ne $ReadValue) {
        $ShutdownSource = [System.Convert]::ToBoolean($ReadValue)
    }

    $ShutdownTarget = $false
    $ReadValue = Read-Host "Shutdown target after data is fully migrated (true/false)[false]"
    if ("" -ne $ReadValue) {
        $ShutdownTarget = [System.Convert]::ToBoolean($ReadValue)
    }

    $RemoveRMSAgent = $false
    $ReadValue = Read-Host "Remove RMS agent post migration (true/false)[false]"
    if ("" -ne $ReadValue) {
        $RemoveRMSAgent = [System.Convert]::ToBoolean($ReadValue)
    }

    $ReadValue = Read-Host "Enter migration instructions in the format 'key=value' and separated by commas [None]"
    $MigrationInstructions = Get-RMStringAsHashtable -InputString $ReadValue

    #TODO: Add Ignore validation errors after we add the preflight cmdlet

    $HashArguments = @{
        CloudAccount = $CloudAccount
        Entitlement = $Entitlement
        Source = $Source
        TargetVMName = $TargetVMName
        SelectedMounts = $SelectedMountPoints
        EncryptVolumes = $EncryptVolumes
        VolumeType = $VolumeType
        IOPS = $IOPS
        Region = $Region
        VPCID = $VPCID
        SubnetID = $SubnetID
        AutoAssignPublicIP = $AutoAssignPublicIP
        StaticPrivateIP = $StaticPrivateIP
        InstanceType = $InstanceType
        EnforceTargetNetworkIsolation = $EnforceTargetNetworkIsolation
        SecurityGroups = $SecurityGroups
        IAMRole = $IAMRole
        ShouldCreateAMI = $ShouldCreateAMI
        InstanceTags = $InstanceTags
        ShutdownSource = $ShutdownSource
        ShutdownTarget = $ShutdownTarget
        RemoveRMSAgent = $RemoveRMSAgent
        MigrationInstructions = $MigrationInstructions
    }

    $Response = New-RMAWSMigrationProfile @HashArguments

    $RMLoginResult = Get-Variable -Name "RMContext-UserLogin"
    $Uri = Get-Variable -Name "RMContext-ReactorURI"

    $Headers = @{
        Accept = "application/rm+json"
        "X-Auth-Token" = $RMLoginResult.Value.token
    }

    $Params = @{
        Method = "Post"
        Uri = $Uri.Value + "/migrationprofiles/" + $Response.id + "/migrations"
        Headers = $Headers
        ContentType = "application/json"
    }

    return Invoke-RMRestMethod -Params $Params
}

function Start-RMInteractiveAWSVMBasedMigration {
    param()
    $UserInput = @{}
    $CloudAccount = Read-RMCloudAccount -AccountType "aws" `
        -UserMessage "Enter target cloud that supports VM-based migrations" -VMBasedAppliancesOnly $true
    $Source = Read-RMVMBasedSource -UserMessage "Enter the VM name of the source machine to be migrated" `
        -ParameterName "Source VM name" -IsRequired $true -CloudAccount $CloudAccount

    $Entitlement = Get-RMEntitlement
    $UserInput.Add("Entitlement", $Entitlement)

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    if ($null -eq $CloudAccount) {
        return Update-RMMigrationReturnAsError `
            -Message "The migration appliance associated with the given source is not ready for use, cannot start the migration" `
            -RMMigrationReturn $RMMigrationReturn
    }

    $UserInput.Add("CloudAccount", $CloudAccount)
    $CloudAttributes = Get-RMCloudAttribute -CloudAccount $CloudAccount
    $IgnoreValidationErrors = Read-RMBoolean -UserMessage "Ignore validation errors" -DefaultValue "false"
    $UserInput.Add("IgnoreValidationErrors", $IgnoreValidationErrors)

    $SourceAttributeResult = Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
        -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn `
        -AccountType "aws"
    $Source, $ShouldExit, $OverrideExistingMigrationWarning, $OverrideExistingMigrationError = $SourceAttributeResult
    $UserInput.Add("Source", $Source)
    if ($ShouldExit) {
        return $RMMigrationReturn
    }
    
    if ($OverrideExistingMigrationError) {
        $OverrideExistingMigrationError = Read-RMBoolean -UserMessage "Would you like to override the previous migration attempt" -DefaultValue $false
        if (!$IgnoreValidationErrors -and !$OverrideExistingMigrationError) {
            return $RMMigrationReturn
        }
    }

    $ScheduledAt = Read-RMMigrationSchedule
    $UserInput.Add("ScheduledAt", $ScheduledAt)
    $TargetVMName = Read-RMString -UserMessage "Enter target VM Name" -DefaultValue $Source.host `
        -ParameterName "Target VM Name" -IsRequired $false
    $UserInput.Add("TargetVMName", $TargetVMName)

    $CloudSummary = Get-CloudAttributesSummary -CloudAccount $CloudAccount
    $RegionName = Read-RMString -UserMessage "Enter the region name where the source VM should be migrated to" `
        -ParameterName "Region name" -Options $CloudSummary.properties.regions.name `
        -DefaultValue $CloudAttributes.properties.regions[0].name -IsRequired $false
    $UserInput.Add("Region", $RegionName)

    $HasRegionChanged = $false
    if ($CloudAttributes.properties.regions[0].name -ine $RegionName) {
        Write-Output "Getting cloud attributes for region $RegionName ..." | Out-Host
        $CloudAttributes = Get-RMCloudAttributesByRegion -Region $RegionName -CloudAccount $CloudAccount
        $HasRegionChanged = $true
    }

    $VPC = Get-RMVPCByCloudAttributes -CloudAttributes $CloudAttributes
    $VPCID = ""
    if ($HasRegionChanged) {
        $VPCID = Read-RMString -UserMessage "Enter the VPC ID" -ParameterName "VPC ID" `
        -Options $VPC.Keys -IsRequired $true
    } else {
        $VPCID = Read-RMString -UserMessage "Enter the VPC ID" -ParameterName "VPC ID" `
            -DefaultValue $CloudAccount.appliance.cloud_properties.vpc `
            -Options $VPC.Keys -IsRequired $false
    }
    $UserInput.Add("VPCID", $VPCID)

    if (0 -eq $Source.attributes.storage.vm_disks.Count) {
        return Update-RMMigrationReturnAsError `
            -Message "Source has no disks, cannot be migrated" -RMMigrationReturn $RMMigrationReturn
    }

    $SourceDisksAsString = "Disks to be migrated [" + ((Get-RMVMDiskProperty -Source $Source) -join ", ") + "]"
    Write-Output $SourceDisksAsString | Out-Host

    $SelectedDisks = @()
    $ReturnedValue = Get-RMSelectedDisk -Source $Source
    if ($SelectedDisks -is [hashtable]) {
        $SelectedDisks += $ReturnedValue
    } else {
        $SelectedDisks = $ReturnedValue
    }
    $UserInput.Add("SelectedDisk", $SelectedDisks)

    $EnableEBSEncryption = Read-RMBoolean -UserMessage "Enable EBS encryption on all volumes" -DefaultValue "false"
    $UserInput.Add("EncryptAllVolume", $EnableEBSEncryption)

    #TODO - Add check for source supports IO1 and IO2 (NEED TO FIND WHEN EACH VOL TYPE IS SUPPORTED, INCLUDING GP2 AND 3)
    $VolumeType = Read-RMString -UserMessage "Enter volume type" -ParameterName "Volume type" -Options "GP2", `
        "GP3", "IO1", "IO2", "Magnetic" -DefaultValue "GP2" -IsRequired $false
    $UserInput.Add("VolumeType", (Get-RMVolumeTypeByUserInput -UserInputVolumeType $VolumeType))

    $SubnetIDs = Get-RMSubnetIDBySubnet -Subnet $VPC[$VPCID]
    $SubnetID = ""
    if ($HasRegionChanged) {
        $SubnetID = Read-RMString -UserMessage "Enter the subnet ID" -ParameterName "Subnet ID" `
            -Option $SubnetIDs -IsRequired $true
    } else {
        $SubnetID = Read-RMString -UserMessage "Enter the subnet ID" -ParameterName "Subnet ID" `
            -DefaultValue $CloudAccount.appliance.cloud_properties.network.interfaces.eth0.network_name `
            -Option $SubnetIDs -IsRequired $false
    }
    $UserInput.Add("SubnetID", $SubnetID)

    $Subnet = Get-RMSubnetByVPCIDAndSubnetID -VPCID $VPCID -SubnetID $SubnetID -VPC $VPC
    $AutoAssignPublicIP = Read-RMBoolean -UserMessage "Auto assign public IP address" `
        -DefaultValue ($Subnet.map_public_ip).ToString().ToLower()
    $UserInput.Add("AutoAssignPublicIP", $AutoAssignPublicIP)
    $UserInput.Add("AvailabilityZone", $Subnet.availability_zone)
    
    $UserInput.Add("StaticPrivateIP", (Read-RMString -UserMessage "Enter the static private IP address" `
        -DefaultValue "None" -ParameterName "Static private IP address" -IsRequired $false))

    $UserInput.Add("InstanceType", (Read-RMInstanceType -CloudAttributes $CloudAttributes))

    $EnforceTargetNetworkIsolation = Read-RMBoolean -UserMessage "Enforce target network isolation" -DefaultValue "true"
    if (!$EnforceTargetNetworkIsolation) {
        $VPCIDToSecurityGroup = Get-RMVPCAndSecurityGroupMapping -CloudAttributes $CloudAttributes
        $RMRequiredSG = $VPCIDToSecurityGroup[$VPCID].name | Where-Object {$_ -ieq ("RM-Migration-TargetWorker-" + $VPCID)}
        $UserSecurityGroups = @()
        if ($null -eq $RMRequiredSG) {
            $UserSecurityGroups = Read-RMToken -UserMessage "Enter one or more security group names, separated by commas" `
                -ParameterName "Security group names" -Options $VPCIDToSecurityGroup[$VPCID].name -IsRequired $true -Separator ","
        } else {
            $UserSecurityGroups = Read-RMToken -UserMessage "Enter one or more security group names, separated by commas" `
                -ParameterName "Security group names" -Options $VPCIDToSecurityGroup[$VPCID].name -DefaultValue $RMRequiredSG `
                -IsRequired $false -Separator ","
            if ($UserSecurityGroups -notcontains $RMRequiredSG) {
                $UserSecurityGroups += $RMRequiredSG
            }        
        }
        $SecurityGroupMapping = Get-RMSecurityGroupIDToNameMapping -SelectedSecurityGroupName $UserSecurityGroups `
            -SecurityGroup $VPCIDToSecurityGroup[$VPCID]
        $UserInput.Add("SecurityGroup", $SecurityGroupMapping)
    }

    $DisableTargetDNSRegistration = $false
    if("windows" -ieq $Source.os_type) {
        $DisableTargetDNSRegistration = Read-RMBoolean -UserMessage "Disable automatic DNS registration on target" -DefaultValue "false"
    }
    $UserInput.Add("DisableAutomaticDNSRegistrationOnTheTarget", $DisableTargetDNSRegistration)

    if ($CloudAttributes.properties.instance_profiles.Count -gt 0) {
        $IAMNameToARNMapping = @{}
        foreach ($InstanceProfile in $CloudAttributes.properties.instance_profiles) {
            $IAMNameToARNMapping[$InstanceProfile.name] = $InstanceProfile.arn
        }
        $IAMRole = Read-RMString -UserMessage "Enter the IAM role name that you want to add to the target VM" `
            -ParameterName "IAM role name" -Options $IAMNameToARNMapping.Keys -DefaultValue "None" -IsRequired $false
        if (![string]::IsNullOrWhiteSpace($IAMRole)) {
            $UserInput.Add("AddIAMRole", $IAMNameToARNMapping[$IAMRole])
        }    
    }

    $UserInput.Add("CreateAMI", (Read-RMBoolean -UserMessage "Create AMI from the target instance" -DefaultValue "false"))

    $ReadValue = Read-RMPair -UserMessage "Enter one or more instance tags in the format 'key=value' and separated by commas" `
        -Separator "=" -DefaultValue "None"
    $InstanceTags = Get-RMStringAsHashtable -InputString $ReadValue
    $UserInput.Add("InstanceTag", $InstanceTags)

    $UserInput.Add("ShutdownSource", (Read-RMBoolean -UserMessage "Shutdown source after data is fully migrated" -DefaultValue "false"))
    $UserInput.Add("ShutdownTarget", (Read-RMBoolean -UserMessage "Shutdown target after data is fully migrated" -DefaultValue "false"))
    $UserInput.Add("FinalizeMigration", (Read-RMBoolean -UserMessage "Remove snapshot(s) from the Target VM in preparation for a cutover" `
        -DefaultValue "false"))

    $ReadValue = Read-RMPair -UserMessage "Enter migration instructions in the format 'key=value' and separated by commas" `
        -Separator "=" -DefaultValue "None"
    if (($IgnoreValidationErrors -and $OverrideExistingMigrationError) -or (!$IgnoreValidationErrors -and $OverrideExistingMigrationError) -or $OverrideExistingMigrationWarning) {
        if ("" -eq $ReadValue) {
            $ReadValue = "override_source_migration=true"
        } else {
            $ReadValue += ",override_source_migration=true"
        }
    }
    $MigrationInstructions = Get-RMStringAsHashtable -InputString $ReadValue
    $UserInput.Add("MigrationInstruction", $MigrationInstructions)

    $Response = New-RMAWSVMBasedMigrationProfile @UserInput
    $ShouldExit = Start-RMMigrationPreflight -MigrationProfileId $Response.id `
        -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }

    $IsScheduled = ![string]::IsNullOrEmpty($ScheduledAt)
    $MigrationResponse = Invoke-RMMigrationPost -MigrationProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"    
}

Export-ModuleMember -Function Start-RMAWSOSBasedMigration, Start-RMInteractiveAWSVMBasedMigration