@appliance.sh/api-server 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/services/build.service.ts +67 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appliance.sh/api-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "Eliot Lim",
|
|
6
6
|
"repository": "https://github.com/appliance-sh/appliance.sh",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"test:e2e": "vitest run --config vitest.e2e.config.ts"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@appliance.sh/infra": "1.
|
|
23
|
-
"@appliance.sh/sdk": "1.
|
|
22
|
+
"@appliance.sh/infra": "1.22.0",
|
|
23
|
+
"@appliance.sh/sdk": "1.22.0",
|
|
24
24
|
"@aws-sdk/client-ecr": "^3.1005.0",
|
|
25
25
|
"@aws-sdk/client-s3": "^3.750.0",
|
|
26
26
|
"express": "^5.2.1"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
2
2
|
import { ECRClient, GetAuthorizationTokenCommand } from '@aws-sdk/client-ecr';
|
|
3
3
|
import { applianceBaseConfig, applianceInput } from '@appliance.sh/sdk';
|
|
4
|
-
import type { ApplianceContainer } from '@appliance.sh/sdk';
|
|
4
|
+
import type { ApplianceContainer, ApplianceFrameworkApp } from '@appliance.sh/sdk';
|
|
5
5
|
import { execSync } from 'child_process';
|
|
6
6
|
import * as fs from 'node:fs';
|
|
7
7
|
import * as path from 'node:path';
|
|
@@ -10,6 +10,11 @@ import * as os from 'node:os';
|
|
|
10
10
|
export interface ResolvedBuild {
|
|
11
11
|
imageUri?: string;
|
|
12
12
|
codeS3Key?: string;
|
|
13
|
+
runtime?: string;
|
|
14
|
+
handler?: string;
|
|
15
|
+
layers?: string[];
|
|
16
|
+
architectures?: string[];
|
|
17
|
+
environment?: Record<string, string>;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
function getBaseConfig() {
|
|
@@ -18,6 +23,23 @@ function getBaseConfig() {
|
|
|
18
23
|
return applianceBaseConfig.parse(JSON.parse(raw));
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
const LAMBDA_ADAPTER_LAYER: Record<string, string> = {
|
|
27
|
+
'linux/amd64': 'arn:aws:lambda:${region}:753240598075:layer:LambdaAdapterLayerX86:26',
|
|
28
|
+
'linux/arm64': 'arn:aws:lambda:${region}:753240598075:layer:LambdaAdapterLayerArm64:26',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const FRAMEWORK_RUNTIMES: Record<string, string> = {
|
|
32
|
+
node: 'nodejs22.x',
|
|
33
|
+
python: 'python3.13',
|
|
34
|
+
auto: 'nodejs22.x',
|
|
35
|
+
other: 'nodejs22.x',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const FRAMEWORK_ARCHITECTURES: Record<string, string> = {
|
|
39
|
+
'linux/amd64': 'x86_64',
|
|
40
|
+
'linux/arm64': 'arm64',
|
|
41
|
+
};
|
|
42
|
+
|
|
21
43
|
export class BuildService {
|
|
22
44
|
async resolve(buildId: string, tag: string): Promise<ResolvedBuild> {
|
|
23
45
|
const config = getBaseConfig();
|
|
@@ -55,8 +77,9 @@ export class BuildService {
|
|
|
55
77
|
|
|
56
78
|
if (manifest.type === 'container') {
|
|
57
79
|
return this.resolveContainer(tmpDir, manifest, tag, config);
|
|
80
|
+
} else if (manifest.type === 'framework') {
|
|
81
|
+
return this.resolveFramework(manifest, s3Key, config);
|
|
58
82
|
} else {
|
|
59
|
-
// For framework/other, the zip is already in S3 — just reference it
|
|
60
83
|
return { codeS3Key: s3Key };
|
|
61
84
|
}
|
|
62
85
|
} finally {
|
|
@@ -64,6 +87,43 @@ export class BuildService {
|
|
|
64
87
|
}
|
|
65
88
|
}
|
|
66
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Framework builds are fully pre-processed by the CLI (dependencies installed,
|
|
92
|
+
* run.sh generated). The server just resolves Lambda-specific params from the
|
|
93
|
+
* manifest metadata and points at the original uploaded zip.
|
|
94
|
+
*/
|
|
95
|
+
private resolveFramework(
|
|
96
|
+
manifest: ApplianceFrameworkApp,
|
|
97
|
+
s3Key: string,
|
|
98
|
+
config: ReturnType<typeof getBaseConfig>
|
|
99
|
+
): ResolvedBuild {
|
|
100
|
+
const port = manifest.port ?? 8080;
|
|
101
|
+
const framework = manifest.framework ?? 'auto';
|
|
102
|
+
const runtime = FRAMEWORK_RUNTIMES[framework] ?? FRAMEWORK_RUNTIMES['node'];
|
|
103
|
+
|
|
104
|
+
const platform = manifest.platform;
|
|
105
|
+
const layerArn = LAMBDA_ADAPTER_LAYER[platform]?.replace('${region}', config.aws.region);
|
|
106
|
+
if (!layerArn) throw new Error(`No Lambda Web Adapter layer for platform: ${platform}`);
|
|
107
|
+
const architecture = FRAMEWORK_ARCHITECTURES[platform];
|
|
108
|
+
if (!architecture) throw new Error(`No Lambda architecture for platform: ${platform}`);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
codeS3Key: s3Key,
|
|
112
|
+
runtime,
|
|
113
|
+
handler: 'run.sh',
|
|
114
|
+
layers: [layerArn],
|
|
115
|
+
architectures: [architecture],
|
|
116
|
+
environment: {
|
|
117
|
+
AWS_LAMBDA_EXEC_WRAPPER: '/opt/bootstrap',
|
|
118
|
+
AWS_LWA_PORT: String(port),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Container builds are fully pre-processed by the CLI (Lambda Web Adapter
|
|
125
|
+
* already injected). The server just loads the image and pushes it to ECR.
|
|
126
|
+
*/
|
|
67
127
|
private async resolveContainer(
|
|
68
128
|
tmpDir: string,
|
|
69
129
|
manifest: ApplianceContainer,
|
|
@@ -76,31 +136,12 @@ export class BuildService {
|
|
|
76
136
|
const imageTarPath = path.join(tmpDir, 'image.tar');
|
|
77
137
|
if (!fs.existsSync(imageTarPath)) throw new Error('Build missing image.tar');
|
|
78
138
|
|
|
79
|
-
// Load the
|
|
139
|
+
// Load the pre-built Lambda-ready image
|
|
80
140
|
const loadOutput = execSync(`docker load -i "${imageTarPath}"`, { encoding: 'utf-8' });
|
|
81
|
-
// Output is like "Loaded image: name:tag" or "Loaded image ID: sha256:abc..."
|
|
82
141
|
const loadedMatch = loadOutput.match(/Loaded image(?: ID)?:\s*(.+)/);
|
|
83
142
|
if (!loadedMatch) throw new Error(`Failed to parse docker load output: ${loadOutput}`);
|
|
84
143
|
const loadedImage = loadedMatch[1].trim();
|
|
85
144
|
|
|
86
|
-
// Wrap the image with the Lambda Web Adapter so the same plain HTTP
|
|
87
|
-
// container works on both Lambda and ECS/Fargate without any changes.
|
|
88
|
-
const lambdaImageName = `${manifest.name}-lambda`;
|
|
89
|
-
const wrapperDockerfile = path.join(tmpDir, 'Dockerfile.lambda');
|
|
90
|
-
fs.writeFileSync(
|
|
91
|
-
wrapperDockerfile,
|
|
92
|
-
[
|
|
93
|
-
`FROM --platform=${manifest.platform} public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 AS adapter`,
|
|
94
|
-
`FROM ${loadedImage}`,
|
|
95
|
-
`COPY --from=adapter /lambda-adapter /opt/extensions/lambda-adapter`,
|
|
96
|
-
`ENV AWS_LWA_PORT=${manifest.port}`,
|
|
97
|
-
].join('\n')
|
|
98
|
-
);
|
|
99
|
-
execSync(
|
|
100
|
-
`docker build --platform ${manifest.platform} --provenance=false -f "${wrapperDockerfile}" -t "${lambdaImageName}" "${tmpDir}"`,
|
|
101
|
-
{ stdio: 'pipe' }
|
|
102
|
-
);
|
|
103
|
-
|
|
104
145
|
// Auth with ECR
|
|
105
146
|
const ecr = new ECRClient({ region: config.aws.region });
|
|
106
147
|
const authResult = await ecr.send(new GetAuthorizationTokenCommand({}));
|
|
@@ -116,15 +157,15 @@ export class BuildService {
|
|
|
116
157
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
117
158
|
});
|
|
118
159
|
|
|
119
|
-
// Tag and push
|
|
160
|
+
// Tag and push
|
|
120
161
|
const remoteTag = `${ecrRepositoryUrl}:${tag}`;
|
|
121
|
-
execSync(`docker tag ${
|
|
122
|
-
execSync(`docker push ${remoteTag}`, { stdio: 'pipe' });
|
|
162
|
+
execSync(`docker tag "${loadedImage}" "${remoteTag}"`, { stdio: 'pipe' });
|
|
163
|
+
execSync(`docker push "${remoteTag}"`, { stdio: 'pipe' });
|
|
123
164
|
|
|
124
165
|
// Get digest
|
|
125
166
|
let imageUri: string;
|
|
126
167
|
try {
|
|
127
|
-
imageUri = execSync(`docker inspect --format='{{index .RepoDigests 0}}' ${remoteTag}`, {
|
|
168
|
+
imageUri = execSync(`docker inspect --format='{{index .RepoDigests 0}}' "${remoteTag}"`, {
|
|
128
169
|
encoding: 'utf-8',
|
|
129
170
|
}).trim();
|
|
130
171
|
} catch {
|