@appliance.sh/infra 1.19.0 → 1.20.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.0",
3
+ "version": "1.20.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.0",
26
+ "@appliance.sh/sdk": "1.20.0",
27
27
  "@pulumi/aws": "^7.16.0",
28
28
  "@pulumi/aws-native": "^1.48.0",
29
29
  "@pulumi/awsx": "^3.1.0",
@@ -32,7 +32,11 @@ export class ApplianceDeploymentService {
32
32
  this.region = this.baseConfig?.aws.region || 'us-east-1';
33
33
  }
34
34
 
35
- private inlineProgram(stackName: string, metadata?: ApplianceStackMetadata) {
35
+ private inlineProgram(
36
+ stackName: string,
37
+ metadata?: ApplianceStackMetadata,
38
+ build?: { imageUri?: string; codeS3Key?: string }
39
+ ) {
36
40
  return async () => {
37
41
  if (!this.baseConfig) {
38
42
  throw new Error('Missing base config');
@@ -58,6 +62,8 @@ export class ApplianceDeploymentService {
58
62
  {
59
63
  metadata,
60
64
  config: this.baseConfig,
65
+ imageUri: build?.imageUri,
66
+ codeS3Key: build?.codeS3Key,
61
67
  },
62
68
  {
63
69
  globalProvider,
@@ -73,8 +79,12 @@ export class ApplianceDeploymentService {
73
79
  };
74
80
  }
75
81
 
76
- private async getOrCreateStack(stackName: string, metadata?: ApplianceStackMetadata): Promise<auto.Stack> {
77
- const program = this.inlineProgram(stackName, metadata);
82
+ private async getOrCreateStack(
83
+ stackName: string,
84
+ metadata?: ApplianceStackMetadata,
85
+ build?: { imageUri?: string; codeS3Key?: string }
86
+ ): Promise<auto.Stack> {
87
+ const program = this.inlineProgram(stackName, metadata, build);
78
88
  const envVars: Record<string, string> = {
79
89
  AWS_REGION: this.region,
80
90
  };
@@ -112,8 +122,12 @@ export class ApplianceDeploymentService {
112
122
  return auto.Stack.createOrSelect(stackName, ws);
113
123
  }
114
124
 
115
- async deploy(stackName: string, metadata?: ApplianceStackMetadata): Promise<PulumiResult> {
116
- const stack = await this.getOrCreateStack(stackName, metadata);
125
+ async deploy(
126
+ stackName: string,
127
+ metadata?: ApplianceStackMetadata,
128
+ build?: { imageUri?: string; codeS3Key?: string }
129
+ ): Promise<PulumiResult> {
130
+ const stack = await this.getOrCreateStack(stackName, metadata, build);
117
131
  const result = await stack.up({ onOutput: (m) => console.log(m) });
118
132
  const changes = result.summary.resourceChanges || {};
119
133
  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,8 @@ export interface ApplianceStackMetadata {
47
47
  export interface ApplianceStackArgs {
48
48
  metadata?: ApplianceStackMetadata;
49
49
  config: ApplianceBaseConfig;
50
+ imageUri?: string;
51
+ codeS3Key?: string;
50
52
  }
51
53
 
52
54
  export interface ApplianceStackOpts extends pulumi.ComponentResourceOptions {
@@ -91,11 +93,50 @@ export class ApplianceStack extends pulumi.ComponentResource {
91
93
  tags: defaultTags,
92
94
  });
93
95
 
96
+ const policyStatements = [
97
+ { Effect: 'Allow' as const, Action: 'logs:CreateLogGroup', Resource: '*' },
98
+ { Effect: 'Allow' as const, Action: 'logs:CreateLogStream', Resource: '*' },
99
+ { Effect: 'Allow' as const, Action: 'logs:PutLogEvents', Resource: '*' },
100
+ ];
101
+
102
+ if (args.imageUri) {
103
+ policyStatements.push(
104
+ {
105
+ Effect: 'Allow' as const,
106
+ Action: 'ecr:GetDownloadUrlForLayer',
107
+ Resource: '*',
108
+ },
109
+ {
110
+ Effect: 'Allow' as const,
111
+ Action: 'ecr:BatchGetImage',
112
+ Resource: '*',
113
+ },
114
+ {
115
+ Effect: 'Allow' as const,
116
+ Action: 'ecr:BatchCheckLayerAvailability',
117
+ Resource: '*',
118
+ },
119
+ {
120
+ Effect: 'Allow' as const,
121
+ Action: 'ecr:GetAuthorizationToken',
122
+ Resource: '*',
123
+ }
124
+ );
125
+ }
126
+
127
+ if (args.codeS3Key && args.config.aws.dataBucketName) {
128
+ policyStatements.push({
129
+ Effect: 'Allow' as const,
130
+ Action: 's3:GetObject',
131
+ Resource: `arn:aws:s3:::${args.config.aws.dataBucketName}/${args.codeS3Key}`,
132
+ });
133
+ }
134
+
94
135
  this.lambdaRolePolicy = new aws.iam.Policy(`${rid}-policy`, {
95
136
  path: `/appliance/${name}/`,
96
137
  policy: {
97
138
  Version: '2012-10-17',
98
- Statement: [{ Effect: 'Allow', Action: 'logs:CreateLogGroup', Resource: '*' }],
139
+ Statement: policyStatements,
99
140
  },
100
141
  });
101
142
 
@@ -104,17 +145,48 @@ export class ApplianceStack extends pulumi.ComponentResource {
104
145
  policyArn: this.lambdaRolePolicy.arn,
105
146
  });
106
147
 
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!' }) };
148
+ if (args.imageUri) {
149
+ this.lambda = new aws.lambda.Function(
150
+ `${rid}-handler`,
151
+ {
152
+ packageType: 'Image',
153
+ imageUri: args.imageUri,
154
+ role: this.lambdaRole.arn,
155
+ timeout: 30,
156
+ memorySize: 512,
157
+ tags: defaultTags,
113
158
  },
114
- tags: defaultTags,
115
- },
116
- defaultOpts
117
- );
159
+ defaultOpts
160
+ );
161
+ } else if (args.codeS3Key && args.config.aws.dataBucketName) {
162
+ this.lambda = new aws.lambda.Function(
163
+ `${rid}-handler`,
164
+ {
165
+ packageType: 'Zip',
166
+ runtime: 'nodejs22.x',
167
+ handler: 'index.handler',
168
+ s3Bucket: args.config.aws.dataBucketName,
169
+ s3Key: args.codeS3Key,
170
+ role: this.lambdaRole.arn,
171
+ timeout: 30,
172
+ memorySize: 512,
173
+ tags: defaultTags,
174
+ },
175
+ defaultOpts
176
+ );
177
+ } else {
178
+ this.lambda = new aws.lambda.CallbackFunction(
179
+ `${rid}-handler`,
180
+ {
181
+ runtime: 'nodejs22.x',
182
+ callback: async () => {
183
+ return { statusCode: 200, body: JSON.stringify({ message: 'Hello world!' }) };
184
+ },
185
+ tags: defaultTags,
186
+ },
187
+ defaultOpts
188
+ );
189
+ }
118
190
 
119
191
  // lambda url
120
192
  this.lambdaUrl = new aws.lambda.FunctionUrl(