Skip to content

Add support for packed modules to keep deployments <250mb #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions powershell-runtime/source/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ Import-Module '/opt/modules/pwsh-runtime.psd1'
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-bootstrap]Importing .NET class from .cs file to support script properties and method' }
Add-Type -TypeDefinition ([System.IO.File]::ReadAllText('/opt/PowerShellLambdaContext.cs'))

# Unpack compressed modules, if present

# Combined
If (Test-RuntimePackedModule -Combined) {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') {Write-Host '[RUNTIME-bootstrap]Unpacking combined module archives'}
Import-ModuleArchive
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') {Write-Host '[RUNTIME-bootstrap]Finished unpacking archives'}

}
# NuPkg
If (Test-RuntimePackedModule -NuPkg) {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') {Write-Host '[RUNTIME-bootstrap]Unpacking module NuGet packages'}
Import-ModulePackage
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') {Write-Host '[RUNTIME-bootstrap]Finished unpacking NuGet packages'}
}

# Modify $env:PSModulePath to support Lambda paths
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') {Write-Host '[RUNTIME-bootstrap]Modify PSModulePath to support Lambda paths'}
Set-PSModulePath
Expand Down
40 changes: 40 additions & 0 deletions powershell-runtime/source/modules/Private/Import-ModuleArchive.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
function private:Import-ModuleArchive {
<#
.SYNOPSIS
Unpacks compressed PowerShell modules from .zip archives (modules.zip)
.DESCRIPTION
Unpacks compressed PowerShell modules from .zip archives (modules.zip) into a subdirectory of /tmp.

This folder is later added to $env:PSModulePath, before user code runs, if module archives existed.
.NOTES
The contents of this archive should match the format of a folder in $Env:PSModulePath. More specifically:
* Module names should be top-level directories.
* One or more versions of the same module may be hosted in their own subdirectories, with respective version numbers.
* The module root (.psd1/.psm1 files, etc.) is contained within either the module-named or module-versioned directory.

Module packages are imported from two locations, from lowest to highest precedence:
* /opt/ (Combined Lambda layer directory)
* $Env:LAMBDA_TASK_ROOT (Lambda Function Package deployment directory)

If archives are detected at both locations, they will be extracted over the top of each-other.
#>

$SearchPaths = @(
"/opt/modules.zip"
$(Join-Path $env:LAMBDA_TASK_ROOT -ChildPath "modules.zip")
)

If ($SearchPaths | ? { Test-Path $_ }) {
$UnpackDirectory = '/tmp/powershell-custom-runtime-unpacked-modules/combined/'
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-bootstrap]Creating unpack directory for combined module archives' }
New-Item -ItemType Directory -Path $UnpackDirectory -Force
$SearchPaths | ? { Test-Path $_ } | ForEach-Object {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-bootstrap]Unpacking $_ to $UnpackDirectory" }
Expand-Archive -LiteralPath $_ -DestinationPath $UnpackDirectory -Force
}
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-bootstrap]Archive unpack complete' }
}
else {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-bootstrap]No module archives detected; nothing to do.' }
}
}
49 changes: 49 additions & 0 deletions powershell-runtime/source/modules/Private/Import-ModulePackage.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
function private:Import-ModulePackage {
<#
.SYNOPSIS
Installs compressed PowerShell modules from NuGet packages (*.nupkg)
.DESCRIPTION
Installs compressed PowerShell modules from NuGet packages (*.nupkg) into a subdirectory of /tmp.

This folder is later added to $env:PSModulePath, before user code runs, if module packages existed.
.NOTES
These packages should match the NuPkg format used by PSResourceGet or PowerShellGet.

Packages can be exported either by:
* Downloading the .nupkg files directly from an upstream source (e.g. PowerShell Gallery)
* Using the -AsNuPkg parameter on Save-PSResource in the Microsoft.PowerShell.PSResourceGet module.

Module packages are imported from two locations, from lowest to highest precedence:
* /opt/module-nupkgs/ (Combined Lambda layer directory)
* $Env:LAMBDA_TASK_ROOT/module-nupkgs/ (Lambda Function Package deployment directory)
#>
$SearchPaths = @{
Layer = "/opt/module-nupkgs/*.nupkg"
Root = (Join-Path $env:LAMBDA_TASK_ROOT -ChildPath "module-nupkgs" -AdditionalChildPath "*.nupkg")
}

If ($SearchPaths.Values | ? { Test-Path $_ }) {
$UnpackDirectory = '/tmp/powershell-custom-runtime-unpacked-modules/nupkgs/'
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-bootstrap]Creating unpack directory for individual module packages' }
New-Item -ItemType Directory -Path $UnpackDirectory -Force
$SearchPaths.GetEnumerator() | ? { Test-Path $_.Value } | ForEach-Object {
$PackageDirectory = Split-Path $_.Value -Parent
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-bootstrap]Importing module packages from $PackageDirectory" }
$RepositoryName = "Lambda-Local-$($_.Key)"
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-bootstrap]Registering local package repository $RepositoryName" }
Register-PSResourceRepository -Name $RepositoryName -Uri $PackageDirectory -Trusted -Priority 1
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-bootstrap]Enumerating packages in $PackageDirectory (PSResource repository $RepositoryName)" }
Find-PSResource -Name * -Repository $RepositoryName | ForEach-Object -Parallel {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-bootstrap]Saving package $($_.Name) version $($_.Version) (PSResource repository $($using:RepositoryName))" }
$_ | Save-PSResource -SkipDependencyCheck -Path $using:PackageDirectory -Quiet -AcceptLicense -Confirm:$false
}
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-bootstrap]Registering local package repository $RepositoryName" }
Unregister-PSResourceRepository -Name $RepositoryName -Confirm:$false
}
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-bootstrap]Archive unpack complete' }
}
else {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-bootstrap]No module archives detected; nothing to do.' }
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function private:Set-PSModulePath {
1: Modules supplied with pwsh
2: User supplied modules as part of Lambda Layers
3: User supplied modules as part of function package
4: Compressed modules
#>
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-Set-PSModulePath]Start: Set-PSModulePath' }
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host '[RUNTIME-Set-PSModulePath]Setting PSModulePath environment variable' }
Expand All @@ -22,5 +23,13 @@ function private:Set-PSModulePath {
'/opt/modules', # User supplied modules as part of Lambda Layers
[System.IO.Path]::Combine($env:LAMBDA_TASK_ROOT, 'modules') # User supplied modules as part of function package
) -join ':'
If (Test-RuntimePackedModule -Combined) {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-Set-PSModulePath]Combined module package detected, adding unpack directory to PSModulePath" }
$env:PSModulePath += (':' + '/tmp/powershell-custom-runtime-unpacked-modules/combined') # Modules unpacked via Import-ModuleArchive
}
If (Test-RuntimePackedModule -NuPkg) {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-Set-PSModulePath]Nupkg module package(s) detected, adding unpack directory to PSModulePath" }
$env:PSModulePath += (':' + '/tmp/powershell-custom-runtime-unpacked-modules/nupkg') # Modules unpacked via Import-ModulePackage
}
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-Set-PSModulePath]PSModulePath environment variable set to: $($env:PSModulePath)" }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
function private:Test-RuntimePackedModule {
<#
.SYNOPSIS
Tests whether the current runtime environment contains compressed module packages (combined .zip or per-module .nupkg)
.DESCRIPTION
Tests whether the current runtime environment contains compressed module packages (combined .zip or per-module .nupkg)
.NOTES
Looks for module packages in two locations:
* /opt/ (Combined Lambda layer directory)
* $Env:LAMBDA_TASK_ROOT (Lambda Function package directory)

Module packages can take two forms:
* A single, combined module archive, named "modules.zip".
The contents of this archive should match the format of a folder in $Env:PSModulePath.
(Module names as top-level directories, optional version subdirectory, corresponding module root)
* Individual module archives, as .nupkg files, inside a subdirectory named "module-nupkgs"
These files should match:
* The naming convention used by PSResourceGet. (e.g. <module-name>.<version>.nupkg)
* The Nupkg archive spec (module root at archive root, NuGet [Content_Types].xml/_rels, etc.)

The following file locations should all be detected (assume $Env:LAMBDA_TASK_ROOT = /var/lambda/)
* /opt/modules.zip
* /var/lambda/modules.zip
* /opt/module-nupkgs/AWS.Tools.Common.4.1.833.nupkg
* /var/lambda/module-nupkgs/AWS.Tools.Common.4.1.833.nupkg
.EXAMPLE
Test-MyTestFunction -Verbose
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
#>

[CmdletBinding()]
param(
# Looks for combined module archives (modules.zip).
[Parameter(
Mandatory,
ParameterSetName="Combined"
)]
[Switch]
$Combined,

# Looks for individual module packages (*.nupkg).
[Parameter(
Mandatory,
ParameterSetName="NuPkg"
)]
[Switch]
$NuPkg
)

$BaseDirectories = @(
"/opt",
$Env:LAMBDA_TASK_ROOT
)

switch ($PSCmdlet.ParameterSetName) {
"Combined" {

if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-Test-RuntimePackedModule]Searching for combined module archives" }

$BaseDirectories | Join-Path -ChildPath "modules.zip" | Get-Item -ErrorAction SilentlyContinue | Set-Variable FoundItems

}
"NuPkg" {

if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-Test-RuntimePackedModule]Searching for individual module packages" }

$BaseDirectories | Join-Path -ChildPath "module-nupkgs" -AdditionalChildPath "*.nupkg" | Get-Item -ErrorAction SilentlyContinue | Set-Variable FoundItems

}
}

If ($FoundItems) {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-Test-RuntimePackedModule]Found $($FoundItems | Measure-Object | % Count) match(es)" }
return $true
} else {
if ($env:POWERSHELL_RUNTIME_VERBOSE -eq 'TRUE') { Write-Host "[RUNTIME-Test-RuntimePackedModule]No matches found" }
return $false
}
}