Powershell - Нарезка файлов для AWS Glacier

Записка от 13.07.2017

Сервис архивирования Amazon Web Services Glacier имеет ограничения на загрузку файлов по размеру в 4 ГБ. Чтобы обойти эту проблему и иметь возможность загружать файлы большого объема Amazon реализовал метод загрузки архивов по частям - команды initiate-multipart-upload, upload-multipart-part, complete-multipart-upload и т.п.

Я разработал PowerShell скрипт для нарезки файлов и формирования команд для инициализации, загрузки и закрытия архива в AWS Glacier.

Для работы скрипта необходимо установить на сервер, где выполняется нарезка, две программы AWS Command Line Interface и AWS Tools For Windows. Создать два файла Split-File.ps1 и Split-File.cmd с содержимым, показанным ниже.

Код скрипта Split-File.ps1:

Add-Type -Path "C:\Program Files (x86)\AWS SDK for .NET\bin\Net45\AWSSDK.Core.dll"
Add-Type -Path "C:\Program Files (x86)\AWS SDK for .NET\bin\Net45\AWSSDK.Glacier.dll"

# AWS variables
$AWSAccountID = YOUR_AWS_ID
$AWSRegion = 'YOUR_AWS_REGION'
$AWSVaultName = 'YOUR_AWS_GLACIER_VAILT_NAME'
$AWSProfileAccessKey = "YOUR_AWS_SERVICE_ACCOUNT_ACCESS_KEY"
$AWSProfileSecretKey = "YOUR_AWS_SERVICE_ACCOUNT_SECRET_KEY"

if($args[0] -eq $null) {
  Write-Host "Error: No input parameter"
  Break
} else {
  [String] $InputFile = $args[0];
} 

[Int32] $PartSize = 1073741824 # 1 GB
[Int32] $PartNumber = 1
[String[]] $PartChecksumList = @()

# ------- Split-File -------

$InputStream = [System.IO.File]::OpenRead($InputFile)

$Piece = New-Object byte[] $PartSize
While ($BytesRead = $InputStream.Read($Piece, 0, $PartSize)) {
  $OutputFile = $InputFile + ".p" + $PartNumber.ToString("00")
  $OutputStream = [System.IO.File]::OpenWrite($OutputFile)
  $OutputStream.Write($Piece, 0, $BytesRead)
  $OutputStream.Close()
  $OutputStream = [System.IO.File]::OpenRead($OutputFile)
  $CurrentChecksum = [Amazon.Glacier.TreeHashGenerator]::CalculateTreeHash($OutputStream)
  $OutputStream.Close()
  $PartChecksumList += $CurrentChecksum
  $PartNumber += 1
}
$InputStream.Close()
$TreeHash = [Amazon.Glacier.TreeHashGenerator]::CalculateTreeHash([String[]]$PartChecksumList)
$OutputTreeHashFile = $InputFile + ".hash"
$TreeHash | Out-File $OutputTreeHashFile


# ------- Generate multiupload command -------

$FileDirectory = (Get-Item -Path $InputFile).DirectoryName
$FileName = (Get-Item -Path $InputFile).Name
$FileSize = (Get-Item -Path $InputFile).Length
$FileParts = Get-ChildItem $FileDirectory -Filter "$FileName.p*" | Sort-Object -Property "Name"

$MultiuploadCommandFile = $InputFile + "_Command1.txt"
Out-File -FilePath $MultiuploadCommandFile
$CommandLine = "aws glacier initiate-multipart-upload --account-id $AWSAccountID"
$CommandLine += " --vault-name $AWSVaultName --part-size $PartSize"
$CommandLine += " --archive-description ""$FileName"""
$CommandLine | Out-File -FilePath $MultiuploadCommandFile -Append

$MultiuploadCommandFile = $InputFile + "_Command2.txt"
Out-File -FilePath $MultiuploadCommandFile
"set uploadID=Put_Here_Upload_ID" | Out-File -FilePath $MultiuploadCommandFile -Append
"set uploadTreeHash=$TreeHash" | Out-File -FilePath $MultiuploadCommandFile -Append
"" | Out-File -FilePath $MultiuploadCommandFile -Append

[int] $i=1
[long] $StartByte = 0
[long] $EndByte = 0
ForEach ($FilePart in $FileParts) {
  If ($i -lt $FileParts.Count) {
    $EndByte = $StartByte + $PartSize - 1
  } Else {
    $EndByte = $FileSize - 1
  }
  $FilePartRange = """bytes $StartByte-$EndByte/*"""
  $FilePartPath = """" + $FilePart.FullName + """"
  $CommandLine = "aws glacier upload-multipart-part --account-id $AWSAccountID --vault-name $AWSVaultName"
  $CommandLine +=" --upload-id %uploadID% --range $FilePartRange --body $FilePartPath"
  $CommandLine | Out-File -FilePath $MultiuploadCommandFile -Append
  $i++
  $StartByte = $EndByte + 1
}

"" | Out-File -FilePath $MultiuploadCommandFile -Append
$CommandLine = "aws glacier complete-multipart-upload --account-id $AWSAccountID --vault-name $AWSVaultName"
$CommandLine += " --upload-id %uploadID% --archive-size $FileSize --checksum %uploadTreeHash%"
$CommandLine | Out-File -FilePath $MultiuploadCommandFile -Append

Код скрипта Split-File.cmd:

powershell.exe "%~DP0Split-File.ps1" %1

Перед запуском нарезки файла необходимо в файле Split-File.ps1 вписать данные своей учетной записи AWS с строках 5-9.

Для нарезки файла необходимо перетащить архив на скрипт Split-File.cmd, который вызовет Split-File.ps1 и подставит в качестве входного параметра путь к архиву.

Результатом работы скрипта будет:

  • Коллекция частей архива по 1 ГБ (имя_архива.p01, имя_архива.p02 и т.д).
  • Файл с контрольной суммой (имя_архива.hash).
  • Файл с командой для инициализации закачки (имя_архива_Command1.txt).
  • Файл с командами закачки чайтей архива и закрытия архива (имя_архива_Command2.txt).

Чтобы выполнить закачку разрезанного архива необходимо:

  • Открыть командную строку.
  • Вставить в командную строку содержимое файла имя_архива_Command1.txt.
  • Из ответа сервера скопировать значение uploadID.
  • В файле имя_архива_Command2.txt заменить Put_Here_Upload_ID на скопированное значение uploadID.
  • Вставить в командную строку содержимое файла имя_архива_Command2.txt - начнется поочередное выполнение команд из буфера обмена - закачка частей архива.
  • При успешной закачке всех частей в командную строку сервер AWS вернет значение archiveID, которое он присвоил новому архиву. В противном случае выдаст номер частей архива, загрузка которых не удалась.
Примечание. Учетная запись от имени которой выполняется закачка в AWS Glacier должна иметь права на чтение и закачку в указанный букет архивов. Данные этой учетной записи должны быть сконфигурированы в программе AWS Command Line Interface.
Вверх