@kirschbaum-development/sst-laravel 0.2.11 → 0.2.14
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/laravel-sst.ts +775 -613
- package/package.json +1 -1
- package/src/remote-env-file.ts +110 -5
package/laravel-sst.ts
CHANGED
|
@@ -2,716 +2,878 @@
|
|
|
2
2
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as fs from 'fs';
|
|
5
|
-
import { Component } from
|
|
6
|
-
import { FunctionArgs } from
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
5
|
+
import { Component } from '../../../.sst/platform/src/components/component.js';
|
|
6
|
+
import { FunctionArgs } from '../../../.sst/platform/src/components/aws/function.js';
|
|
7
|
+
import {
|
|
8
|
+
ComponentResourceOptions,
|
|
9
|
+
Input as PulumiInput,
|
|
10
|
+
Output,
|
|
11
|
+
all,
|
|
12
|
+
output,
|
|
13
|
+
runtime,
|
|
14
|
+
} from '@pulumi/pulumi';
|
|
15
|
+
import { Input } from '../../../.sst/platform/src/components/input.js';
|
|
16
|
+
import { ClusterArgs } from '../../../.sst/platform/src/components/aws/cluster.js';
|
|
17
|
+
import { ServiceArgs } from '../../../.sst/platform/src/components/aws/service.js';
|
|
18
|
+
import { Dns } from '../../../.sst/platform/src/components/dns.js';
|
|
19
|
+
import {
|
|
20
|
+
applyLinkedResourcesEnv,
|
|
21
|
+
EnvCallback,
|
|
22
|
+
EnvCallbacks,
|
|
23
|
+
extractSecrets,
|
|
24
|
+
} from './src/laravel-env';
|
|
25
|
+
import { RemoteEnvVault, RemoteEnvVaultArgs } from './src/laravel-env-manager';
|
|
26
|
+
import { getPackagePath } from './src/config';
|
|
27
|
+
import { RemoteEnvFile } from './src/remote-env-file';
|
|
28
|
+
import { getSecretsFingerprint } from './src/secrets-manager';
|
|
17
29
|
|
|
18
30
|
// Re-export RemoteEnvVault for external use
|
|
19
31
|
export { RemoteEnvVault, RemoteEnvVaultArgs };
|
|
20
32
|
|
|
21
33
|
// duplicate from cluster.ts
|
|
22
|
-
type Port = `${number}/${
|
|
34
|
+
type Port = `${number}/${'http' | 'https' | 'tcp' | 'udp' | 'tcp_udp' | 'tls'}`;
|
|
23
35
|
|
|
24
36
|
type Ports = {
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
listen: Port;
|
|
38
|
+
forward: Port;
|
|
27
39
|
}[];
|
|
28
40
|
|
|
29
41
|
enum ImageType {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
Web = 'web',
|
|
43
|
+
Worker = 'worker',
|
|
44
|
+
Cli = 'cli',
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
export interface LaravelServiceArgs {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
architecture?: ServiceArgs['architecture'];
|
|
49
|
+
cpu?: ServiceArgs['cpu'];
|
|
50
|
+
memory?: ServiceArgs['memory'];
|
|
51
|
+
storage?: ServiceArgs['storage'];
|
|
52
|
+
loadBalancer?: ServiceArgs['loadBalancer'];
|
|
53
|
+
scaling?: ServiceArgs['scaling'];
|
|
54
|
+
logging?: ServiceArgs['logging'];
|
|
55
|
+
health?: ServiceArgs['health'];
|
|
56
|
+
executionRole?: ServiceArgs['executionRole'];
|
|
57
|
+
permissions?: ServiceArgs['permissions'];
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
export interface LaravelWebArgs extends LaravelServiceArgs {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Custom domain for the web layer. (if you don't provide a domain name, you will be able to use the load balancer domain for testing (http only))
|
|
63
|
+
*/
|
|
64
|
+
domain?: Input<
|
|
65
|
+
| string
|
|
66
|
+
| {
|
|
67
|
+
/**
|
|
68
|
+
* Domain name. You are able to use variables from the SST config file here.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```js
|
|
72
|
+
* domain: {
|
|
73
|
+
* name: `${$app.stage}.example.com`,
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
name: Input<string>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Certificate ARN. Use this in case you are manually setting up the SSL certificate.
|
|
81
|
+
* This is usually needed when your DNS is not in the same AWS account or is outside of AWS.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```js
|
|
85
|
+
* domain: {
|
|
86
|
+
* cert: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
cert?: Input<string>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* SST DNS configuration. You can use this configuration if your DNS is in Cloudflare or another AWS account.
|
|
94
|
+
*
|
|
95
|
+
* @see https://sst.dev/docs/component/cloudflare/dns/
|
|
96
|
+
* @see https://sst.dev/docs/component/aws/dns/
|
|
97
|
+
* @example
|
|
98
|
+
* ```js
|
|
99
|
+
* domain: {
|
|
100
|
+
* dns: sst.cloudflare.dns(),
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
dns?: Input<false | (Dns & {})>;
|
|
105
|
+
}
|
|
106
|
+
>;
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
export interface LaravelWorkerConfig extends LaravelServiceArgs {
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Running horizon?
|
|
101
|
-
*/
|
|
102
|
-
horizon?: Input<boolean>;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Running scheduler?
|
|
106
|
-
*/
|
|
107
|
-
scheduler?: Input<boolean>;
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Multiple tasks can be run in the worker.
|
|
111
|
-
*/
|
|
112
|
-
tasks?: Input<{
|
|
113
|
-
[key: string]: Input<{
|
|
114
|
-
command: Input<string>;
|
|
115
|
-
dependencies?: Input<string[]>;
|
|
116
|
-
}>
|
|
117
|
-
}>
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface LaravelArgs extends ClusterArgs {
|
|
121
|
-
// dev?: false | DevArgs["dev"];
|
|
122
|
-
path?: Input<string>;
|
|
123
|
-
link?: Array<
|
|
124
|
-
| any
|
|
125
|
-
| {
|
|
126
|
-
resource: any;
|
|
127
|
-
environment?: EnvCallback;
|
|
128
|
-
}
|
|
129
|
-
>;
|
|
130
|
-
|
|
131
|
-
permissions?: Array<{
|
|
132
|
-
actions: string[];
|
|
133
|
-
resources: string[];
|
|
134
|
-
}>;
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* If enabled, a container will be created to handle HTTP traffic.
|
|
138
|
-
*/
|
|
139
|
-
web?: LaravelWebArgs;
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Multiple workers settings.
|
|
143
|
-
*/
|
|
144
|
-
workers?: LaravelWorkerConfig[];
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Config settings.
|
|
148
|
-
*/
|
|
149
|
-
config?: {
|
|
110
|
+
name?: Input<string>;
|
|
150
111
|
/**
|
|
151
|
-
*
|
|
152
|
-
* Available versions: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5
|
|
153
|
-
*
|
|
154
|
-
* @default `8.4`
|
|
112
|
+
* Running horizon?
|
|
155
113
|
*/
|
|
156
|
-
|
|
114
|
+
horizon?: Input<boolean>;
|
|
157
115
|
|
|
158
116
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* @default `true`
|
|
117
|
+
* Running scheduler?
|
|
162
118
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
environment?: {
|
|
166
|
-
/**
|
|
167
|
-
* Use this option if you want to import an .env file during build. By default, SST Laravel won't use your .env file since that might be the wrong file when deploying from your local machine.
|
|
168
|
-
*
|
|
169
|
-
* @example
|
|
170
|
-
* ```js
|
|
171
|
-
* # Use use a fila named .env.$stage as your .env file
|
|
172
|
-
* environment: {
|
|
173
|
-
* file: `.env.${$app.stage}`,
|
|
174
|
-
* }
|
|
175
|
-
* OR
|
|
176
|
-
* environment: {
|
|
177
|
-
* file: `.env`,
|
|
178
|
-
* }
|
|
179
|
-
* ```
|
|
180
|
-
*/
|
|
181
|
-
file?: Input<string>,
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Set this to false in case you don't want to auto inject environment variables from your linked resources.
|
|
185
|
-
*
|
|
186
|
-
* @default `true`
|
|
187
|
-
*/
|
|
188
|
-
autoInject?: Input<boolean>,
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Custom environment variables that will be automatically injected into your application.
|
|
192
|
-
*
|
|
193
|
-
* @example
|
|
194
|
-
* ```js
|
|
195
|
-
* environment: {
|
|
196
|
-
* vars: {
|
|
197
|
-
* SESSION_DRIVER: 'redis',
|
|
198
|
-
* QUEUE_CONNECTION: 'redis',
|
|
199
|
-
* }
|
|
200
|
-
* }
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
|
-
vars?: FunctionArgs["environment"],
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Use a `RemoteEnvVault` component to manage environment variables in AWS Secrets Manager.
|
|
207
|
-
* When provided, secrets will be fetched from AWS Secrets Manager at build time.
|
|
208
|
-
*
|
|
209
|
-
* @example
|
|
210
|
-
* ```js
|
|
211
|
-
* const env = new RemoteEnvVault("Env");
|
|
212
|
-
*
|
|
213
|
-
* new LaravelService("Laravel", {
|
|
214
|
-
* config: {
|
|
215
|
-
* environment: {
|
|
216
|
-
* secrets: env,
|
|
217
|
-
* },
|
|
218
|
-
* },
|
|
219
|
-
* });
|
|
220
|
-
* ```
|
|
221
|
-
*/
|
|
222
|
-
secrets?: RemoteEnvVault,
|
|
223
|
-
};
|
|
119
|
+
scheduler?: Input<boolean>;
|
|
224
120
|
|
|
225
121
|
/**
|
|
226
|
-
*
|
|
122
|
+
* Multiple tasks can be run in the worker.
|
|
227
123
|
*/
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
124
|
+
tasks?: Input<{
|
|
125
|
+
[key: string]: Input<{
|
|
126
|
+
command: Input<string>;
|
|
127
|
+
dependencies?: Input<string[]>;
|
|
128
|
+
}>;
|
|
129
|
+
}>;
|
|
234
130
|
}
|
|
235
131
|
|
|
236
|
-
export
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.services = {};
|
|
248
|
-
|
|
249
|
-
args.config = args.config ?? {};
|
|
250
|
-
const sitePath = args.path ?? '.';
|
|
251
|
-
const absSitePath = path.resolve(sitePath.toString());
|
|
252
|
-
const nodeModulePath = getPackagePath();
|
|
253
|
-
|
|
254
|
-
// Determine the path where our plugin will save build files.
|
|
255
|
-
// SST sets __dirname to the .sst/platform directory.
|
|
256
|
-
const pluginBuildPath = path.resolve(__dirname, '../laravel');
|
|
132
|
+
export interface LaravelArgs extends ClusterArgs {
|
|
133
|
+
// dev?: false | DevArgs["dev"];
|
|
134
|
+
path?: Input<string>;
|
|
135
|
+
link?: Array<
|
|
136
|
+
| any
|
|
137
|
+
| {
|
|
138
|
+
resource: any;
|
|
139
|
+
environment?: EnvCallback;
|
|
140
|
+
}
|
|
141
|
+
>;
|
|
257
142
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
143
|
+
permissions?: Array<{
|
|
144
|
+
actions: string[];
|
|
145
|
+
resources: string[];
|
|
146
|
+
}>;
|
|
261
147
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
148
|
+
/**
|
|
149
|
+
* If enabled, a container will be created to handle HTTP traffic.
|
|
150
|
+
*/
|
|
151
|
+
web?: LaravelWebArgs;
|
|
265
152
|
|
|
266
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Multiple workers settings.
|
|
155
|
+
*/
|
|
156
|
+
workers?: LaravelWorkerConfig[];
|
|
267
157
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Config settings.
|
|
160
|
+
*/
|
|
161
|
+
config?: {
|
|
162
|
+
/**
|
|
163
|
+
* PHP version.
|
|
164
|
+
* Available versions: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5
|
|
165
|
+
*
|
|
166
|
+
* @default `8.4`
|
|
167
|
+
*/
|
|
168
|
+
php?: Input<Number>;
|
|
272
169
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
170
|
+
/**
|
|
171
|
+
* PHP Opcache should be enabled?
|
|
172
|
+
*
|
|
173
|
+
* @default `true`
|
|
174
|
+
*/
|
|
175
|
+
opcache?: Input<boolean>;
|
|
176
|
+
|
|
177
|
+
environment?: {
|
|
178
|
+
/**
|
|
179
|
+
* Use this option if you want to import an .env file during build. By default, SST Laravel won't use your .env file since that might be the wrong file when deploying from your local machine.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```js
|
|
183
|
+
* # Use use a fila named .env.$stage as your .env file
|
|
184
|
+
* environment: {
|
|
185
|
+
* file: `.env.${$app.stage}`,
|
|
186
|
+
* }
|
|
187
|
+
* OR
|
|
188
|
+
* environment: {
|
|
189
|
+
* file: `.env`,
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
file?: Input<string>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Set this to false in case you don't want to auto inject environment variables from your linked resources.
|
|
197
|
+
*
|
|
198
|
+
* @default `true`
|
|
199
|
+
*/
|
|
200
|
+
autoInject?: Input<boolean>;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Custom environment variables that will be automatically injected into your application.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```js
|
|
207
|
+
* environment: {
|
|
208
|
+
* vars: {
|
|
209
|
+
* SESSION_DRIVER: 'redis',
|
|
210
|
+
* QUEUE_CONNECTION: 'redis',
|
|
211
|
+
* }
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
vars?: FunctionArgs['environment'];
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Use a `RemoteEnvVault` component to manage environment variables in AWS Secrets Manager.
|
|
219
|
+
* When provided, secrets will be fetched from AWS Secrets Manager at build time.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```js
|
|
223
|
+
* const env = new RemoteEnvVault("Env");
|
|
224
|
+
*
|
|
225
|
+
* new LaravelService("Laravel", {
|
|
226
|
+
* config: {
|
|
227
|
+
* environment: {
|
|
228
|
+
* secrets: env,
|
|
229
|
+
* },
|
|
230
|
+
* },
|
|
231
|
+
* });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
secrets?: RemoteEnvVault;
|
|
235
|
+
};
|
|
277
236
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Custom deployment configurations.
|
|
239
|
+
*/
|
|
240
|
+
deployment?: {
|
|
241
|
+
// migrate?: Input<boolean>;
|
|
242
|
+
// optimize?: Input<boolean>;
|
|
243
|
+
script?: Input<string>;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
}
|
|
282
247
|
|
|
283
|
-
|
|
284
|
-
|
|
248
|
+
export class LaravelService extends Component {
|
|
249
|
+
private readonly services: Record<string, sst.aws.Service>;
|
|
250
|
+
private readonly _messages: string[] = [];
|
|
285
251
|
|
|
286
|
-
|
|
287
|
-
|
|
252
|
+
constructor(
|
|
253
|
+
name: string,
|
|
254
|
+
args: LaravelArgs,
|
|
255
|
+
opts: ComponentResourceOptions = {},
|
|
256
|
+
) {
|
|
257
|
+
super(__pulumiType, name, args, opts);
|
|
288
258
|
|
|
289
|
-
|
|
290
|
-
vpc: args.vpc
|
|
291
|
-
});
|
|
259
|
+
this.services = {};
|
|
292
260
|
|
|
293
|
-
|
|
294
|
-
|
|
261
|
+
args.config = args.config ?? {};
|
|
262
|
+
const sitePath = args.path ?? '.';
|
|
263
|
+
const absSitePath = path.resolve(sitePath.toString());
|
|
264
|
+
const nodeModulePath = getPackagePath();
|
|
295
265
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
permissions: args.permissions,
|
|
266
|
+
// Determine the path where our plugin will save build files.
|
|
267
|
+
// SST sets __dirname to the .sst/platform directory.
|
|
268
|
+
const pluginBuildPath = path.resolve(__dirname, '../laravel');
|
|
300
269
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
*/
|
|
304
|
-
image: getImage(ImageType.Web),
|
|
305
|
-
environment: envVariables,
|
|
306
|
-
scaling: args.web?.scaling,
|
|
307
|
-
|
|
308
|
-
loadBalancer: args.web && args.web.loadBalancer ? args.web.loadBalancer : {
|
|
309
|
-
domain: args.web?.domain,
|
|
310
|
-
ports: getDefaultPublicPorts(),
|
|
311
|
-
},
|
|
312
|
-
|
|
313
|
-
dev: {
|
|
314
|
-
command: `php ${sitePath}/artisan serve`,
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
transform: {
|
|
318
|
-
taskDefinition: (args) => {
|
|
319
|
-
args.containerDefinitions = (args.containerDefinitions as $util.Output<string>).apply(a => {
|
|
320
|
-
return JSON.stringify([{
|
|
321
|
-
...JSON.parse(a)[0],
|
|
322
|
-
linuxParameters: {
|
|
323
|
-
initProcessEnabled: false,
|
|
324
|
-
}
|
|
325
|
-
}]);
|
|
326
|
-
})
|
|
327
|
-
}
|
|
270
|
+
if (!fs.existsSync(pluginBuildPath)) {
|
|
271
|
+
fs.mkdirSync(pluginBuildPath, { recursive: true });
|
|
328
272
|
}
|
|
329
|
-
}, {
|
|
330
|
-
dependsOn: environmentFileDependency ? [environmentFileDependency] : [],
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
273
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
fs.mkdirSync(s6UserContentsPath, { recursive: true });
|
|
274
|
+
if (!fs.existsSync(pluginBuildPath + '/deploy')) {
|
|
275
|
+
fs.mkdirSync(pluginBuildPath + '/deploy', { recursive: true });
|
|
276
|
+
}
|
|
339
277
|
|
|
340
|
-
|
|
341
|
-
...((workerConfig.tasks as any) ?? {}),
|
|
342
|
-
};
|
|
278
|
+
const envFilePath = path.resolve(pluginBuildPath, 'deploy', '.env');
|
|
343
279
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
280
|
+
const envFileHasVariable = (variableName: string): boolean => {
|
|
281
|
+
const content = fs.readFileSync(envFilePath, 'utf-8');
|
|
282
|
+
return content
|
|
283
|
+
.split('\n')
|
|
284
|
+
.some((line) => line.trim().startsWith(`${variableName}=`));
|
|
347
285
|
};
|
|
348
|
-
}
|
|
349
286
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
287
|
+
const envFileSetVariable = (variableName: string, value: string) => {
|
|
288
|
+
fs.appendFileSync(envFilePath, `\n${variableName}=${value}\n`);
|
|
289
|
+
this._messages.push(
|
|
290
|
+
`Added ${variableName} to environment file: ${value}`,
|
|
291
|
+
);
|
|
353
292
|
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
Object.entries(tasks).forEach(([taskName, config]) => {
|
|
357
|
-
const tasksDir = path.resolve(s6RcDPath, `${taskName}`);
|
|
358
|
-
fs.mkdirSync(tasksDir, { recursive: true });
|
|
359
|
-
|
|
360
|
-
const scriptSrcPath = path.join(tasksDir, 'script');
|
|
361
|
-
|
|
362
|
-
fs.writeFileSync(scriptSrcPath, `#!/command/with-contenv bash\ncd /var/www/html\n${config.command}`, { mode: 0o777 });
|
|
363
|
-
fs.writeFileSync(path.join(tasksDir, 'run'), `#!/command/execlineb -P\n/etc/s6-overlay/s6-rc.d/${taskName}/script`, { mode: 0o777 });
|
|
364
|
-
fs.writeFileSync(path.join(tasksDir, 'type'), 'longrun');
|
|
365
|
-
fs.writeFileSync(path.join(tasksDir, 'dependencies'), (config.dependencies || []).join('\n'));
|
|
366
|
-
fs.writeFileSync(path.join(s6UserContentsPath, taskName), '');
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
293
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
this.services[serviceName] = new sst.aws.Service(serviceName, {
|
|
379
|
-
cluster,
|
|
380
|
-
link: getLinks(),
|
|
381
|
-
permissions: args.permissions,
|
|
382
|
-
|
|
383
|
-
image: getImage(ImageType.Worker, imgBuildArgs),
|
|
384
|
-
scaling: workerConfig.scaling,
|
|
385
|
-
environment: getEnvironmentVariables(),
|
|
386
|
-
|
|
387
|
-
dev: {
|
|
388
|
-
command: `php ${sitePath}/artisan horizon`,
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
transform: {
|
|
392
|
-
taskDefinition: (args) => {
|
|
393
|
-
args.containerDefinitions = (args.containerDefinitions as $util.Output<string>).apply(a => {
|
|
394
|
-
return JSON.stringify([{
|
|
395
|
-
...JSON.parse(a)[0],
|
|
396
|
-
linuxParameters: {
|
|
397
|
-
initProcessEnabled: false,
|
|
398
|
-
}
|
|
399
|
-
}]);
|
|
400
|
-
})
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}, {
|
|
404
|
-
dependsOn: environmentFileDependency ? [environmentFileDependency] : [],
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function addWorkerServices() {
|
|
409
|
-
args.workers?.forEach((workerConfig, index) => {
|
|
410
|
-
const workerName = workerConfig.name || `worker-${index + 1}`;
|
|
411
|
-
const absWorkerBuildPath = path.resolve(pluginBuildPath, `worker-${workerName}`);
|
|
294
|
+
const envFileSetVariableIfMissing = (
|
|
295
|
+
variableName: string,
|
|
296
|
+
value: string,
|
|
297
|
+
) => {
|
|
298
|
+
if (envFileHasVariable(variableName)) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
412
301
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
302
|
+
envFileSetVariable(variableName, value);
|
|
303
|
+
};
|
|
416
304
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
305
|
+
const environmentFileDependency = prepareEnvironmentFile();
|
|
306
|
+
prepareDeploymentScript();
|
|
420
307
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
308
|
+
const addEnvironmentFileImageDependency = (
|
|
309
|
+
_args: unknown,
|
|
310
|
+
opts: $util.CustomResourceOptions,
|
|
311
|
+
_name: string,
|
|
312
|
+
) => {
|
|
313
|
+
if (!environmentFileDependency) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
424
316
|
|
|
425
|
-
|
|
426
|
-
let ports;
|
|
427
|
-
const forwardPort: Port = "8080/http";
|
|
428
|
-
const portHttp: Port = "80/http";
|
|
429
|
-
const portHttps: Port = "443/https";
|
|
430
|
-
|
|
431
|
-
if (args.web?.domain) {
|
|
432
|
-
ports = [
|
|
433
|
-
{ listen: portHttp, forward: forwardPort },
|
|
434
|
-
{ listen: portHttps, forward: forwardPort },
|
|
435
|
-
];
|
|
436
|
-
} else {
|
|
437
|
-
ports = [
|
|
438
|
-
{ listen: portHttp, forward: forwardPort },
|
|
439
|
-
];
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return ports;
|
|
443
|
-
}
|
|
317
|
+
opts.dependsOn = [environmentFileDependency];
|
|
444
318
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const img = getDefaultImage(imgType, extraArgs);
|
|
319
|
+
return undefined;
|
|
320
|
+
};
|
|
448
321
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
322
|
+
const cluster = new sst.aws.Cluster(`${name}-Cluster`, {
|
|
323
|
+
vpc: normalizeClusterVpc(args.vpc),
|
|
324
|
+
});
|
|
452
325
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
326
|
+
const addWebService = () => {
|
|
327
|
+
const envVariables = getEnvironmentVariables();
|
|
328
|
+
|
|
329
|
+
this.services['web'] = new sst.aws.Service(
|
|
330
|
+
`${name}-Web`,
|
|
331
|
+
{
|
|
332
|
+
cluster,
|
|
333
|
+
link: getLinks(),
|
|
334
|
+
permissions: args.permissions,
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Image passed or use our default provided image.
|
|
338
|
+
*/
|
|
339
|
+
image: getImage(ImageType.Web),
|
|
340
|
+
environment: envVariables,
|
|
341
|
+
scaling: args.web?.scaling,
|
|
342
|
+
|
|
343
|
+
loadBalancer:
|
|
344
|
+
args.web && args.web.loadBalancer
|
|
345
|
+
? args.web.loadBalancer
|
|
346
|
+
: {
|
|
347
|
+
domain: args.web?.domain,
|
|
348
|
+
ports: getDefaultPublicPorts(),
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
dev: {
|
|
352
|
+
command: `php ${sitePath}/artisan serve`,
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
transform: {
|
|
356
|
+
image: addEnvironmentFileImageDependency,
|
|
357
|
+
taskDefinition: (args) => {
|
|
358
|
+
args.containerDefinitions = (
|
|
359
|
+
args.containerDefinitions as $util.Output<string>
|
|
360
|
+
).apply((a) => {
|
|
361
|
+
return JSON.stringify([
|
|
362
|
+
{
|
|
363
|
+
...JSON.parse(a)[0],
|
|
364
|
+
linuxParameters: {
|
|
365
|
+
initProcessEnabled: false,
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
]);
|
|
369
|
+
});
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
dependsOn: environmentFileDependency
|
|
375
|
+
? [environmentFileDependency]
|
|
376
|
+
: [],
|
|
377
|
+
},
|
|
378
|
+
);
|
|
379
|
+
};
|
|
456
380
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
381
|
+
function createWorkerTasks(
|
|
382
|
+
workerConfig: LaravelWorkerConfig,
|
|
383
|
+
workerBuildPath: string,
|
|
384
|
+
) {
|
|
385
|
+
const s6RcDPath = path.resolve(
|
|
386
|
+
workerBuildPath,
|
|
387
|
+
'etc/s6-overlay/s6-rc.d',
|
|
388
|
+
);
|
|
389
|
+
const s6UserContentsPath = path.resolve(
|
|
390
|
+
s6RcDPath,
|
|
391
|
+
'user/contents.d',
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
fs.mkdirSync(s6UserContentsPath, { recursive: true });
|
|
395
|
+
|
|
396
|
+
const tasks: Record<
|
|
397
|
+
string,
|
|
398
|
+
{ command: string; dependencies?: string[] }
|
|
399
|
+
> = {
|
|
400
|
+
...((workerConfig.tasks as any) ?? {}),
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
if (workerConfig.horizon) {
|
|
404
|
+
tasks['laravel-horizon'] = {
|
|
405
|
+
command: 'php artisan horizon',
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (workerConfig.scheduler) {
|
|
410
|
+
tasks['laravel-scheduler'] = {
|
|
411
|
+
command: 'php artisan schedule:work',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
Object.entries(tasks).forEach(([taskName, config]) => {
|
|
416
|
+
const tasksDir = path.resolve(s6RcDPath, `${taskName}`);
|
|
417
|
+
fs.mkdirSync(tasksDir, { recursive: true });
|
|
418
|
+
|
|
419
|
+
const scriptSrcPath = path.join(tasksDir, 'script');
|
|
420
|
+
|
|
421
|
+
fs.writeFileSync(
|
|
422
|
+
scriptSrcPath,
|
|
423
|
+
`#!/command/with-contenv bash\ncd /var/www/html\n${config.command}`,
|
|
424
|
+
{ mode: 0o777 },
|
|
425
|
+
);
|
|
426
|
+
fs.writeFileSync(
|
|
427
|
+
path.join(tasksDir, 'run'),
|
|
428
|
+
`#!/command/execlineb -P\n/etc/s6-overlay/s6-rc.d/${taskName}/script`,
|
|
429
|
+
{ mode: 0o777 },
|
|
430
|
+
);
|
|
431
|
+
fs.writeFileSync(path.join(tasksDir, 'type'), 'longrun');
|
|
432
|
+
fs.writeFileSync(
|
|
433
|
+
path.join(tasksDir, 'dependencies'),
|
|
434
|
+
(config.dependencies || []).join('\n'),
|
|
435
|
+
);
|
|
436
|
+
fs.writeFileSync(path.join(s6UserContentsPath, taskName), '');
|
|
437
|
+
});
|
|
438
|
+
}
|
|
461
439
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
440
|
+
const createWorkerService = (
|
|
441
|
+
workerConfig: LaravelWorkerConfig,
|
|
442
|
+
serviceName: string,
|
|
443
|
+
workerBuildPath: string,
|
|
444
|
+
) => {
|
|
445
|
+
createWorkerTasks(workerConfig, workerBuildPath);
|
|
446
|
+
|
|
447
|
+
const imgBuildArgs = {
|
|
448
|
+
CONF_PATH: path
|
|
449
|
+
.resolve(nodeModulePath, 'conf')
|
|
450
|
+
.replace(absSitePath, ''),
|
|
451
|
+
CUSTOM_CONF_PATH: workerBuildPath.replace(absSitePath, ''),
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
this.services[serviceName] = new sst.aws.Service(
|
|
455
|
+
serviceName,
|
|
456
|
+
{
|
|
457
|
+
cluster,
|
|
458
|
+
link: getLinks(),
|
|
459
|
+
permissions: args.permissions,
|
|
460
|
+
|
|
461
|
+
image: getImage(ImageType.Worker, imgBuildArgs),
|
|
462
|
+
scaling: workerConfig.scaling,
|
|
463
|
+
environment: getEnvironmentVariables(),
|
|
464
|
+
|
|
465
|
+
dev: {
|
|
466
|
+
command: `php ${sitePath}/artisan horizon`,
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
transform: {
|
|
470
|
+
image: addEnvironmentFileImageDependency,
|
|
471
|
+
taskDefinition: (args) => {
|
|
472
|
+
args.containerDefinitions = (
|
|
473
|
+
args.containerDefinitions as $util.Output<string>
|
|
474
|
+
).apply((a) => {
|
|
475
|
+
return JSON.stringify([
|
|
476
|
+
{
|
|
477
|
+
...JSON.parse(a)[0],
|
|
478
|
+
linuxParameters: {
|
|
479
|
+
initProcessEnabled: false,
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
]);
|
|
483
|
+
});
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
dependsOn: environmentFileDependency
|
|
489
|
+
? [environmentFileDependency]
|
|
490
|
+
: [],
|
|
491
|
+
},
|
|
492
|
+
);
|
|
493
|
+
};
|
|
465
494
|
|
|
466
|
-
|
|
495
|
+
function addWorkerServices() {
|
|
496
|
+
args.workers?.forEach((workerConfig, index) => {
|
|
497
|
+
const workerName = workerConfig.name || `worker-${index + 1}`;
|
|
498
|
+
const absWorkerBuildPath = path.resolve(
|
|
499
|
+
pluginBuildPath,
|
|
500
|
+
`worker-${workerName}`,
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
createWorkerService(
|
|
504
|
+
workerConfig,
|
|
505
|
+
`${name}-${workerName}`,
|
|
506
|
+
absWorkerBuildPath,
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
467
510
|
|
|
468
|
-
|
|
511
|
+
if (args.web) {
|
|
512
|
+
addWebService();
|
|
513
|
+
}
|
|
469
514
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (!lines.find((line) => line === ".sst")) {
|
|
473
|
-
fs.writeFileSync(
|
|
474
|
-
dockerIgnore,
|
|
475
|
-
[...lines, "", "# sst", "!.sst/laravel"].join("\n"),
|
|
476
|
-
);
|
|
515
|
+
if (args.workers) {
|
|
516
|
+
addWorkerServices();
|
|
477
517
|
}
|
|
478
518
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
519
|
+
function normalizeClusterVpc(
|
|
520
|
+
vpc: LaravelArgs['vpc'],
|
|
521
|
+
): LaravelArgs['vpc'] {
|
|
522
|
+
if (
|
|
523
|
+
!vpc ||
|
|
524
|
+
typeof vpc !== 'object' ||
|
|
525
|
+
!('publicSubnets' in vpc) ||
|
|
526
|
+
!('nodes' in vpc)
|
|
527
|
+
) {
|
|
528
|
+
return vpc;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const cloudmapNamespace = vpc.nodes?.cloudmapNamespace;
|
|
532
|
+
|
|
533
|
+
if (!cloudmapNamespace) {
|
|
534
|
+
return vpc;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return {
|
|
538
|
+
id: vpc.id,
|
|
539
|
+
securityGroups: vpc.securityGroups,
|
|
540
|
+
containerSubnets: vpc.publicSubnets,
|
|
541
|
+
loadBalancerSubnets: vpc.publicSubnets,
|
|
542
|
+
cloudmapNamespaceId: cloudmapNamespace.id,
|
|
543
|
+
cloudmapNamespaceName: cloudmapNamespace.name,
|
|
544
|
+
};
|
|
484
545
|
}
|
|
485
|
-
}
|
|
486
546
|
|
|
487
|
-
|
|
488
|
-
|
|
547
|
+
function getDefaultPublicPorts(): Ports {
|
|
548
|
+
let ports;
|
|
549
|
+
const forwardPort: Port = '8080/http';
|
|
550
|
+
const portHttp: Port = '80/http';
|
|
551
|
+
const portHttps: Port = '443/https';
|
|
552
|
+
|
|
553
|
+
if (args.web?.domain) {
|
|
554
|
+
ports = [
|
|
555
|
+
{ listen: portHttp, forward: forwardPort },
|
|
556
|
+
{ listen: portHttps, forward: forwardPort },
|
|
557
|
+
];
|
|
558
|
+
} else {
|
|
559
|
+
ports = [{ listen: portHttp, forward: forwardPort }];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return ports;
|
|
563
|
+
}
|
|
489
564
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
565
|
+
// TODO: We have to test if it works when a custom image is provided in sst.config.js
|
|
566
|
+
function getImage(imgType: ImageType, extraArgs: object = {}) {
|
|
567
|
+
const img = getDefaultImage(imgType, extraArgs);
|
|
568
|
+
|
|
569
|
+
const context =
|
|
570
|
+
typeof img === 'string'
|
|
571
|
+
? sitePath.toString()
|
|
572
|
+
: (img as { context: string }).context.toString();
|
|
573
|
+
|
|
574
|
+
const dockerfile =
|
|
575
|
+
typeof img === 'string'
|
|
576
|
+
? 'Dockerfile'
|
|
577
|
+
: (img as { dockerfile: string }).dockerfile;
|
|
578
|
+
|
|
579
|
+
// add .sst/laravel to .dockerignore if not exist
|
|
580
|
+
const dockerIgnore = (() => {
|
|
581
|
+
let filePath = path.join(context, `${dockerfile}.dockerignore`);
|
|
582
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
583
|
+
|
|
584
|
+
return path.join(context, '.dockerignore');
|
|
585
|
+
})();
|
|
586
|
+
|
|
587
|
+
const content = fs.existsSync(dockerIgnore)
|
|
588
|
+
? fs.readFileSync(dockerIgnore).toString()
|
|
589
|
+
: '';
|
|
590
|
+
|
|
591
|
+
const lines = content.split('\n');
|
|
592
|
+
|
|
593
|
+
const normalizedLines = [
|
|
594
|
+
...lines.filter(
|
|
595
|
+
(line) =>
|
|
596
|
+
line !== '.sst' &&
|
|
597
|
+
line !== '!.sst/laravel' &&
|
|
598
|
+
line !== '# sst' &&
|
|
599
|
+
line !== '# sst-laravel',
|
|
600
|
+
),
|
|
601
|
+
'',
|
|
602
|
+
'# sst',
|
|
603
|
+
'.sst',
|
|
604
|
+
'',
|
|
605
|
+
'# sst-laravel',
|
|
606
|
+
'!.sst/laravel',
|
|
607
|
+
];
|
|
608
|
+
|
|
609
|
+
if (normalizedLines.join('\n') !== lines.join('\n')) {
|
|
610
|
+
fs.writeFileSync(dockerIgnore, normalizedLines.join('\n'));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return img;
|
|
614
|
+
}
|
|
505
615
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
616
|
+
function getDefaultImage(imageType: ImageType, extraArgs: object = {}) {
|
|
617
|
+
return {
|
|
618
|
+
context: sitePath,
|
|
619
|
+
dockerfile: path
|
|
620
|
+
.resolve(nodeModulePath, `Dockerfile.${imageType}`)
|
|
621
|
+
.replace(absSitePath, '.'),
|
|
622
|
+
args: {
|
|
623
|
+
PHP_VERSION: getPhpVersion().toString(),
|
|
624
|
+
PHP_OPCACHE_ENABLE: args.config?.opcache ? '1' : '0',
|
|
625
|
+
AUTORUN_LARAVEL_MIGRATION:
|
|
626
|
+
imageType === ImageType.Web ? 'true' : 'false',
|
|
627
|
+
CONTAINER_TYPE: imageType,
|
|
628
|
+
stage: 'deploy',
|
|
629
|
+
platform: 'linux/amd64',
|
|
630
|
+
...extraArgs,
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
}
|
|
509
634
|
|
|
510
|
-
|
|
511
|
-
|
|
635
|
+
function getPhpVersion() {
|
|
636
|
+
return args.config?.php ?? 8.4;
|
|
637
|
+
}
|
|
512
638
|
|
|
513
|
-
|
|
514
|
-
|
|
639
|
+
function getEnvironmentVariables() {
|
|
640
|
+
const env = args.config?.environment?.vars || {};
|
|
515
641
|
|
|
516
|
-
|
|
517
|
-
const links = (args.link || []);
|
|
518
|
-
const resources: any[] = [];
|
|
519
|
-
const customEnv: Record<string, string | Output<string>> = {};
|
|
520
|
-
|
|
521
|
-
links.forEach(link => {
|
|
522
|
-
if (link && typeof link === 'object' && 'resource' in link) {
|
|
523
|
-
// Link is an object with resource and optional envCallback
|
|
524
|
-
resources.push(link.resource);
|
|
525
|
-
|
|
526
|
-
// If there's an envCallback, call it and merge the result
|
|
527
|
-
const callback = (link as { environment?: EnvCallback; envCallback?: EnvCallback }).environment
|
|
528
|
-
|| (link as { environment?: EnvCallback; envCallback?: EnvCallback }).envCallback;
|
|
529
|
-
if (callback) {
|
|
530
|
-
const callbackResult = callback(link.resource);
|
|
531
|
-
Object.assign(customEnv, callbackResult);
|
|
532
|
-
}
|
|
533
|
-
} else {
|
|
534
|
-
// Link is just a resource
|
|
535
|
-
resources.push(link);
|
|
642
|
+
return env;
|
|
536
643
|
}
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
return {
|
|
540
|
-
linkedEnvironment: {
|
|
541
|
-
...applyLinkedResourcesEnv(resources),
|
|
542
|
-
...customEnv,
|
|
543
|
-
},
|
|
544
|
-
linkedSecrets: extractSecrets(resources).map(secret => ({
|
|
545
|
-
name: secret.name,
|
|
546
|
-
value: secret.value,
|
|
547
|
-
})),
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
644
|
|
|
551
|
-
|
|
552
|
-
|
|
645
|
+
function getLinkedEnvironmentData() {
|
|
646
|
+
const links = args.link || [];
|
|
647
|
+
const resources: any[] = [];
|
|
648
|
+
const customEnv: Record<string, string | Output<string>> = {};
|
|
649
|
+
|
|
650
|
+
links.forEach((link) => {
|
|
651
|
+
if (link && typeof link === 'object' && 'resource' in link) {
|
|
652
|
+
// Link is an object with resource and optional envCallback
|
|
653
|
+
resources.push(link.resource);
|
|
654
|
+
|
|
655
|
+
// If there's an envCallback, call it and merge the result
|
|
656
|
+
const callback =
|
|
657
|
+
(
|
|
658
|
+
link as {
|
|
659
|
+
environment?: EnvCallback;
|
|
660
|
+
envCallback?: EnvCallback;
|
|
661
|
+
}
|
|
662
|
+
).environment ||
|
|
663
|
+
(
|
|
664
|
+
link as {
|
|
665
|
+
environment?: EnvCallback;
|
|
666
|
+
envCallback?: EnvCallback;
|
|
667
|
+
}
|
|
668
|
+
).envCallback;
|
|
669
|
+
if (callback) {
|
|
670
|
+
const callbackResult = callback(link.resource);
|
|
671
|
+
Object.assign(customEnv, callbackResult);
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
// Link is just a resource
|
|
675
|
+
resources.push(link);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
return {
|
|
680
|
+
linkedEnvironment: {
|
|
681
|
+
...applyLinkedResourcesEnv(resources),
|
|
682
|
+
...customEnv,
|
|
683
|
+
},
|
|
684
|
+
linkedSecrets: extractSecrets(resources).map((secret) => ({
|
|
685
|
+
name: secret.name,
|
|
686
|
+
value: secret.value,
|
|
687
|
+
})),
|
|
688
|
+
};
|
|
689
|
+
}
|
|
553
690
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
691
|
+
function applyLinkedResourcesToEnvironment() {
|
|
692
|
+
const { linkedEnvironment, linkedSecrets } =
|
|
693
|
+
getLinkedEnvironmentData();
|
|
557
694
|
|
|
558
|
-
|
|
695
|
+
// Apply default environment variables for all resources
|
|
696
|
+
if (!args.config) args.config = {};
|
|
697
|
+
if (!args.config.environment) args.config.environment = {};
|
|
559
698
|
|
|
560
|
-
|
|
561
|
-
|
|
699
|
+
fs.appendFileSync(
|
|
700
|
+
envFilePath,
|
|
701
|
+
'\n' + '# --- SST-LARAVEL AUTO-INJECTED VARIABLES ---' + '\n',
|
|
702
|
+
);
|
|
562
703
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
566
|
-
.join('\n');
|
|
704
|
+
addAppUrlIfMissing();
|
|
705
|
+
envFileSetVariableIfMissing('LOG_CHANNEL', 'stderr');
|
|
567
706
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
707
|
+
all(Object.entries(linkedEnvironment)).apply((entries) => {
|
|
708
|
+
const envContent = entries
|
|
709
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
710
|
+
.join('\n');
|
|
572
711
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
});
|
|
578
|
-
};
|
|
712
|
+
if (envContent) {
|
|
713
|
+
fs.appendFileSync(envFilePath, '\n' + envContent);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
579
716
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (link && typeof link === 'object' && 'resource' in link) {
|
|
586
|
-
return link.resource;
|
|
717
|
+
linkedSecrets.forEach((secret) => {
|
|
718
|
+
all([secret.name, secret.value]).apply(([name, value]) => {
|
|
719
|
+
fs.appendFileSync(envFilePath, `\n${name}=${value}`);
|
|
720
|
+
});
|
|
721
|
+
});
|
|
587
722
|
}
|
|
588
723
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
724
|
+
/**
|
|
725
|
+
* Return the links as an array of resources in the original SST format.
|
|
726
|
+
*/
|
|
727
|
+
function getLinks(): any[] {
|
|
728
|
+
return (args.link || []).map((link) => {
|
|
729
|
+
if (link && typeof link === 'object' && 'resource' in link) {
|
|
730
|
+
return link.resource;
|
|
731
|
+
}
|
|
592
732
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
733
|
+
return link;
|
|
734
|
+
});
|
|
735
|
+
}
|
|
596
736
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
737
|
+
function prepareEnvironmentFile() {
|
|
738
|
+
const envFile = args.config?.environment?.file as
|
|
739
|
+
| string
|
|
740
|
+
| undefined;
|
|
741
|
+
const secrets = args.config?.environment?.secrets;
|
|
742
|
+
|
|
743
|
+
if (secrets) {
|
|
744
|
+
return prepareRemoteEnvironmentFile(secrets);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Handle traditional env file configuration
|
|
748
|
+
if (!envFile) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const src = path.resolve(absSitePath, envFile);
|
|
753
|
+
|
|
754
|
+
if (fs.existsSync(src)) {
|
|
755
|
+
fs.copyFileSync(src, envFilePath);
|
|
756
|
+
fs.chmodSync(envFilePath, 0o755);
|
|
757
|
+
} else {
|
|
758
|
+
fs.writeFileSync(envFilePath, '');
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (args.config?.environment?.autoInject !== false) {
|
|
762
|
+
applyLinkedResourcesToEnvironment();
|
|
763
|
+
}
|
|
764
|
+
}
|
|
600
765
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
766
|
+
function prepareRemoteEnvironmentFile(secrets: RemoteEnvVault) {
|
|
767
|
+
if (runtime.isDryRun() && !fs.existsSync(envFilePath)) {
|
|
768
|
+
fs.writeFileSync(
|
|
769
|
+
envFilePath,
|
|
770
|
+
'# WARNING: RemoteEnvVault secrets are loaded during deployment. Preview uses a placeholder file.\n',
|
|
771
|
+
);
|
|
772
|
+
fs.chmodSync(envFilePath, 0o755);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const { linkedEnvironment, linkedSecrets } =
|
|
776
|
+
getLinkedEnvironmentData();
|
|
777
|
+
|
|
778
|
+
return new RemoteEnvFile(
|
|
779
|
+
`${name}-RemoteEnv`,
|
|
780
|
+
{
|
|
781
|
+
secretPath: secrets.path,
|
|
782
|
+
envFilePath,
|
|
783
|
+
fingerprint: output(secrets.path).apply((secretPath) =>
|
|
784
|
+
getSecretsFingerprint(secretPath),
|
|
785
|
+
),
|
|
786
|
+
autoInject: args.config?.environment?.autoInject !== false,
|
|
787
|
+
appUrl: getAppUrl(),
|
|
788
|
+
linkedEnvironment,
|
|
789
|
+
linkedSecrets,
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
parent: this,
|
|
793
|
+
},
|
|
794
|
+
);
|
|
795
|
+
}
|
|
605
796
|
|
|
606
|
-
|
|
797
|
+
function addAppUrlIfMissing() {
|
|
798
|
+
if (envFileHasVariable('APP_URL')) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
607
801
|
|
|
608
|
-
|
|
609
|
-
fs.copyFileSync(src, envFilePath);
|
|
610
|
-
fs.chmodSync(envFilePath, 0o755);
|
|
611
|
-
} else {
|
|
612
|
-
fs.writeFileSync(envFilePath, '');
|
|
613
|
-
}
|
|
802
|
+
const appUrl = getAppUrl();
|
|
614
803
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
804
|
+
if (typeof appUrl === 'string') {
|
|
805
|
+
envFileSetVariable('APP_URL', appUrl);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
619
808
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
809
|
+
function getAppUrl(): PulumiInput<string | undefined> | undefined {
|
|
810
|
+
if (!args.web?.domain) {
|
|
811
|
+
return undefined;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (typeof args.web.domain === 'string') {
|
|
815
|
+
return `https://${args.web.domain}`;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (
|
|
819
|
+
typeof args.web.domain === 'object' &&
|
|
820
|
+
'name' in args.web.domain
|
|
821
|
+
) {
|
|
822
|
+
return output(
|
|
823
|
+
(args.web.domain as { name: Input<string> }).name,
|
|
824
|
+
).apply((domainName) =>
|
|
825
|
+
domainName ? `https://${domainName}` : undefined,
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return undefined;
|
|
830
|
+
}
|
|
642
831
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
832
|
+
function prepareDeploymentScript() {
|
|
833
|
+
const deployDir = path.resolve(pluginBuildPath, 'deploy');
|
|
834
|
+
const dst = path.resolve(deployDir, '60-deploy.sh');
|
|
835
|
+
|
|
836
|
+
fs.mkdirSync(deployDir, { recursive: true });
|
|
837
|
+
|
|
838
|
+
const script = args.config?.deployment?.script as
|
|
839
|
+
| string
|
|
840
|
+
| undefined;
|
|
841
|
+
if (script) {
|
|
842
|
+
const src = path.resolve(absSitePath, script);
|
|
843
|
+
if (fs.existsSync(src)) {
|
|
844
|
+
fs.copyFileSync(src, dst);
|
|
845
|
+
fs.chmodSync(dst, 0o755);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
647
849
|
|
|
648
|
-
|
|
850
|
+
fs.writeFileSync(dst, '#!/bin/sh\nexit 0\n');
|
|
851
|
+
fs.chmodSync(dst, 0o755);
|
|
852
|
+
}
|
|
649
853
|
|
|
650
|
-
|
|
651
|
-
envFileSetVariable('APP_URL', appUrl);
|
|
652
|
-
}
|
|
854
|
+
this.registerOutputs({ _hint: this.messages });
|
|
653
855
|
}
|
|
654
856
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (typeof args.web.domain === 'object' && 'name' in args.web.domain) {
|
|
665
|
-
return output((args.web.domain as { name: Input<string> }).name)
|
|
666
|
-
.apply(domainName => domainName ? `https://${domainName}` : undefined);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
return undefined;
|
|
857
|
+
/**
|
|
858
|
+
* The URL of the service.
|
|
859
|
+
*
|
|
860
|
+
* If `public.domain` is set, this is the URL with the custom domain.
|
|
861
|
+
* Otherwise, it's the auto-generated load balancer URL.
|
|
862
|
+
*/
|
|
863
|
+
public get url() {
|
|
864
|
+
return this.services['web'].url;
|
|
670
865
|
}
|
|
671
866
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (script) {
|
|
680
|
-
const src = path.resolve(absSitePath, script);
|
|
681
|
-
if (fs.existsSync(src)) {
|
|
682
|
-
fs.copyFileSync(src, dst);
|
|
683
|
-
fs.chmodSync(dst, 0o755);
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
fs.writeFileSync(dst, "#!/bin/sh\nexit 0\n");
|
|
689
|
-
fs.chmodSync(dst, 0o755);
|
|
867
|
+
/**
|
|
868
|
+
* The messages from the service.
|
|
869
|
+
*
|
|
870
|
+
* This is useful for debugging and troubleshooting.
|
|
871
|
+
*/
|
|
872
|
+
public get messages() {
|
|
873
|
+
return this._messages;
|
|
690
874
|
}
|
|
691
|
-
|
|
692
|
-
this.registerOutputs({ _hint: this.messages });
|
|
693
|
-
};
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* The URL of the service.
|
|
697
|
-
*
|
|
698
|
-
* If `public.domain` is set, this is the URL with the custom domain.
|
|
699
|
-
* Otherwise, it's the auto-generated load balancer URL.
|
|
700
|
-
*/
|
|
701
|
-
public get url() {
|
|
702
|
-
return this.services['web'].url;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* The messages from the service.
|
|
707
|
-
*
|
|
708
|
-
* This is useful for debugging and troubleshooting.
|
|
709
|
-
*/
|
|
710
|
-
public get messages() {
|
|
711
|
-
return this._messages;
|
|
712
|
-
}
|
|
713
875
|
}
|
|
714
876
|
|
|
715
|
-
const __pulumiType =
|
|
877
|
+
const __pulumiType = 'sst:aws:LaravelService';
|
|
716
878
|
// @ts-expect-error
|
|
717
879
|
LaravelService.__pulumiType = __pulumiType;
|