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

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.
Метки