Skip to content

Commit

Permalink
[plugin] support for resources packaging on ubuntu (#467)
Browse files Browse the repository at this point in the history
When including resources in the package and packaging on Ubuntu,
`FileManager` throws a FilePermission error. The docker daemon runs as
root and files to be copied are owned by `root` while the archiver runs
as the current user (`ubuntu` on EC2 Ubuntu). The `FileManager` manages
to copy the files but throws an error after the copy. We suspect the
`FileManager` to perform some kind of operation after the copy and it
fails because of the `root` permission of the files.

See
#449 (comment)
for a description of the problem.

This PR contains code to reproduce the problem, a very simple
workaround, and an integration test.
The workaround consists of 
- trapping all errors
- verify if the error is the permission error (Code = 513)
- verify if the files have been copied or not 
- if the two above conditions are met, ignore the error, otherwise
re-throw it

I would rather prefer a solution that solves the root cause rather than
just ignoring the error.
We're still investigating the root cause (see [this
thread](https://forums.swift.org/t/filemanager-copyitem-on-linux-fails-after-copying-the-files/77282)
on the Swift Forum and this issue on Swift Foundation
swiftlang/swift-foundation#1125
  • Loading branch information
sebsto authored Jan 21, 2025
1 parent b4f0164 commit 71d49b0
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 13 deletions.
15 changes: 13 additions & 2 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ on:
type: boolean
description: "Boolean to enable the compilation of examples. Defaults to true."
default: true
archive_plugin_examples:
type: string
description: "The list of examples to run through the archive plugin test. Pass a String with a valid JSON array such as \"[ 'HelloWorld', 'APIGateway' ]\""
required: true
default: ""
archive_plugin_enabled:
type: boolean
description: "Boolean to enable the test of the archive plugin. Defaults to true."
Expand Down Expand Up @@ -54,7 +59,7 @@ jobs:
# We are using only one Swift version
swift:
- image: ${{ inputs.matrix_linux_swift_container_image }}
swift_version: "6.0.1-amazonlinux2"
swift_version: "6.0.3-amazonlinux2"
container:
image: ${{ matrix.swift.image }}
steps:
Expand Down Expand Up @@ -98,6 +103,10 @@ jobs:
name: Test archive plugin
if: ${{ inputs.archive_plugin_enabled }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
examples: ${{ fromJson(inputs.archive_plugin_examples) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -107,8 +116,10 @@ jobs:
# https://github.com/actions/checkout/issues/766
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: Test the archive plugin
env:
EXAMPLE: ${{ matrix.examples }}
run: |
.github/workflows/scripts/check-archive-plugin.sh
.github/workflows/scripts/check-archive-plugin.sh
check-foundation:
name: No dependencies on Foundation
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ jobs:
# We pass the list of examples here, but we can't pass an array as argument
# Instead, we pass a String with a valid JSON array.
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Testing', 'Tutorial' ]"

examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'ResourcesPackaging', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Testing', 'Tutorial' ]"
archive_plugin_examples: "[ 'HelloWorld', 'ResourcesPackaging' ]"
archive_plugin_enabled: true

swift-6-language-mode:
Expand Down
16 changes: 13 additions & 3 deletions .github/workflows/scripts/check-archive-plugin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@
##
##===----------------------------------------------------------------------===##

EXAMPLE=HelloWorld
log() { printf -- "** %s\n" "$*" >&2; }
error() { printf -- "** ERROR: %s\n" "$*" >&2; }
fatal() { error "$@"; exit 1; }

test -n "${EXAMPLE:-}" || fatal "EXAMPLE unset"

OUTPUT_DIR=.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager
OUTPUT_FILE=${OUTPUT_DIR}/MyLambda/bootstrap
ZIP_FILE=${OUTPUT_DIR}/MyLambda/MyLambda.zip

pushd Examples/${EXAMPLE} || exit 1
pushd "Examples/${EXAMPLE}" || exit 1

# package the example (docker and swift toolchain are installed on the GH runner)
LAMBDA_USE_LOCAL_DEPS=../.. swift package archive --allow-network-connections docker || exit 1
Expand All @@ -33,5 +38,10 @@ file "${OUTPUT_FILE}" | grep --silent ELF
# does the ZIP file contain the bootstrap?
unzip -l "${ZIP_FILE}" | grep --silent bootstrap

echo "✅ The archive plugin is OK"
# if EXAMPLE is ResourcesPackaging, check if the ZIP file contains hello.txt
if [ "$EXAMPLE" == "ResourcesPackaging" ]; then
unzip -l "${ZIP_FILE}" | grep --silent hello.txt
fi

echo "✅ The archive plugin is OK with example ${EXAMPLE}"
popd || exit 1
3 changes: 2 additions & 1 deletion .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ Package.resolved
*.yaml
*.yml
**/.npmignore
**/*.json
**/*.json
**/*.txt
8 changes: 8 additions & 0 deletions Examples/ResourcesPackaging/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
57 changes: 57 additions & 0 deletions Examples/ResourcesPackaging/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

// needed for CI to test the local version of the library
import struct Foundation.URL

let package = Package(
name: "ResourcesPackaging",
platforms: [.macOS(.v15)],
products: [
.executable(name: "MyLambda", targets: ["MyLambda"])
],
dependencies: [
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main")
],
targets: [
.executableTarget(
name: "MyLambda",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
],
path: ".",
resources: [
.process("hello.txt")
]
)
]
)

if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
localDepsPath != "",
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
v.isDirectory == true
{
// when we use the local runtime as deps, let's remove the dependency added above
let indexToRemove = package.dependencies.firstIndex { dependency in
if case .sourceControl(
name: _,
location: "https://github.com/swift-server/swift-aws-lambda-runtime.git",
requirement: _
) = dependency.kind {
return true
}
return false
}
if let indexToRemove {
package.dependencies.remove(at: indexToRemove)
}

// then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..)
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
package.dependencies += [
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
]
}
26 changes: 26 additions & 0 deletions Examples/ResourcesPackaging/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import AWSLambdaRuntime
import Foundation

let runtime = LambdaRuntime {
(event: String, context: LambdaContext) in
guard let fileURL = Bundle.module.url(forResource: "hello", withExtension: "txt") else {
fatalError("no file url")
}
return try String(contentsOf: fileURL, encoding: .utf8)
}

try await runtime.run()
1 change: 1 addition & 0 deletions Examples/ResourcesPackaging/hello.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello World
28 changes: 23 additions & 5 deletions Plugins/AWSLambdaPackager/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,29 @@ struct AWSLambdaPackager: CommandPlugin {
let resourcesDirectoryName = artifactURL.lastPathComponent
let relocatedResourcesDirectory = workingDirectory.appending(path: resourcesDirectoryName)
if FileManager.default.fileExists(atPath: artifactURL.path()) {
try FileManager.default.copyItem(
atPath: artifactURL.path(),
toPath: relocatedResourcesDirectory.path()
)
arguments.append(resourcesDirectoryName)
do {
try FileManager.default.copyItem(
atPath: artifactURL.path(),
toPath: relocatedResourcesDirectory.path()
)
arguments.append(resourcesDirectoryName)
} catch let error as CocoaError {

// On Linux, when the build has been done with Docker,
// the source file are owned by root
// this causes a permission error **after** the files have been copied
// see https://github.com/swift-server/swift-aws-lambda-runtime/issues/449
// see https://forums.swift.org/t/filemanager-copyitem-on-linux-fails-after-copying-the-files/77282

// because this error happens after the files have been copied, we can ignore it
// this code checks if the destination file exists
// if they do, just ignore error, otherwise throw it up to the caller.
if !(error.code == CocoaError.Code.fileWriteNoPermission
&& FileManager.default.fileExists(atPath: relocatedResourcesDirectory.path()))
{
throw error
} // else just ignore it
}
}
}

Expand Down
70 changes: 70 additions & 0 deletions scripts/ubuntu-install-swift.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftAWSLambdaRuntime open source project
##
## Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##

sudo apt update && sudo apt -y upgrade

# Install Swift 6.0.3
sudo apt-get -y install \
binutils \
git \
gnupg2 \
libc6-dev \
libcurl4-openssl-dev \
libedit2 \
libgcc-13-dev \
libncurses-dev \
libpython3-dev \
libsqlite3-0 \
libstdc++-13-dev \
libxml2-dev \
libz3-dev \
pkg-config \
tzdata \
unzip \
zip \
zlib1g-dev

wget https://download.swift.org/swift-6.0.3-release/ubuntu2404-aarch64/swift-6.0.3-RELEASE/swift-6.0.3-RELEASE-ubuntu24.04-aarch64.tar.gz

tar xfvz swift-6.0.3-RELEASE-ubuntu24.04-aarch64.tar.gz

export PATH=/home/ubuntu/swift-6.0.3-RELEASE-ubuntu24.04-aarch64/usr/bin:"${PATH}"

swift --version

# Install Docker
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
# shellcheck source=/etc/os-release
# shellcheck disable=SC1091
. /etc/os-release
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$VERSION_CODENAME stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Add the current user to the docker group
sudo usermod -aG docker "$USER"

# LOGOUT and LOGIN to apply the changes
exit 0
28 changes: 28 additions & 0 deletions scripts/ubuntu-test-plugin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftAWSLambdaRuntime open source project
##
## Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##

# Connect with ssh

export PATH=/home/ubuntu/swift-6.0.3-RELEASE-ubuntu24.04-aarch64/usr/bin:"${PATH}"

# clone a project
git clone https://github.com/swift-server/swift-aws-lambda-runtime.git

# be sure Swift is install.
# Youc an install swift with the following command: ./scripts/ubuntu-install-swift.sh

# build the project
cd swift-aws-lambda-runtime/Examples/ResourcesPackaging/ || exit 1
LAMBDA_USE_LOCAL_DEPS=../.. swift package archive --allow-network-connections docker

0 comments on commit 71d49b0

Please sign in to comment.