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