@appliance.sh/infra 1.17.0 → 1.18.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
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appliance.sh/infra",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Deploy the Appliance Infrastructure",
|
|
5
5
|
"repository": "https://github.com/appliance-sh/appliance.sh",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Eliot Lim",
|
|
8
|
-
"main": "
|
|
8
|
+
"main": "src/index.ts",
|
|
9
9
|
"types": "dist/lib/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
19
19
|
"build": "tsc",
|
|
20
|
+
"clean": "rm -rf ./dist/",
|
|
20
21
|
"deploy:entrypoint": "ts-node src/index.ts",
|
|
21
|
-
"deploy": "pulumi up",
|
|
22
|
+
"deploy": "npm run clean && pulumi up && npm run build",
|
|
22
23
|
"dev:setup": "npm link"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@appliance.sh/sdk": "1.
|
|
26
|
+
"@appliance.sh/sdk": "1.18.0",
|
|
26
27
|
"@pulumi/aws": "^7.16.0",
|
|
27
28
|
"@pulumi/aws-native": "^1.48.0",
|
|
28
29
|
"@pulumi/awsx": "^3.1.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as auto from '@pulumi/pulumi/automation';
|
|
2
2
|
import * as aws from '@pulumi/aws';
|
|
3
3
|
import * as awsNative from '@pulumi/aws-native';
|
|
4
|
-
import { ApplianceStack } from './aws/ApplianceStack';
|
|
4
|
+
import { ApplianceStack, toResourceId } from './aws/ApplianceStack';
|
|
5
5
|
import { applianceBaseConfig, ApplianceBaseConfig } from '@appliance.sh/sdk';
|
|
6
6
|
|
|
7
7
|
export type PulumiAction = 'deploy' | 'destroy';
|
|
@@ -32,32 +32,31 @@ export class ApplianceDeploymentService {
|
|
|
32
32
|
this.region = this.baseConfig?.aws.region || 'us-east-1';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
private inlineProgram() {
|
|
35
|
+
private inlineProgram(stackName: string) {
|
|
36
36
|
return async () => {
|
|
37
|
-
const name = 'appliance';
|
|
38
|
-
|
|
39
37
|
if (!this.baseConfig) {
|
|
40
38
|
throw new Error('Missing base config');
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
const
|
|
41
|
+
const rid = toResourceId(stackName);
|
|
42
|
+
const regionalProvider = new aws.Provider(`${rid}-regional`, {
|
|
44
43
|
region: (this.baseConfig?.aws.region as aws.Region) ?? 'ap-southeast-1',
|
|
45
44
|
});
|
|
46
|
-
const globalProvider = new aws.Provider(`${
|
|
45
|
+
const globalProvider = new aws.Provider(`${rid}-global`, {
|
|
47
46
|
region: 'us-east-1',
|
|
48
47
|
});
|
|
49
|
-
const nativeRegionalProvider = new awsNative.Provider(`${
|
|
48
|
+
const nativeRegionalProvider = new awsNative.Provider(`${rid}-native-regional`, {
|
|
50
49
|
region: (this.baseConfig?.aws.region as awsNative.Region) ?? 'ap-southeast-1',
|
|
51
50
|
});
|
|
52
51
|
|
|
53
|
-
const nativeGlobalProvider = new awsNative.Provider(`${
|
|
52
|
+
const nativeGlobalProvider = new awsNative.Provider(`${rid}-native-global`, {
|
|
54
53
|
region: 'us-east-1',
|
|
55
54
|
});
|
|
56
55
|
|
|
57
56
|
const applianceStack = new ApplianceStack(
|
|
58
|
-
|
|
57
|
+
stackName,
|
|
59
58
|
{
|
|
60
|
-
tags: { project:
|
|
59
|
+
tags: { project: stackName },
|
|
61
60
|
config: this.baseConfig,
|
|
62
61
|
},
|
|
63
62
|
{
|
|
@@ -75,7 +74,7 @@ export class ApplianceDeploymentService {
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
private async getOrCreateStack(stackName: string): Promise<auto.Stack> {
|
|
78
|
-
const program = this.inlineProgram();
|
|
77
|
+
const program = this.inlineProgram(stackName);
|
|
79
78
|
const envVars: Record<string, string> = {
|
|
80
79
|
AWS_REGION: this.region,
|
|
81
80
|
};
|
|
@@ -20,6 +20,7 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
|
|
|
20
20
|
public readonly certificateArn?: pulumi.Output<string>;
|
|
21
21
|
public readonly cloudfrontDistribution?: aws.cloudfront.Distribution;
|
|
22
22
|
|
|
23
|
+
public readonly dataBucket: aws.s3.Bucket;
|
|
23
24
|
public readonly config;
|
|
24
25
|
|
|
25
26
|
constructor(name: string, args: ApplianceBaseAwsPublicArgs, opts?: ApplianceBaseAwsPublicOpts) {
|
|
@@ -123,6 +124,33 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
|
|
|
123
124
|
{ parent: this, provider: opts?.provider }
|
|
124
125
|
);
|
|
125
126
|
|
|
127
|
+
this.dataBucket = new aws.s3.Bucket(
|
|
128
|
+
`${name}-data`,
|
|
129
|
+
{
|
|
130
|
+
acl: 'private',
|
|
131
|
+
forceDestroy: true,
|
|
132
|
+
},
|
|
133
|
+
{ parent: this, provider: opts?.provider }
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
new aws.s3.BucketVersioning(
|
|
137
|
+
`${name}-data-versioning`,
|
|
138
|
+
{
|
|
139
|
+
bucket: this.dataBucket.bucket,
|
|
140
|
+
versioningConfiguration: { status: 'Enabled' },
|
|
141
|
+
},
|
|
142
|
+
{ parent: this, provider: opts?.provider }
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
new aws.s3.BucketServerSideEncryptionConfiguration(
|
|
146
|
+
`${name}-data-sse`,
|
|
147
|
+
{
|
|
148
|
+
bucket: this.dataBucket.bucket,
|
|
149
|
+
rules: [{ applyServerSideEncryptionByDefault: { sseAlgorithm: 'AES256' } }],
|
|
150
|
+
},
|
|
151
|
+
{ parent: this, provider: opts?.provider }
|
|
152
|
+
);
|
|
153
|
+
|
|
126
154
|
const lambdaOrigin = new aws.lambda.CallbackFunction(
|
|
127
155
|
`${name}-origin`,
|
|
128
156
|
{
|
|
@@ -458,6 +486,7 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
|
|
|
458
486
|
cloudfrontDistributionId: this.cloudfrontDistribution.id,
|
|
459
487
|
cloudfrontDistributionDomainName: this.cloudfrontDistribution.domainName,
|
|
460
488
|
edgeRouterRoleArn: edgeRouterRole.arn,
|
|
489
|
+
dataBucketName: this.dataBucket.bucket,
|
|
461
490
|
},
|
|
462
491
|
};
|
|
463
492
|
|
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
import * as pulumi from '@pulumi/pulumi';
|
|
2
2
|
import * as aws from '@pulumi/aws';
|
|
3
3
|
import * as awsNative from '@pulumi/aws-native';
|
|
4
|
-
import { ApplianceBaseConfig } from '@appliance.sh/sdk';
|
|
4
|
+
import type { ApplianceBaseConfig } from '@appliance.sh/sdk';
|
|
5
|
+
import { createHash } from 'crypto';
|
|
6
|
+
|
|
7
|
+
// AWS resource name limits (IAM roles, Lambda functions) are 64 chars.
|
|
8
|
+
// Pulumi appends an 8-char suffix (-xxxxxxx). The longest resource
|
|
9
|
+
// suffix we add is "-handler" (8 chars). Budget: 64 - 8 - 8 = 48.
|
|
10
|
+
const MAX_RESOURCE_ID_LENGTH = 48;
|
|
11
|
+
|
|
12
|
+
// DNS labels (each segment between dots) are limited to 63 chars.
|
|
13
|
+
const MAX_DNS_LABEL_LENGTH = 63;
|
|
14
|
+
|
|
15
|
+
function truncateWithHash(name: string, maxLength: number): string {
|
|
16
|
+
if (name.length <= maxLength) return name;
|
|
17
|
+
const hash = createHash('sha256').update(name).digest('hex').slice(0, 7);
|
|
18
|
+
return `${name.slice(0, maxLength - 8)}-${hash}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Derive a short, deterministic resource ID from a stack name.
|
|
23
|
+
* If the name fits within the limit it is returned as-is.
|
|
24
|
+
* Otherwise it is truncated and a 7-char hash suffix is appended
|
|
25
|
+
* to preserve uniqueness.
|
|
26
|
+
*/
|
|
27
|
+
export function toResourceId(name: string): string {
|
|
28
|
+
return truncateWithHash(name, MAX_RESOURCE_ID_LENGTH);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Derive a DNS-safe label from a stack name (max 63 chars).
|
|
33
|
+
*/
|
|
34
|
+
export function toDnsLabel(name: string): string {
|
|
35
|
+
return truncateWithHash(name, MAX_DNS_LABEL_LENGTH);
|
|
36
|
+
}
|
|
5
37
|
|
|
6
38
|
export interface ApplianceStackArgs {
|
|
7
39
|
tags?: Record<string, string>;
|
|
@@ -25,17 +57,22 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
25
57
|
constructor(name: string, args: ApplianceStackArgs, opts: ApplianceStackOpts) {
|
|
26
58
|
super('appliance:aws:ApplianceStack', name, args, opts);
|
|
27
59
|
|
|
60
|
+
// Short ID for AWS resource names (subject to 64-char limits)
|
|
61
|
+
const rid = toResourceId(name);
|
|
62
|
+
// DNS-safe label (max 63 chars per label)
|
|
63
|
+
const dnsLabel = toDnsLabel(name);
|
|
64
|
+
|
|
28
65
|
const defaultOpts = { parent: this, provider: opts.provider };
|
|
29
66
|
const defaultNativeOpts = { parent: this, provider: opts.nativeProvider };
|
|
30
67
|
const defaultTags = { stack: name, managed: 'appliance', ...args.tags };
|
|
31
68
|
|
|
32
|
-
this.lambdaRole = new aws.iam.Role(`${
|
|
69
|
+
this.lambdaRole = new aws.iam.Role(`${rid}-role`, {
|
|
33
70
|
path: `/appliance/${name}/`,
|
|
34
71
|
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: 'lambda.amazonaws.com' }),
|
|
35
72
|
tags: defaultTags,
|
|
36
73
|
});
|
|
37
74
|
|
|
38
|
-
this.lambdaRolePolicy = new aws.iam.Policy(`${
|
|
75
|
+
this.lambdaRolePolicy = new aws.iam.Policy(`${rid}-policy`, {
|
|
39
76
|
path: `/appliance/${name}/`,
|
|
40
77
|
policy: {
|
|
41
78
|
Version: '2012-10-17',
|
|
@@ -43,13 +80,13 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
43
80
|
},
|
|
44
81
|
});
|
|
45
82
|
|
|
46
|
-
new aws.iam.RolePolicyAttachment(`${
|
|
83
|
+
new aws.iam.RolePolicyAttachment(`${rid}-role-policy-attachment`, {
|
|
47
84
|
role: this.lambdaRole.name,
|
|
48
85
|
policyArn: this.lambdaRolePolicy.arn,
|
|
49
86
|
});
|
|
50
87
|
|
|
51
88
|
this.lambda = new aws.lambda.CallbackFunction(
|
|
52
|
-
`${
|
|
89
|
+
`${rid}-handler`,
|
|
53
90
|
{
|
|
54
91
|
runtime: 'nodejs22.x',
|
|
55
92
|
callback: async () => {
|
|
@@ -62,7 +99,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
62
99
|
|
|
63
100
|
// lambda url
|
|
64
101
|
this.lambdaUrl = new aws.lambda.FunctionUrl(
|
|
65
|
-
`${
|
|
102
|
+
`${rid}-url`,
|
|
66
103
|
{
|
|
67
104
|
functionName: this.lambda.name,
|
|
68
105
|
authorizationType: args.config.aws.cloudfrontDistributionId ? 'AWS_IAM' : 'NONE',
|
|
@@ -70,11 +107,12 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
70
107
|
defaultOpts
|
|
71
108
|
);
|
|
72
109
|
|
|
73
|
-
|
|
110
|
+
// DNS uses the full stack name, not the truncated resource ID
|
|
111
|
+
this.dnsRecord = pulumi.interpolate`${dnsLabel}.${args.config.domainName ?? ''}`;
|
|
74
112
|
|
|
75
113
|
if (args.config.aws.cloudfrontDistributionId) {
|
|
76
114
|
new aws.lambda.Permission(
|
|
77
|
-
`${
|
|
115
|
+
`${rid}-cf-invoke-url`,
|
|
78
116
|
{
|
|
79
117
|
function: this.lambda.name,
|
|
80
118
|
action: 'lambda:InvokeFunctionUrl',
|
|
@@ -92,7 +130,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
92
130
|
// The edge router role is the execution role of the Lambda@Edge function that signs requests
|
|
93
131
|
if (args.config.aws.edgeRouterRoleArn) {
|
|
94
132
|
new aws.lambda.Permission(
|
|
95
|
-
`${
|
|
133
|
+
`${rid}-edge-invoke-url`,
|
|
96
134
|
{
|
|
97
135
|
function: this.lambda.name,
|
|
98
136
|
action: 'lambda:InvokeFunctionUrl',
|
|
@@ -104,7 +142,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
104
142
|
);
|
|
105
143
|
|
|
106
144
|
new awsNative.lambda.Permission(
|
|
107
|
-
`${
|
|
145
|
+
`${rid}-edge-invoke`,
|
|
108
146
|
{
|
|
109
147
|
action: 'lambda:InvokeFunction',
|
|
110
148
|
principal: args.config.aws.edgeRouterRoleArn,
|
|
@@ -116,7 +154,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
116
154
|
}
|
|
117
155
|
} else {
|
|
118
156
|
new aws.lambda.Permission(
|
|
119
|
-
`${
|
|
157
|
+
`${rid}-public-invoke-url`,
|
|
120
158
|
{
|
|
121
159
|
function: this.lambda.name,
|
|
122
160
|
action: 'lambda:InvokeFunctionUrl',
|
|
@@ -130,7 +168,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
130
168
|
|
|
131
169
|
if (args.config.aws.cloudfrontDistributionId && args.config.aws.cloudfrontDistributionDomainName) {
|
|
132
170
|
new awsNative.lambda.Permission(
|
|
133
|
-
`${
|
|
171
|
+
`${rid}-cf-invoke`,
|
|
134
172
|
{
|
|
135
173
|
action: 'lambda:InvokeFunction',
|
|
136
174
|
principal: 'cloudfront.amazonaws.com',
|
|
@@ -144,10 +182,10 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
144
182
|
);
|
|
145
183
|
|
|
146
184
|
new aws.route53.Record(
|
|
147
|
-
`${
|
|
185
|
+
`${rid}-cname`,
|
|
148
186
|
{
|
|
149
187
|
zoneId: args.config.aws.zoneId,
|
|
150
|
-
name: pulumi.interpolate`${
|
|
188
|
+
name: pulumi.interpolate`${dnsLabel}.${args.config.domainName ?? ''}`,
|
|
151
189
|
type: 'CNAME',
|
|
152
190
|
ttl: 60,
|
|
153
191
|
records: [args.config.aws.cloudfrontDistributionDomainName],
|
|
@@ -156,10 +194,10 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
156
194
|
);
|
|
157
195
|
|
|
158
196
|
new aws.route53.Record(
|
|
159
|
-
`${
|
|
197
|
+
`${rid}-txt`,
|
|
160
198
|
{
|
|
161
199
|
zoneId: args.config.aws.zoneId,
|
|
162
|
-
name: pulumi.interpolate`origin.${
|
|
200
|
+
name: pulumi.interpolate`origin.${dnsLabel}.${args.config.domainName ?? ''}`,
|
|
163
201
|
type: 'TXT',
|
|
164
202
|
ttl: 60,
|
|
165
203
|
records: [this.lambdaUrl.functionUrl],
|