@appliance.sh/infra 1.19.1 → 1.21.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@appliance.sh/infra",
3
- "version": "1.19.1",
3
+ "version": "1.21.0",
4
4
  "description": "Deploy the Appliance Infrastructure",
5
5
  "repository": "https://github.com/appliance-sh/appliance.sh",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "dev:setup": "npm link"
24
24
  },
25
25
  "dependencies": {
26
- "@appliance.sh/sdk": "1.19.1",
26
+ "@appliance.sh/sdk": "1.21.0",
27
27
  "@pulumi/aws": "^7.16.0",
28
28
  "@pulumi/aws-native": "^1.48.0",
29
29
  "@pulumi/awsx": "^3.1.0",
@@ -14,6 +14,16 @@ export interface PulumiResult {
14
14
  stackName: string;
15
15
  }
16
16
 
17
+ export interface ResolvedBuildParams {
18
+ imageUri?: string;
19
+ codeS3Key?: string;
20
+ runtime?: string;
21
+ handler?: string;
22
+ layers?: string[];
23
+ architectures?: string[];
24
+ environment?: Record<string, string>;
25
+ }
26
+
17
27
  export interface ApplianceDeploymentServiceOptions {
18
28
  baseConfig?: ApplianceBaseConfig;
19
29
  }
@@ -32,7 +42,7 @@ export class ApplianceDeploymentService {
32
42
  this.region = this.baseConfig?.aws.region || 'us-east-1';
33
43
  }
34
44
 
35
- private inlineProgram(stackName: string, metadata?: ApplianceStackMetadata) {
45
+ private inlineProgram(stackName: string, metadata?: ApplianceStackMetadata, build?: ResolvedBuildParams) {
36
46
  return async () => {
37
47
  if (!this.baseConfig) {
38
48
  throw new Error('Missing base config');
@@ -58,6 +68,13 @@ export class ApplianceDeploymentService {
58
68
  {
59
69
  metadata,
60
70
  config: this.baseConfig,
71
+ imageUri: build?.imageUri,
72
+ codeS3Key: build?.codeS3Key,
73
+ runtime: build?.runtime,
74
+ handler: build?.handler,
75
+ layers: build?.layers,
76
+ architectures: build?.architectures,
77
+ environment: build?.environment,
61
78
  },
62
79
  {
63
80
  globalProvider,
@@ -73,8 +90,12 @@ export class ApplianceDeploymentService {
73
90
  };
74
91
  }
75
92
 
76
- private async getOrCreateStack(stackName: string, metadata?: ApplianceStackMetadata): Promise<auto.Stack> {
77
- const program = this.inlineProgram(stackName, metadata);
93
+ private async getOrCreateStack(
94
+ stackName: string,
95
+ metadata?: ApplianceStackMetadata,
96
+ build?: ResolvedBuildParams
97
+ ): Promise<auto.Stack> {
98
+ const program = this.inlineProgram(stackName, metadata, build);
78
99
  const envVars: Record<string, string> = {
79
100
  AWS_REGION: this.region,
80
101
  };
@@ -112,8 +133,12 @@ export class ApplianceDeploymentService {
112
133
  return auto.Stack.createOrSelect(stackName, ws);
113
134
  }
114
135
 
115
- async deploy(stackName: string, metadata?: ApplianceStackMetadata): Promise<PulumiResult> {
116
- const stack = await this.getOrCreateStack(stackName, metadata);
136
+ async deploy(
137
+ stackName: string,
138
+ metadata?: ApplianceStackMetadata,
139
+ build?: ResolvedBuildParams
140
+ ): Promise<PulumiResult> {
141
+ const stack = await this.getOrCreateStack(stackName, metadata, build);
117
142
  const result = await stack.up({ onOutput: (m) => console.log(m) });
118
143
  const changes = result.summary.resourceChanges || {};
119
144
  const totalChanges = Object.entries(changes)
@@ -21,6 +21,7 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
21
21
  public readonly cloudfrontDistribution?: aws.cloudfront.Distribution;
22
22
 
23
23
  public readonly dataBucket: aws.s3.Bucket;
24
+ public readonly ecrRepository: aws.ecr.Repository;
24
25
  public readonly config;
25
26
 
26
27
  constructor(name: string, args: ApplianceBaseAwsPublicArgs, opts?: ApplianceBaseAwsPublicOpts) {
@@ -151,6 +152,35 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
151
152
  { parent: this, provider: opts?.provider }
152
153
  );
153
154
 
155
+ this.ecrRepository = new aws.ecr.Repository(
156
+ `${name}-ecr`,
157
+ {
158
+ name: name.replaceAll('.', '-'),
159
+ imageScanningConfiguration: { scanOnPush: true },
160
+ imageTagMutability: 'MUTABLE',
161
+ forceDelete: true,
162
+ },
163
+ { parent: this, provider: opts?.provider }
164
+ );
165
+
166
+ new aws.ecr.LifecyclePolicy(
167
+ `${name}-ecr-lifecycle`,
168
+ {
169
+ repository: this.ecrRepository.name,
170
+ policy: JSON.stringify({
171
+ rules: [
172
+ {
173
+ rulePriority: 1,
174
+ description: 'Keep last 50 images',
175
+ selection: { tagStatus: 'any', countType: 'imageCountMoreThan', countNumber: 50 },
176
+ action: { type: 'expire' },
177
+ },
178
+ ],
179
+ }),
180
+ },
181
+ { parent: this, provider: opts?.provider }
182
+ );
183
+
154
184
  const lambdaOrigin = new aws.lambda.CallbackFunction(
155
185
  `${name}-origin`,
156
186
  {
@@ -487,6 +517,7 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
487
517
  cloudfrontDistributionDomainName: this.cloudfrontDistribution.domainName,
488
518
  edgeRouterRoleArn: edgeRouterRole.arn,
489
519
  dataBucketName: this.dataBucket.bucket,
520
+ ecrRepositoryUrl: this.ecrRepository.repositoryUrl,
490
521
  },
491
522
  };
492
523
 
@@ -47,6 +47,13 @@ export interface ApplianceStackMetadata {
47
47
  export interface ApplianceStackArgs {
48
48
  metadata?: ApplianceStackMetadata;
49
49
  config: ApplianceBaseConfig;
50
+ imageUri?: string;
51
+ codeS3Key?: string;
52
+ runtime?: string;
53
+ handler?: string;
54
+ layers?: string[];
55
+ architectures?: string[];
56
+ environment?: Record<string, string>;
50
57
  }
51
58
 
52
59
  export interface ApplianceStackOpts extends pulumi.ComponentResourceOptions {
@@ -91,11 +98,50 @@ export class ApplianceStack extends pulumi.ComponentResource {
91
98
  tags: defaultTags,
92
99
  });
93
100
 
101
+ const policyStatements = [
102
+ { Effect: 'Allow' as const, Action: 'logs:CreateLogGroup', Resource: '*' },
103
+ { Effect: 'Allow' as const, Action: 'logs:CreateLogStream', Resource: '*' },
104
+ { Effect: 'Allow' as const, Action: 'logs:PutLogEvents', Resource: '*' },
105
+ ];
106
+
107
+ if (args.imageUri) {
108
+ policyStatements.push(
109
+ {
110
+ Effect: 'Allow' as const,
111
+ Action: 'ecr:GetDownloadUrlForLayer',
112
+ Resource: '*',
113
+ },
114
+ {
115
+ Effect: 'Allow' as const,
116
+ Action: 'ecr:BatchGetImage',
117
+ Resource: '*',
118
+ },
119
+ {
120
+ Effect: 'Allow' as const,
121
+ Action: 'ecr:BatchCheckLayerAvailability',
122
+ Resource: '*',
123
+ },
124
+ {
125
+ Effect: 'Allow' as const,
126
+ Action: 'ecr:GetAuthorizationToken',
127
+ Resource: '*',
128
+ }
129
+ );
130
+ }
131
+
132
+ if (args.codeS3Key && args.config.aws.dataBucketName) {
133
+ policyStatements.push({
134
+ Effect: 'Allow' as const,
135
+ Action: 's3:GetObject',
136
+ Resource: `arn:aws:s3:::${args.config.aws.dataBucketName}/${args.codeS3Key}`,
137
+ });
138
+ }
139
+
94
140
  this.lambdaRolePolicy = new aws.iam.Policy(`${rid}-policy`, {
95
141
  path: `/appliance/${name}/`,
96
142
  policy: {
97
143
  Version: '2012-10-17',
98
- Statement: [{ Effect: 'Allow', Action: 'logs:CreateLogGroup', Resource: '*' }],
144
+ Statement: policyStatements,
99
145
  },
100
146
  });
101
147
 
@@ -104,17 +150,51 @@ export class ApplianceStack extends pulumi.ComponentResource {
104
150
  policyArn: this.lambdaRolePolicy.arn,
105
151
  });
106
152
 
107
- this.lambda = new aws.lambda.CallbackFunction(
108
- `${rid}-handler`,
109
- {
110
- runtime: 'nodejs22.x',
111
- callback: async () => {
112
- return { statusCode: 200, body: JSON.stringify({ message: 'Hello world!' }) };
153
+ if (args.imageUri) {
154
+ this.lambda = new aws.lambda.Function(
155
+ `${rid}-handler`,
156
+ {
157
+ packageType: 'Image',
158
+ imageUri: args.imageUri,
159
+ role: this.lambdaRole.arn,
160
+ timeout: 30,
161
+ memorySize: 512,
162
+ tags: defaultTags,
113
163
  },
114
- tags: defaultTags,
115
- },
116
- defaultOpts
117
- );
164
+ defaultOpts
165
+ );
166
+ } else if (args.codeS3Key && args.config.aws.dataBucketName) {
167
+ this.lambda = new aws.lambda.Function(
168
+ `${rid}-handler`,
169
+ {
170
+ packageType: 'Zip',
171
+ runtime: args.runtime ?? 'nodejs22.x',
172
+ handler: args.handler ?? 'index.handler',
173
+ s3Bucket: args.config.aws.dataBucketName,
174
+ s3Key: args.codeS3Key,
175
+ role: this.lambdaRole.arn,
176
+ timeout: 30,
177
+ memorySize: 512,
178
+ layers: args.layers,
179
+ architectures: args.architectures,
180
+ environment: args.environment ? { variables: args.environment } : undefined,
181
+ tags: defaultTags,
182
+ },
183
+ defaultOpts
184
+ );
185
+ } else {
186
+ this.lambda = new aws.lambda.CallbackFunction(
187
+ `${rid}-handler`,
188
+ {
189
+ runtime: 'nodejs22.x',
190
+ callback: async () => {
191
+ return { statusCode: 200, body: JSON.stringify({ message: 'Hello world!' }) };
192
+ },
193
+ tags: defaultTags,
194
+ },
195
+ defaultOpts
196
+ );
197
+ }
118
198
 
119
199
  // lambda url
120
200
  this.lambdaUrl = new aws.lambda.FunctionUrl(