Skip to content

Instantly share code, notes, and snippets.

@r4rohan
Last active April 26, 2022 11:33
Show Gist options
  • Save r4rohan/50d19391cd95d93e35557b88766ca953 to your computer and use it in GitHub Desktop.
Save r4rohan/50d19391cd95d93e35557b88766ca953 to your computer and use it in GitHub Desktop.
Powershell script to create GCE Disk Snapshot before patching GCE VMs via VM Manager
Set-StrictMode -Version Latest;
$ErrorActionPreference = "Stop";
<#
.SYNOPSIS
This function calls the gcloud binary
.PARAMETER Arguments
Array of arguments to be passed to gcloud
.OUTPUTS
PSCustomObject {StandardOutput, StandardError, ExitCode}
.EXAMPLE
Invoke-Gcloud -Arguments @("compute", "instances", "list");
#>
function Invoke-Gcloud
{
param
(
[string[]] $Arguments
);
process
{
$info = New-Object System.Diagnostics.ProcessStartInfo;
$info.FileName = "cmd.exe";
$info.RedirectStandardError = $true;
$info.RedirectStandardOutput = $true;
$info.UseShellExecute = $false;
$info.LoadUserProfile = $true;
$info.Arguments = "/C gcloud.cmd $($Arguments -join " ")";
$process = New-Object System.Diagnostics.Process;
$process.StartInfo = $info;
$process.Start() | Out-Null;
[PSCustomObject] @{
StandardOutput = $process.StandardOutput.ReadToEnd()
StandardError = $process.StandardError.ReadToEnd()
ExitCode = $process.ExitCode
};
$process.WaitForExit();
}
}
<#
.SYNOPSIS
This function returns the object name from a given resource URI
.PARAMETER Uri
GCP resource URI
.OUTPUTS
Object name
.EXAMPLE
ConvertTo-ObjectName -Uri "https://www.googleapis.com/compute/v1/projects/test/zones/europe-west4-a/disks/test";
#>
function ConvertTo-ObjectName
{
param
(
[string] $Uri
);
process
{
return $uri.Substring($uri.LastIndexOf("/") + 1);
}
}
<#
.SYNOPSIS
This function returns the project from a given resource URI
.PARAMETER Uri
GCP resource URI
.OUTPUTS
Project
.EXAMPLE
ConvertTo-Project -Uri "https://www.googleapis.com/compute/v1/projects/test/zones/europe-west4-a/disks/test";
#>
function ConvertTo-Project
{
param
(
[string] $Uri
);
process
{
$start = $Uri.IndexOf("projects/") + 9;
$end = $Uri.IndexOf("/", $start);
if($end -eq -1)
{
$end = $Uri.Length - $start;
}
return $Uri.Substring($start, $end);
}
}
<#
.SYNOPSIS
This function returns the zone from a given resource URI
.PARAMETER Uri
GCP resource URI
.OUTPUTS
Zone
.EXAMPLE
ConvertTo-Zone -Uri "https://www.googleapis.com/compute/v1/projects/test/zones/europe-west4-a/disks/test";
#>
function ConvertTo-Zone
{
param
(
[string] $Uri
);
process
{
$start = $Uri.IndexOf("zones/") + 6;
$end = $Uri.IndexOf("/", $start);
if($end -eq -1)
{
$end = $Uri.Length - $start;
}
return $Uri.Substring($start, $end);
}
}
<#
.SYNOPSIS
Returns instance metadata queried from the GCE metadata endpoint
.PARAMETER Entry
Metadata entry to query for
.OUTPUTS
Metadata value for the entry
.EXAMPLE
Get-InstanceMetadata -Entry "name";
.LINK
https://cloud.google.com/compute/docs/storing-retrieving-metadata#project-instance-metadata
#>
function Get-InstanceMetadata
{
param
(
[string] $Entry
);
process
{
return Invoke-RestMethod `
-Headers @{"Metadata-Flavor" = "Google"} `
-Uri "http://metadata/computeMetadata/v1/instance/$($Entry)";
}
}
<#
.SYNOPSIS
Function determines the ID of the patch job
.DESCRIPTION
At any given time more than one patch job might be active.
This function validates each active patch job with the name
of the current VM and its region and returns the ID of the
first patch job that matches all criteria.
.PARAMETER VmName
Name of the VM
.PARAMETER Zone
Zone of the VM
.OUTPUTS
GUID representing the patch job ID
.EXAMPLE
Get-PatchJobId -VmName "instance-1" -Zone "europe-west4-a"
#>
function Get-PatchJobId
{
param
(
[string] $VmName,
[string] $Zone
);
process
{
# Get running patch jobs
$jobs = gcloud compute os-config patch-jobs list --filter="state:patching" --format="value(ID)";
# Iterating all jobs
foreach($job in $jobs)
{
# Check if this instance is targetted by the patch job
# and the job is in the RUNNING_PRE_PATCH_STEP
$filter = "name:$($VmName) AND zone:$($Zone) AND state:RUNNING_PRE_PATCH_STEP";
$instance = gcloud compute os-config patch-jobs list-instance-details $job --filter=$filter --format="value(NAME)";
if($instance -eq $VmName)
{
return $job;
}
}
return $null;
}
}
<#
.SYNOPSIS
Gets the name of the current VM from metadata
.OUTPUTS
Name of the VM
.EXAMPLE
Get-VmName;
#>
function Get-VmName
{
param
(
);
process
{
return Get-InstanceMetadata -Entry "name";
}
}
<#
.SYNOPSIS
Gets the zone of the current VM from metadata
.OUTPUTS
Zone of the VM
.EXAMPLE
Get-Zone;
#>
function Get-Zone
{
param
(
);
process
{
$zone = Get-InstanceMetadata -Entry "zone";
return ConvertTo-Zone -Uri $zone;
}
}
<#
.SYNOPSIS
Retrieve the resource IDs for the disks associated with the given VM
.PARAMETER VmName
Name of the VM
.PARAMETER Zone
Zone of the VM
.OUTPUTS
Array with disk resource IDs
.EXAMPLE
Get-Disks -VmName "instance-1" -Zone "europe-west4-a"
#>
function Get-Disks
{
param
(
[string] $VmName,
[string] $Zone
);
process
{
$arguments = @(
"compute",
"instances",
"describe",
$VmName,
"--zone $Zone",
'--format="value[delimiter=\n](disks[].source)"'
);
$process = Invoke-Gcloud -Arguments $arguments;
# Trim whitespaces from output and split on newline
return $process.StandardOutput.Trim().Split("`n");
}
}
<#
.SYNOPSIS
Creates a disk clone for all attached disk(s)
.PARAMETER Disks
Array of disk resource URIs
.PARAMETER VmName
Name of the current VM
.PARAMETER PachJobId
Id of the patch job
.EXAMPLE
New-Snapshot -Disks @(https://www.googleapis.com/compute/v1/projects/test/zones/europe-west4-a/disks/boot https://www.googleapis.com/compute/v1/projects/test/zones/europe-west4-a/disks/data) `
-VmName "instance-1" -PatchJobId "9245b1bc-643b-4f87-8020-eda82d1d3cb4"
#>
function New-Snapshot
{
param
(
[string[]] $Disks,
[string] $VmName,
[string] $PatchJobId,
[string] $zone
);
process
{
foreach($diskId in $Disks)
{
$sourceName = ConvertTo-ObjectName -Uri $diskId;
$targetName = "$sourceName-$PatchJobId";
$targetDiskId = $diskId.Replace($sourceName, $targetName);
$zone = Get-Zone;
$Tdate=Get-Date -UFormat "%m%d%Y%H%M%S"
$arguments = @(
"compute",
"disks",
"snapshot",
"$sourceName",
"--user-output-enabled true",
"--snapshot-names $sourceName-$Tdate",
"--zone $zone"
);
$process = Invoke-Gcloud -Arguments $arguments;
if($process.ExitCode -ne 0)
{
return $false;
}
}
return $true;
}
}
$vmName = Get-VmName;
$zone = Get-Zone;
Write-Host -NoNewline "Determining patch job: ";
$jobId = Get-PatchJobId -VmName $vmName -Zone $zone;
Write-Host $jobId;
Write-Host -NoNewline "Retrieving disks associated with VM: ";
$disks = @(Get-Disks -VmName $vmName -Zone $zone);
Write-Host "$($disks.Count) disk(s) found";
Write-Host -NoNewline "Creating Snapshot(s): ";
$result = New-Snapshot -Disks $disks -VmName $VmName -PatchJobId $jobId;
if($result)
{
Write-Host "done";
}
else
{
Write-Host "failed";
# Return non-zero exit code to stop patching
[System.Environment]::Exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment