Skip to content
Merged
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
6 changes: 2 additions & 4 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ concurrency:
jobs:
swift-test:
name: Swift tests
runs-on: ubuntu-24.04-arm # free arm64 runner (public repo); matches prod arch
container: swift:6.3-amazonlinux2 # exact prod toolchain — same image scripts/build.sh uses
runs-on: ubuntu-24.04-arm # free arm64 runner (public repo); matches prod arch
container: swift:6.3-amazonlinux2023 # exact prod toolchain — same image scripts/build.sh uses; already bundles git + openssl-devel
steps:
- name: Install git + openssl (AL2 image, needed by SwiftPM/deps)
run: yum -y install git openssl openssl-devel
- uses: actions/checkout@v4
- name: swift test
run: swift test
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SAM custom-runtime build target for the Swift Lambda (provided.al2).
# SAM custom-runtime build target for the Swift Lambda (provided.al2023).
# `sam build` invokes `make build-NZImageApiFunction` with ARTIFACTS_DIR set; it then
# zips ARTIFACTS_DIR itself (no package.sh needed). scripts/build.sh cross-compiles a
# static arm64 Amazon Linux 2 binary in Docker (pinned --platform linux/arm64 to match
# static arm64 Amazon Linux 2023 binary in Docker (pinned --platform linux/arm64 to match
# Architectures: [arm64] in template.yaml).
build-NZImageApiFunction:
./scripts/build.sh
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ The deployment script handles building with Docker and packaging with UPX:

**Manual build steps:**
```bash
# 1. Build using Docker (compiles for Amazon Linux 2)
# 1. Build using Docker (compiles for Amazon Linux 2023)
./scripts/build.sh

# 2. Package with UPX compression
Expand All @@ -269,7 +269,7 @@ The deployment script handles building with Docker and packaging with UPX:
2. Select your function (or create new)
3. Upload `.build/lambda/NZImageApiLambda/lambda.zip`
4. Configure:
- Runtime: `provided.al2` (Amazon Linux 2)
- Runtime: `provided.al2023` (Amazon Linux 2023)
- Handler: `bootstrap`
- Architecture: `arm64`
- Timeout: 60 seconds
Expand All @@ -292,7 +292,7 @@ aws lambda update-function-configuration \
```bash
aws lambda create-function \
--function-name NZImageApiLambda \
--runtime provided.al2 \
--runtime provided.al2023 \
--role YOUR_LAMBDA_ROLE_ARN \
--handler bootstrap \
--zip-file fileb://.build/lambda/NZImageApiLambda/lambda.zip \
Expand Down Expand Up @@ -370,8 +370,8 @@ The design rationale (why free static secrets instead of Amazon Cognito) is reco
## Build Details

The build process:
- Uses Docker with `swift:6.0-amazonlinux2` image
- Compiles for AWS Lambda's Amazon Linux 2 runtime
- Uses Docker with `swift:6.3-amazonlinux2023` image
- Compiles for AWS Lambda's Amazon Linux 2023 runtime
- Statically links the Swift standard library
- Compresses the binary with UPX (~70% size reduction: 106MB → 32MB)
- Creates a `bootstrap` executable (required by AWS custom runtime)
18 changes: 15 additions & 3 deletions Sources/Testing/CollectionLister/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,25 @@
// Lists all image collections from Digital NZ and their counts
//

// Must be the first import: Glibc's stdout/stderr aren't marked Sendable, so Swift 6 strict
// concurrency flags any reference to them. @preconcurrency suppresses that, but only if this
// import is resolved before Foundation's own (non-preconcurrency) transitive import of Glibc —
// see https://github.com/swiftlang/swift/issues/77866.
#if canImport(Glibc)
@preconcurrency import Glibc
#endif

import Alamofire
import Foundation

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

// nonisolated(unsafe) because this is a new global of a non-Sendable type
// (UnsafeMutablePointer<FILE>); safe here since this CLI is single-threaded terminal I/O.
nonisolated(unsafe) private let standardError = stderr

// MARK: - Models

struct FacetsResponse: Codable {
Expand Down Expand Up @@ -47,7 +59,7 @@ struct CollectionListOutput: Codable {
struct CollectionListerApp {
static func main() async throws {
guard let apiKey = ProcessInfo.processInfo.environment["DIGITALNZ_API_KEY"] else {
fputs("Error: DIGITALNZ_API_KEY environment variable not set\n", stderr)
fputs("Error: DIGITALNZ_API_KEY environment variable not set\n", standardError)
exit(1)
}

Expand Down Expand Up @@ -75,12 +87,12 @@ struct CollectionListerApp {

// Check for API errors
if let errors = response.errors, !errors.isEmpty {
fputs("API returned errors: \(errors.joined(separator: ", "))\n", stderr)
fputs("API returned errors: \(errors.joined(separator: ", "))\n", standardError)
exit(1)
}

guard let facetDict = response.search?.facets?.primaryCollection else {
fputs("Error: No facet data returned from API\n", stderr)
fputs("Error: No facet data returned from API\n", standardError)
exit(1)
}

Expand Down
21 changes: 17 additions & 4 deletions Sources/Testing/CollectionReviewer/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
// Interactive review tool for all collections
//

// Must be the first import: Glibc's stdout/stderr aren't marked Sendable, so Swift 6 strict
// concurrency flags any reference to them. @preconcurrency suppresses that, but only if this
// import is resolved before Foundation's own (non-preconcurrency) transitive import of Glibc —
// see https://github.com/swiftlang/swift/issues/77866.
#if canImport(Glibc)
@preconcurrency import Glibc
#endif

import Foundation
import LambdaTesting
import Synchronization
Expand All @@ -13,6 +21,11 @@ import Synchronization
import FoundationNetworking
#endif

// nonisolated(unsafe) because these are new globals of a non-Sendable type
// (UnsafeMutablePointer<FILE>); safe here since this CLI is single-threaded terminal I/O.
nonisolated(unsafe) private let standardOutput = stdout
nonisolated(unsafe) private let standardError = stderr

// MARK: - Progress Tracking

struct ProgressState: Sendable {
Expand Down Expand Up @@ -229,7 +242,7 @@ func printClickableLink(_ url: URL, label: String) {

func getSelectionInput(prompt: String) -> SelectionStatus {
print(prompt, terminator: " ")
fflush(stdout)
fflush(standardOutput)

while true {
if let input = readLine()?.lowercased().trimmingCharacters(in: .whitespaces) {
Expand All @@ -249,13 +262,13 @@ func getSelectionInput(prompt: String) -> SelectionStatus {
}
}
print("Please enter y/n/u/m/s: ", terminator: "")
fflush(stdout)
fflush(standardOutput)
}
}

func getTextInput(prompt: String) -> String {
print(prompt, terminator: " ")
fflush(stdout)
fflush(standardOutput)
return readLine() ?? ""
}

Expand Down Expand Up @@ -297,7 +310,7 @@ struct CollectionReviewerApp {
progressState.withLock { $0.filePath = "\(currentDir)/Research/.collection-reviewer-progress" }

guard fileManager.fileExists(atPath: detailsPath) else {
fputs("Error: Could not find \(detailsPath)\n", stderr)
fputs("Error: Could not find \(detailsPath)\n", standardError)
exit(1)
}

Expand Down
26 changes: 23 additions & 3 deletions Sources/Testing/ImageResolutionChecker/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,30 @@
// Checks image resolutions for Digital NZ collections
//

// Must be the first import: Glibc's stdout/stderr aren't marked Sendable, so Swift 6 strict
// concurrency flags any reference to them. @preconcurrency suppresses that, but only if this
// import is resolved before Foundation's own (non-preconcurrency) transitive import of Glibc —
// see https://github.com/swiftlang/swift/issues/77866.
#if canImport(Glibc)
@preconcurrency import Glibc
#endif

import Alamofire
import Foundation
#if canImport(ImageIO)
import ImageIO
#endif
import OrderedCollections
import RichError

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

// nonisolated(unsafe) because this is a new global of a non-Sendable type
// (UnsafeMutablePointer<FILE>); safe here since this CLI is single-threaded terminal I/O.
nonisolated(unsafe) private let standardError = stderr

// MARK: - Models

struct NZRecordsResult: Codable, Sendable {
Expand Down Expand Up @@ -79,6 +93,7 @@ struct ResolutionCheckOutput: Codable {
// MARK: - Image Resolution Checker

func getImageResolution(from url: URL) async -> ImageResolution? {
#if canImport(ImageIO)
do {
let (data, _) = try await URLSession.shared.data(from: url)

Expand All @@ -94,6 +109,11 @@ func getImageResolution(from url: URL) async -> ImageResolution? {
} catch {
return nil
}
#else
// ImageIO is Darwin-only; this dev CLI tool isn't built/run on Linux (see CLAUDE.md's
// "Sources/Testing/*" CI exclusion), but Linux `swift test` still compiles this target.
return nil
#endif
}

func checkImageURL(_ url: URL?) async -> ImageCheckResult? {
Expand Down Expand Up @@ -172,20 +192,20 @@ struct ImageResolutionCheckerApp {
let arguments = CommandLine.arguments

guard arguments.count >= 2 else {
fputs("Usage: ImageResolutionChecker <collection_name>\n", stderr)
fputs("Usage: ImageResolutionChecker <collection_name>\n", standardError)
exit(1)
}

let collection = arguments[1]

guard let apiKey = ProcessInfo.processInfo.environment["DIGITALNZ_API_KEY"] else {
fputs("Error: DIGITALNZ_API_KEY environment variable not set\n", stderr)
fputs("Error: DIGITALNZ_API_KEY environment variable not set\n", standardError)
exit(1)
}

// Fetch a result from Digital NZ
guard let result = try await fetchDigitalNZResult(collection: collection, apiKey: apiKey) else {
fputs("Error: No results found for collection '\(collection)'\n", stderr)
fputs("Error: No results found for collection '\(collection)'\n", standardError)
exit(1)
}

Expand Down
3 changes: 3 additions & 0 deletions Sources/Testing/LambdaTesting/LambdaTesting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import Darwin
#else
import Glibc
#endif
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

// MARK: - Lambda Response

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import Foundation
import XCTest
@testable import NZImageApiLambda
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

/// Stands in for the real network-backed `ValidatedRequestManager`. Returns a canned
/// `NZRecordsResponse` for both the initial (`per_page=0`, count-only) and secondary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
// testable without any actual network request.
//

import Foundation
import XCTest
@testable import NZImageApiLambda
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

final class NetworkRequestManagerValidationTests: XCTestCase {
private func response(statusCode: Int, contentType: String?) -> HTTPURLResponse {
Expand Down
6 changes: 3 additions & 3 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ echo "Cleaning previous build artifacts..."
rm -rf .build/checkouts .build/repositories .build/workspace-state.json

# Build using Docker
echo "Building Lambda for Amazon Linux 2..."
echo "Building Lambda for Amazon Linux 2023..."
docker run \
--rm \
--platform linux/arm64 \
--volume "$(pwd)/:/src" \
--workdir "/src/" \
swift:6.3-amazonlinux2 \
/bin/bash -c "yum -y update; yum -y install openssl openssl-devel -y; swift build --product NZImageApiLambda -c release -Xswiftc -static-stdlib"
swift:6.3-amazonlinux2023 \
/bin/bash -c "dnf -y install openssl-devel; swift build --product NZImageApiLambda -c release -Xswiftc -static-stdlib"
4 changes: 2 additions & 2 deletions template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,13 @@ Resources:
AlarmActions:
- !Ref AlarmTopic

# --- Swift NZ Image API (provided.al2 custom runtime, built via the Makefile) -------
# --- Swift NZ Image API (provided.al2023 custom runtime, built via the Makefile) -------
NZImageApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: bootstrap
Runtime: provided.al2
Runtime: provided.al2023
Architectures:
- arm64
MemorySize: 512
Expand Down
Loading