@appliance.sh/infra 1.17.0 → 1.19.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.19.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.19.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, ApplianceStackMetadata, 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, metadata?: ApplianceStackMetadata) {
|
|
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
|
-
|
|
59
|
+
metadata,
|
|
61
60
|
config: this.baseConfig,
|
|
62
61
|
},
|
|
63
62
|
{
|
|
@@ -74,8 +73,8 @@ export class ApplianceDeploymentService {
|
|
|
74
73
|
};
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
private async getOrCreateStack(stackName: string): Promise<auto.Stack> {
|
|
78
|
-
const program = this.inlineProgram();
|
|
76
|
+
private async getOrCreateStack(stackName: string, metadata?: ApplianceStackMetadata): Promise<auto.Stack> {
|
|
77
|
+
const program = this.inlineProgram(stackName, metadata);
|
|
79
78
|
const envVars: Record<string, string> = {
|
|
80
79
|
AWS_REGION: this.region,
|
|
81
80
|
};
|
|
@@ -113,8 +112,8 @@ export class ApplianceDeploymentService {
|
|
|
113
112
|
return auto.Stack.createOrSelect(stackName, ws);
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
async deploy(stackName
|
|
117
|
-
const stack = await this.getOrCreateStack(stackName);
|
|
115
|
+
async deploy(stackName: string, metadata?: ApplianceStackMetadata): Promise<PulumiResult> {
|
|
116
|
+
const stack = await this.getOrCreateStack(stackName, metadata);
|
|
118
117
|
const result = await stack.up({ onOutput: (m) => console.log(m) });
|
|
119
118
|
const changes = result.summary.resourceChanges || {};
|
|
120
119
|
const totalChanges = Object.entries(changes)
|
|
@@ -130,7 +129,7 @@ export class ApplianceDeploymentService {
|
|
|
130
129
|
};
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
async destroy(stackName
|
|
132
|
+
async destroy(stackName: string): Promise<PulumiResult> {
|
|
134
133
|
try {
|
|
135
134
|
const stack = await this.selectExistingStack(stackName);
|
|
136
135
|
await stack.destroy({ onOutput: (m) => console.log(m) });
|
|
@@ -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,10 +1,51 @@
|
|
|
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
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ApplianceStackMetadata {
|
|
39
|
+
projectId: string;
|
|
40
|
+
projectName: string;
|
|
41
|
+
environmentId: string;
|
|
42
|
+
environmentName: string;
|
|
43
|
+
deploymentId: string;
|
|
44
|
+
stackName: string;
|
|
45
|
+
}
|
|
5
46
|
|
|
6
47
|
export interface ApplianceStackArgs {
|
|
7
|
-
|
|
48
|
+
metadata?: ApplianceStackMetadata;
|
|
8
49
|
config: ApplianceBaseConfig;
|
|
9
50
|
}
|
|
10
51
|
|
|
@@ -25,17 +66,32 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
25
66
|
constructor(name: string, args: ApplianceStackArgs, opts: ApplianceStackOpts) {
|
|
26
67
|
super('appliance:aws:ApplianceStack', name, args, opts);
|
|
27
68
|
|
|
69
|
+
// Short ID for AWS resource names (subject to 64-char limits)
|
|
70
|
+
const rid = toResourceId(name);
|
|
71
|
+
// DNS-safe label (max 63 chars per label)
|
|
72
|
+
const dnsLabel = toDnsLabel(name);
|
|
73
|
+
|
|
28
74
|
const defaultOpts = { parent: this, provider: opts.provider };
|
|
29
75
|
const defaultNativeOpts = { parent: this, provider: opts.nativeProvider };
|
|
30
|
-
const defaultTags
|
|
76
|
+
const defaultTags: Record<string, string> = {
|
|
77
|
+
'appliance:managed': 'true',
|
|
78
|
+
'appliance:stack-name': name,
|
|
79
|
+
};
|
|
80
|
+
if (args.metadata) {
|
|
81
|
+
defaultTags['appliance:project-id'] = args.metadata.projectId;
|
|
82
|
+
defaultTags['appliance:project-name'] = args.metadata.projectName;
|
|
83
|
+
defaultTags['appliance:environment-id'] = args.metadata.environmentId;
|
|
84
|
+
defaultTags['appliance:environment-name'] = args.metadata.environmentName;
|
|
85
|
+
defaultTags['appliance:deployment-id'] = args.metadata.deploymentId;
|
|
86
|
+
}
|
|
31
87
|
|
|
32
|
-
this.lambdaRole = new aws.iam.Role(`${
|
|
88
|
+
this.lambdaRole = new aws.iam.Role(`${rid}-role`, {
|
|
33
89
|
path: `/appliance/${name}/`,
|
|
34
90
|
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: 'lambda.amazonaws.com' }),
|
|
35
91
|
tags: defaultTags,
|
|
36
92
|
});
|
|
37
93
|
|
|
38
|
-
this.lambdaRolePolicy = new aws.iam.Policy(`${
|
|
94
|
+
this.lambdaRolePolicy = new aws.iam.Policy(`${rid}-policy`, {
|
|
39
95
|
path: `/appliance/${name}/`,
|
|
40
96
|
policy: {
|
|
41
97
|
Version: '2012-10-17',
|
|
@@ -43,13 +99,13 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
43
99
|
},
|
|
44
100
|
});
|
|
45
101
|
|
|
46
|
-
new aws.iam.RolePolicyAttachment(`${
|
|
102
|
+
new aws.iam.RolePolicyAttachment(`${rid}-role-policy-attachment`, {
|
|
47
103
|
role: this.lambdaRole.name,
|
|
48
104
|
policyArn: this.lambdaRolePolicy.arn,
|
|
49
105
|
});
|
|
50
106
|
|
|
51
107
|
this.lambda = new aws.lambda.CallbackFunction(
|
|
52
|
-
`${
|
|
108
|
+
`${rid}-handler`,
|
|
53
109
|
{
|
|
54
110
|
runtime: 'nodejs22.x',
|
|
55
111
|
callback: async () => {
|
|
@@ -62,7 +118,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
62
118
|
|
|
63
119
|
// lambda url
|
|
64
120
|
this.lambdaUrl = new aws.lambda.FunctionUrl(
|
|
65
|
-
`${
|
|
121
|
+
`${rid}-url`,
|
|
66
122
|
{
|
|
67
123
|
functionName: this.lambda.name,
|
|
68
124
|
authorizationType: args.config.aws.cloudfrontDistributionId ? 'AWS_IAM' : 'NONE',
|
|
@@ -70,11 +126,12 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
70
126
|
defaultOpts
|
|
71
127
|
);
|
|
72
128
|
|
|
73
|
-
|
|
129
|
+
// DNS uses the full stack name, not the truncated resource ID
|
|
130
|
+
this.dnsRecord = pulumi.interpolate`${dnsLabel}.${args.config.domainName ?? ''}`;
|
|
74
131
|
|
|
75
132
|
if (args.config.aws.cloudfrontDistributionId) {
|
|
76
133
|
new aws.lambda.Permission(
|
|
77
|
-
`${
|
|
134
|
+
`${rid}-cf-invoke-url`,
|
|
78
135
|
{
|
|
79
136
|
function: this.lambda.name,
|
|
80
137
|
action: 'lambda:InvokeFunctionUrl',
|
|
@@ -92,7 +149,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
92
149
|
// The edge router role is the execution role of the Lambda@Edge function that signs requests
|
|
93
150
|
if (args.config.aws.edgeRouterRoleArn) {
|
|
94
151
|
new aws.lambda.Permission(
|
|
95
|
-
`${
|
|
152
|
+
`${rid}-edge-invoke-url`,
|
|
96
153
|
{
|
|
97
154
|
function: this.lambda.name,
|
|
98
155
|
action: 'lambda:InvokeFunctionUrl',
|
|
@@ -104,7 +161,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
104
161
|
);
|
|
105
162
|
|
|
106
163
|
new awsNative.lambda.Permission(
|
|
107
|
-
`${
|
|
164
|
+
`${rid}-edge-invoke`,
|
|
108
165
|
{
|
|
109
166
|
action: 'lambda:InvokeFunction',
|
|
110
167
|
principal: args.config.aws.edgeRouterRoleArn,
|
|
@@ -116,7 +173,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
116
173
|
}
|
|
117
174
|
} else {
|
|
118
175
|
new aws.lambda.Permission(
|
|
119
|
-
`${
|
|
176
|
+
`${rid}-public-invoke-url`,
|
|
120
177
|
{
|
|
121
178
|
function: this.lambda.name,
|
|
122
179
|
action: 'lambda:InvokeFunctionUrl',
|
|
@@ -130,7 +187,7 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
130
187
|
|
|
131
188
|
if (args.config.aws.cloudfrontDistributionId && args.config.aws.cloudfrontDistributionDomainName) {
|
|
132
189
|
new awsNative.lambda.Permission(
|
|
133
|
-
`${
|
|
190
|
+
`${rid}-cf-invoke`,
|
|
134
191
|
{
|
|
135
192
|
action: 'lambda:InvokeFunction',
|
|
136
193
|
principal: 'cloudfront.amazonaws.com',
|
|
@@ -144,10 +201,10 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
144
201
|
);
|
|
145
202
|
|
|
146
203
|
new aws.route53.Record(
|
|
147
|
-
`${
|
|
204
|
+
`${rid}-cname`,
|
|
148
205
|
{
|
|
149
206
|
zoneId: args.config.aws.zoneId,
|
|
150
|
-
name: pulumi.interpolate`${
|
|
207
|
+
name: pulumi.interpolate`${dnsLabel}.${args.config.domainName ?? ''}`,
|
|
151
208
|
type: 'CNAME',
|
|
152
209
|
ttl: 60,
|
|
153
210
|
records: [args.config.aws.cloudfrontDistributionDomainName],
|
|
@@ -156,10 +213,10 @@ export class ApplianceStack extends pulumi.ComponentResource {
|
|
|
156
213
|
);
|
|
157
214
|
|
|
158
215
|
new aws.route53.Record(
|
|
159
|
-
`${
|
|
216
|
+
`${rid}-txt`,
|
|
160
217
|
{
|
|
161
218
|
zoneId: args.config.aws.zoneId,
|
|
162
|
-
name: pulumi.interpolate`origin.${
|
|
219
|
+
name: pulumi.interpolate`origin.${dnsLabel}.${args.config.domainName ?? ''}`,
|
|
163
220
|
type: 'TXT',
|
|
164
221
|
ttl: 60,
|
|
165
222
|
records: [this.lambdaUrl.functionUrl],
|