@geekmidas/cloud 10.0.0-alpha.12 → 10.0.0-alpha.13
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/dist/index.cjs +3 -3
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -3
- package/dist/utils/index.cjs +106 -5
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +62 -2
- package/dist/utils/index.d.cts.map +1 -0
- package/dist/utils/index.d.mts +62 -2
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +78 -2
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +7 -7
- package/src/dokploy/Application.ts +7 -0
- package/src/dokploy/__tests__/Application.provider.spec.ts +148 -0
- package/src/sst/__tests__/api.spec.ts +230 -0
- package/src/sst/__tests__/fromManifest.integration.spec.ts +368 -0
- package/src/sst/__tests__/function.spec.ts +192 -0
- package/src/sst/__tests__/links.spec.ts +209 -0
- package/src/sst/aws/Database.ts +15 -1
- package/src/sst/fromManifest.ts +8 -1
- package/dist/index-B5CZ1xVf.d.cts +0 -64
- package/dist/index-B5CZ1xVf.d.cts.map +0 -1
- package/dist/index-DhHRjduZ.d.mts +0 -64
- package/dist/index-DhHRjduZ.d.mts.map +0 -1
- package/dist/utils-B1a2UuEO.cjs +0 -129
- package/dist/utils-B1a2UuEO.cjs.map +0 -1
- package/dist/utils-DMOXJ27j.mjs +0 -83
- package/dist/utils-DMOXJ27j.mjs.map +0 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { App } from '../App';
|
|
3
|
+
import { Cron } from '../aws/Cron';
|
|
4
|
+
import { Function } from '../aws/Function';
|
|
5
|
+
import { type GkmLinkable, ResourceType } from '../Linkable';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A Lambda and a schedule as data: the args each hands SST (a recording stub
|
|
9
|
+
* in `test/sst-globals.ts`), and the env validation that fails before deploy.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const app = new App({
|
|
13
|
+
name: 'shop',
|
|
14
|
+
stage: 'prod',
|
|
15
|
+
domain: 'shop.test',
|
|
16
|
+
hostedZoneId: 'Z1',
|
|
17
|
+
region: 'eu-west-1',
|
|
18
|
+
});
|
|
19
|
+
const stack = app.stack('jobs');
|
|
20
|
+
const db: GkmLinkable = { _id: 'db', _type: ResourceType.Postgres };
|
|
21
|
+
|
|
22
|
+
const argsOf = (component: unknown) =>
|
|
23
|
+
(component as { args: Record<string, unknown> }).args;
|
|
24
|
+
|
|
25
|
+
describe('Function', () => {
|
|
26
|
+
it('defaults the runtime and logging, and links only what its variables need', () => {
|
|
27
|
+
const fn = new Function(stack, 'Reports', {
|
|
28
|
+
handler: 'reports.handler',
|
|
29
|
+
links: [db],
|
|
30
|
+
envVars: ['DB_URL'],
|
|
31
|
+
environment: { FEATURE: 'on' },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(fn._type).toBe(ResourceType.Function);
|
|
35
|
+
expect(argsOf(fn)).toMatchObject({
|
|
36
|
+
handler: 'reports.handler',
|
|
37
|
+
runtime: 'nodejs24.x',
|
|
38
|
+
logging: { format: 'json' },
|
|
39
|
+
link: [db],
|
|
40
|
+
environment: expect.objectContaining({
|
|
41
|
+
SERVICE_NAME: 'Reports',
|
|
42
|
+
STAGE: 'prod',
|
|
43
|
+
FEATURE: 'on',
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
expect(fn.validate().valid).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('keeps a runtime and logging it was given', () => {
|
|
50
|
+
const fn = new Function(stack, 'Legacy', {
|
|
51
|
+
handler: 'legacy.handler',
|
|
52
|
+
runtime: 'nodejs20.x',
|
|
53
|
+
logging: { format: 'text' },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(argsOf(fn)).toMatchObject({
|
|
57
|
+
runtime: 'nodejs20.x',
|
|
58
|
+
logging: { format: 'text' },
|
|
59
|
+
link: [],
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('fails the synth on a variable no link provides, unless told not to check', () => {
|
|
64
|
+
expect(
|
|
65
|
+
() =>
|
|
66
|
+
new Function(stack, 'Mailer', {
|
|
67
|
+
handler: 'mailer.handler',
|
|
68
|
+
envVars: ['SMTP_URL'],
|
|
69
|
+
}),
|
|
70
|
+
).toThrow(/SMTP_URL/);
|
|
71
|
+
|
|
72
|
+
const unchecked = new Function(stack, 'Mailer', {
|
|
73
|
+
handler: 'mailer.handler',
|
|
74
|
+
envVars: ['SMTP_URL'],
|
|
75
|
+
autoValidate: false,
|
|
76
|
+
});
|
|
77
|
+
expect(unchecked.validate()).toMatchObject({
|
|
78
|
+
valid: false,
|
|
79
|
+
invalidVars: ['SMTP_URL'],
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('builds one function per manifest entry, named for the stack', () => {
|
|
84
|
+
const functions = Function.fromManifest(
|
|
85
|
+
stack,
|
|
86
|
+
{
|
|
87
|
+
billing: [
|
|
88
|
+
{
|
|
89
|
+
name: 'Invoice',
|
|
90
|
+
handler: '.gkm/aws-lambda/functions/invoice.handler',
|
|
91
|
+
environment: ['DB_URL'],
|
|
92
|
+
timeout: 60,
|
|
93
|
+
memorySize: 512,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
ops: [
|
|
97
|
+
{
|
|
98
|
+
name: 'Ping',
|
|
99
|
+
handler: '.gkm/aws-lambda/functions/ping.handler',
|
|
100
|
+
environment: [],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
} as never,
|
|
104
|
+
{ links: [db] },
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(functions.map((f) => argsOf(f))).toEqual([
|
|
108
|
+
expect.objectContaining({
|
|
109
|
+
name: stack.logicalPrefixedName('Invoice'),
|
|
110
|
+
timeout: '60 seconds',
|
|
111
|
+
memory: '512 MB',
|
|
112
|
+
link: [db],
|
|
113
|
+
}),
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
name: stack.logicalPrefixedName('Ping'),
|
|
116
|
+
timeout: undefined,
|
|
117
|
+
memory: undefined,
|
|
118
|
+
link: [],
|
|
119
|
+
}),
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('Cron', () => {
|
|
125
|
+
it('schedules the function it is given, by ARN', () => {
|
|
126
|
+
const processor = { arn: 'arn:aws:lambda:eu-west-1:1:function:nightly' };
|
|
127
|
+
const cron = new Cron(stack, 'Nightly', {
|
|
128
|
+
processor: processor as never,
|
|
129
|
+
schedule: 'rate(1 day)',
|
|
130
|
+
enabled: false,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(argsOf(cron)).toEqual({
|
|
134
|
+
enabled: false,
|
|
135
|
+
schedule: 'rate(1 day)',
|
|
136
|
+
function: processor.arn,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('builds a validated function and a schedule per manifest cron', () => {
|
|
141
|
+
const crons = Cron.fromManifest(
|
|
142
|
+
stack,
|
|
143
|
+
[
|
|
144
|
+
{
|
|
145
|
+
name: 'Digest',
|
|
146
|
+
handler: '.gkm/aws-lambda/crons/digest.handler',
|
|
147
|
+
schedule: 'cron(0 8 * * ? *)',
|
|
148
|
+
environment: ['DB_URL'],
|
|
149
|
+
timeout: 120,
|
|
150
|
+
memorySize: 1024,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'Sweep',
|
|
154
|
+
handler: '.gkm/aws-lambda/crons/sweep.handler',
|
|
155
|
+
schedule: 'rate(1 hour)',
|
|
156
|
+
environment: [],
|
|
157
|
+
},
|
|
158
|
+
] as never,
|
|
159
|
+
{ links: [db], enabled: true },
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const [digest, sweep] = crons.map((c) => argsOf(c));
|
|
163
|
+
expect(digest).toMatchObject({
|
|
164
|
+
schedule: 'cron(0 8 * * ? *)',
|
|
165
|
+
enabled: true,
|
|
166
|
+
function: expect.stringContaining('DigestFunction'),
|
|
167
|
+
});
|
|
168
|
+
expect(sweep).toMatchObject({ schedule: 'rate(1 hour)' });
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('App and Stack', () => {
|
|
173
|
+
it('pick a value for the stage, falling back to the default', () => {
|
|
174
|
+
expect(app.select({ prod: 'live', default: 'test' })).toBe('live');
|
|
175
|
+
expect(stack.select({ staging: 'test', default: 'dev' } as never)).toBe(
|
|
176
|
+
'dev',
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('name subdomains and URLs under the root domain', () => {
|
|
181
|
+
expect(stack.domain).toBe('shop.test');
|
|
182
|
+
expect(stack.getSubdomain('api')).toBe('api.shop.test');
|
|
183
|
+
expect(stack.getURL('api')).toBe('https://api.shop.test');
|
|
184
|
+
expect(stack.getURL()).toBe('https://shop.test');
|
|
185
|
+
expect(app.getURL()).toBe('https://shop.test');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('prefix physical names with the stage, app and stack', () => {
|
|
189
|
+
expect(app.logicalPrefixedName('Uploads')).toBe('prod-shop-uploads');
|
|
190
|
+
expect(stack.logicalPrefixedName('Uploads')).toBe('prod-shop-jobs-uploads');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Cache, CacheNeedsProvider, CacheNeedsVpc } from '../aws/Cache';
|
|
3
|
+
import { Database } from '../aws/Database';
|
|
4
|
+
import { DatabaseReader, DatabaseSchema } from '../aws/DerivedDatabase';
|
|
5
|
+
import { Email } from '../aws/Email';
|
|
6
|
+
import { FileServer } from '../aws/FileServer';
|
|
7
|
+
import { Queue } from '../aws/Queue';
|
|
8
|
+
import { RestApiSurface } from '../aws/RestApiSurface';
|
|
9
|
+
import { Secret } from '../aws/Secret';
|
|
10
|
+
import { StaticSite } from '../aws/StaticSite';
|
|
11
|
+
import { Storage } from '../aws/Storage';
|
|
12
|
+
import { Topic } from '../aws/Topic';
|
|
13
|
+
import { ResourceType } from '../Linkable';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* What a linked function receives from each component, and the type that
|
|
17
|
+
* decides which variables a link provides. `getSSTLink()` is the payload SST
|
|
18
|
+
* injects into every handler linked to the resource; `_type` is what
|
|
19
|
+
* `LinkedEnvironment` keys the variable names off.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const stack = {} as never;
|
|
23
|
+
const vpc = { subnets: ['subnet-1'], securityGroups: ['sg-1'] } as never;
|
|
24
|
+
|
|
25
|
+
/** Resolves `$util.output(promise)`, which the stub leaves as the promise. */
|
|
26
|
+
const settle = async (value: unknown) =>
|
|
27
|
+
(value as { apply: (fn: (v: unknown) => unknown) => unknown }).apply(
|
|
28
|
+
(v) => v,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
describe('link payloads', () => {
|
|
32
|
+
it('a queue adds its ARN and connection string to SST’s own link', () => {
|
|
33
|
+
const queue = new Queue(stack, 'Emails');
|
|
34
|
+
|
|
35
|
+
expect(queue._type).toBe(ResourceType.Queue);
|
|
36
|
+
// The connection string itself is parsed back in provides.spec; here, the
|
|
37
|
+
// link carries exactly what `provides()` resolved.
|
|
38
|
+
expect(queue.getSSTLink().properties).toEqual({
|
|
39
|
+
name: 'Emails',
|
|
40
|
+
arn: queue.arn,
|
|
41
|
+
...queue.provides(),
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('a topic carries its publisher connection string', () => {
|
|
46
|
+
const topic = new Topic(stack, 'Users');
|
|
47
|
+
|
|
48
|
+
expect(topic._type).toBe(ResourceType.SnsTopic);
|
|
49
|
+
expect(topic.getSSTLink().properties).toEqual({
|
|
50
|
+
name: 'Users',
|
|
51
|
+
...topic.provides(),
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('a secret carries its value', () => {
|
|
56
|
+
const secret = new Secret(stack, 'AuthSecret');
|
|
57
|
+
|
|
58
|
+
expect(secret._type).toBe(ResourceType.SSTSecret);
|
|
59
|
+
expect(secret.getSSTLink().properties).toMatchObject(secret.provides());
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('a mailer carries its URL and sending identity, and nothing else', () => {
|
|
63
|
+
const email = new Email(stack, 'Mail', {
|
|
64
|
+
backend: 'smtp',
|
|
65
|
+
url: 'smtp://relay.test:587',
|
|
66
|
+
from: 'hello@shop.test',
|
|
67
|
+
} as never);
|
|
68
|
+
|
|
69
|
+
expect(email._type).toBe(ResourceType.Email);
|
|
70
|
+
expect(email.getSSTLink()).toEqual({
|
|
71
|
+
properties: { url: 'smtp://relay.test:587', from: 'hello@shop.test' },
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('a file server routes everything to its bucket, and links its URL', () => {
|
|
76
|
+
const origin = new Storage(stack, 'Uploads');
|
|
77
|
+
const server = new FileServer(stack, 'Files', { origin } as never);
|
|
78
|
+
|
|
79
|
+
expect(origin._type).toBe(ResourceType.ObjectStorage);
|
|
80
|
+
expect(origin._id).toBe('Uploads');
|
|
81
|
+
expect(server._type).toBe(ResourceType.FileServer);
|
|
82
|
+
expect((server as unknown as { routed: unknown[] }).routed).toEqual([
|
|
83
|
+
{ pattern: '/*', bucket: origin },
|
|
84
|
+
]);
|
|
85
|
+
expect(server.getSSTLink().properties).toMatchObject({
|
|
86
|
+
url: server.url,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('a site is built the way its variant builds, and links its URL', () => {
|
|
91
|
+
const vite = new StaticSite(stack, 'Web');
|
|
92
|
+
const next = new StaticSite(stack, 'Admin', {
|
|
93
|
+
variant: 'next',
|
|
94
|
+
build: { command: 'pnpm build:custom', output: 'out' },
|
|
95
|
+
} as never);
|
|
96
|
+
const args = (site: unknown) => (site as { args: { build: unknown } }).args;
|
|
97
|
+
|
|
98
|
+
expect(vite._type).toBe(ResourceType.StaticSite);
|
|
99
|
+
expect(args(vite).build).toEqual(expect.objectContaining({}));
|
|
100
|
+
// A build the caller supplied wins over the variant's.
|
|
101
|
+
expect(args(next).build).toEqual({
|
|
102
|
+
command: 'pnpm build:custom',
|
|
103
|
+
output: 'out',
|
|
104
|
+
});
|
|
105
|
+
expect(vite.getSSTLink().properties).toMatchObject({ url: vite.url });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('a surface links its address and, once resolved, who may call it', async () => {
|
|
109
|
+
const bare = new RestApiSurface(stack, 'Api');
|
|
110
|
+
const called = new RestApiSurface(stack, 'Api', {
|
|
111
|
+
callers: Promise.resolve({
|
|
112
|
+
trustedOrigins: 'https://web.shop.test',
|
|
113
|
+
cookieDomain: 'shop.test',
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(bare._type).toBe(ResourceType.ApiGatewayV2);
|
|
118
|
+
expect(await settle(bare.provides().trustedOrigins)).toBe('');
|
|
119
|
+
expect(await settle(bare.provides().cookieDomain)).toBe('');
|
|
120
|
+
|
|
121
|
+
const link = called.getSSTLink().properties as Record<string, unknown>;
|
|
122
|
+
expect(link.url).toBe(called.url);
|
|
123
|
+
expect(await settle(link.trustedOrigins)).toBe('https://web.shop.test');
|
|
124
|
+
expect(await settle(link.cookieDomain)).toBe('shop.test');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('a database and the nodes derived from it each link only their own URL', () => {
|
|
128
|
+
const cluster = new Database(stack, 'Orders', { vpc, schema: 'app' });
|
|
129
|
+
const reader = new DatabaseReader('OrdersReader', cluster);
|
|
130
|
+
const tenant = new DatabaseSchema('AuthDb', cluster, 'authdb');
|
|
131
|
+
|
|
132
|
+
expect(cluster._type).toBe(ResourceType.SSTPostgres);
|
|
133
|
+
expect(reader._type).toBe(ResourceType.SSTPostgres);
|
|
134
|
+
expect(tenant._type).toBe(ResourceType.SSTPostgres);
|
|
135
|
+
expect(cluster.getSSTLink().properties).toMatchObject({
|
|
136
|
+
url: cluster.provides().url,
|
|
137
|
+
});
|
|
138
|
+
// Least privilege: the reader's link is its URL and not the writer's.
|
|
139
|
+
expect(reader.getSSTLink()).toEqual({
|
|
140
|
+
properties: { url: reader.provides().url },
|
|
141
|
+
});
|
|
142
|
+
expect(tenant.getSSTLink()).toEqual({
|
|
143
|
+
properties: { url: tenant.provides().url },
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('a cache given its URL links it as-is', () => {
|
|
148
|
+
const cache = new Cache(stack, 'Sessions', {
|
|
149
|
+
backend: 'db',
|
|
150
|
+
url: 'postgres://db.test/app',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(cache._type).toBe(ResourceType.Cache);
|
|
154
|
+
expect(cache.getSSTLink()).toEqual({
|
|
155
|
+
properties: { url: 'postgres://db.test/app' },
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('Cache on Upstash', () => {
|
|
161
|
+
/** `sst add upstash` installs this global; here, a recording stand-in. */
|
|
162
|
+
const created: { name: string; args: Record<string, unknown> }[] = [];
|
|
163
|
+
class RedisDatabase {
|
|
164
|
+
readonly endpoint = 'eu1-cache.upstash.io';
|
|
165
|
+
readonly restToken = 'tok/en+1';
|
|
166
|
+
constructor(name: string, args: Record<string, unknown>) {
|
|
167
|
+
created.push({ name, args });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
it('creates a TLS database and puts its token in the URL', () => {
|
|
172
|
+
(globalThis as Record<string, unknown>).upstash = { RedisDatabase };
|
|
173
|
+
try {
|
|
174
|
+
const defaulted = new Cache(stack, 'Sessions', { backend: 'upstash' });
|
|
175
|
+
new Cache(stack, 'Rates', { backend: 'upstash', region: 'us-east-1' });
|
|
176
|
+
|
|
177
|
+
expect(created).toEqual([
|
|
178
|
+
{
|
|
179
|
+
name: 'SessionsCache',
|
|
180
|
+
args: { databaseName: 'Sessions', region: 'eu-west-1', tls: true },
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'RatesCache',
|
|
184
|
+
args: { databaseName: 'Rates', region: 'us-east-1', tls: true },
|
|
185
|
+
},
|
|
186
|
+
]);
|
|
187
|
+
// The address and the credential that opens it are one fact.
|
|
188
|
+
expect(defaulted.provides().url).toBe(
|
|
189
|
+
`https://:${encodeURIComponent('tok/en+1')}@eu1-cache.upstash.io`,
|
|
190
|
+
);
|
|
191
|
+
} finally {
|
|
192
|
+
delete (globalThis as Record<string, unknown>).upstash;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('Cache provisioning refusals', () => {
|
|
198
|
+
it('asks for the Upstash provider when it is not installed', () => {
|
|
199
|
+
expect(() => new Cache(stack, 'Sessions', { backend: 'upstash' })).toThrow(
|
|
200
|
+
CacheNeedsProvider,
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('asks for a VPC before creating an ElastiCache', () => {
|
|
205
|
+
expect(
|
|
206
|
+
() => new Cache(stack, 'Sessions', { backend: 'elasticache' }),
|
|
207
|
+
).toThrow(CacheNeedsVpc);
|
|
208
|
+
});
|
|
209
|
+
});
|
package/src/sst/aws/Database.ts
CHANGED
|
@@ -84,7 +84,21 @@ export class Database<
|
|
|
84
84
|
* produces a database that looks empty.
|
|
85
85
|
*/
|
|
86
86
|
provides(): Record<string, $util.Input<string>> {
|
|
87
|
-
return { url: this.urlFor({}) };
|
|
87
|
+
return { url: this.urlFor(this.role ? { as: this.role } : {}) };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The runtime role this database's own handlers connect as.
|
|
92
|
+
*
|
|
93
|
+
* Set by the adapter once the bootstrap has registered the role — the
|
|
94
|
+
* cluster exists before any role does, so it cannot be a constructor
|
|
95
|
+
* argument. Until it is set (and under `roles: false`, where it never is),
|
|
96
|
+
* the URL is the master's.
|
|
97
|
+
*/
|
|
98
|
+
private role: { user: string; password: $util.Input<string> } | undefined;
|
|
99
|
+
|
|
100
|
+
connectAs(role: { user: string; password: $util.Input<string> }): void {
|
|
101
|
+
this.role = role;
|
|
88
102
|
}
|
|
89
103
|
|
|
90
104
|
/**
|
package/src/sst/fromManifest.ts
CHANGED
|
@@ -232,13 +232,20 @@ const PROVISIONERS: Partial<Record<DeclarationKind, Provisioner>> = {
|
|
|
232
232
|
const bootstrap = bootstrapFor(cluster, d.id, context);
|
|
233
233
|
const runtime = roleNameFor(d, context);
|
|
234
234
|
|
|
235
|
-
bootstrap?.add({
|
|
235
|
+
const credentials = bootstrap?.add({
|
|
236
236
|
id: d.id,
|
|
237
237
|
schema: d.schema,
|
|
238
238
|
runtime,
|
|
239
239
|
owner: ownerRole(runtime),
|
|
240
240
|
...(hasReader(d.id, context) ? { reader: readerRole(runtime) } : {}),
|
|
241
241
|
});
|
|
242
|
+
|
|
243
|
+
// Registering the role was half of it: its handlers have to connect as
|
|
244
|
+
// it too, or the DDL creates a role nothing uses and every handler
|
|
245
|
+
// linked to the database still holds the master.
|
|
246
|
+
if (credentials) {
|
|
247
|
+
cluster.connectAs({ user: runtime, password: credentials.runtime });
|
|
248
|
+
}
|
|
242
249
|
}
|
|
243
250
|
|
|
244
251
|
return cluster;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
//#region src/utils/index.d.ts
|
|
2
|
-
declare function getLocalIpAddress(): string | null;
|
|
3
|
-
declare function environmentCase(name: string): string;
|
|
4
|
-
declare enum ResourceType {
|
|
5
|
-
ApiGatewayV2 = "sst.aws.ApiGatewayV2",
|
|
6
|
-
Postgres = "sst.aws.Postgres",
|
|
7
|
-
Function = "sst.aws.Function",
|
|
8
|
-
ObjectStorage = "sst.aws.Bucket",
|
|
9
|
-
Vpc = "sst.aws.Vpc",
|
|
10
|
-
Secret = "sst.sst.Secret",
|
|
11
|
-
SSTSecret = "sst:sst:Secret",
|
|
12
|
-
SSTFunction = "sst:sst:Function",
|
|
13
|
-
SSTApiGatewayV2 = "sst:aws:ApiGatewayV2",
|
|
14
|
-
SSTPostgres = "sst:aws:Postgres",
|
|
15
|
-
SSTObjectStorage = "sst:aws:Bucket",
|
|
16
|
-
SnsTopic = "sst:aws:SnsTopic",
|
|
17
|
-
}
|
|
18
|
-
declare function buildResourceEnv(record: Record<string, Resource | string>): Record<string, string>;
|
|
19
|
-
/**
|
|
20
|
-
* SST links a resource under one of two spellings — `sst.aws.Bucket` when it
|
|
21
|
-
* comes from the state file, `sst:aws:Bucket` when it comes from the runtime —
|
|
22
|
-
* and `processors` has always handled both. Only the dotted half was admitted
|
|
23
|
-
* here, so the colon half could not be passed to `buildResourceEnv` without a
|
|
24
|
-
* cast, and `SnsTopic` was missing from `Resource` altogether despite being the
|
|
25
|
-
* only thing that produces `*_ARN`.
|
|
26
|
-
*/
|
|
27
|
-
type ApiGatewayV2 = {
|
|
28
|
-
type: ResourceType.ApiGatewayV2 | ResourceType.SSTApiGatewayV2;
|
|
29
|
-
url: string;
|
|
30
|
-
};
|
|
31
|
-
type Postgres = {
|
|
32
|
-
database: string;
|
|
33
|
-
host: string;
|
|
34
|
-
password: string;
|
|
35
|
-
port: number;
|
|
36
|
-
type: ResourceType.Postgres | ResourceType.SSTPostgres;
|
|
37
|
-
username: string;
|
|
38
|
-
};
|
|
39
|
-
type Function = {
|
|
40
|
-
name: string;
|
|
41
|
-
type: ResourceType.Function | ResourceType.SSTFunction;
|
|
42
|
-
};
|
|
43
|
-
type Bucket = {
|
|
44
|
-
name: string;
|
|
45
|
-
type: ResourceType.ObjectStorage | ResourceType.SSTObjectStorage;
|
|
46
|
-
};
|
|
47
|
-
type SnsTopic = {
|
|
48
|
-
arn: string;
|
|
49
|
-
type: ResourceType.SnsTopic;
|
|
50
|
-
};
|
|
51
|
-
type Vpc = {
|
|
52
|
-
bastion: string;
|
|
53
|
-
type: ResourceType.Vpc;
|
|
54
|
-
};
|
|
55
|
-
type Secret = {
|
|
56
|
-
type: ResourceType.Secret | ResourceType.SSTSecret;
|
|
57
|
-
value: string;
|
|
58
|
-
};
|
|
59
|
-
type Resource = ApiGatewayV2 | Postgres | Function | Bucket | SnsTopic | Vpc | Secret;
|
|
60
|
-
type ResourceProcessor<K extends Resource> = (name: string, value: K) => Record<string, string | number>;
|
|
61
|
-
//# sourceMappingURL=index.d.ts.map
|
|
62
|
-
//#endregion
|
|
63
|
-
export { ApiGatewayV2, Bucket, Function, Postgres, Resource, ResourceProcessor, ResourceType, Secret, SnsTopic, Vpc, buildResourceEnv, environmentCase, getLocalIpAddress };
|
|
64
|
-
//# sourceMappingURL=index-B5CZ1xVf.d.cts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-B5CZ1xVf.d.cts","names":[],"sources":["../src/utils/index.ts"],"sourcesContent":[],"mappings":";iBAGgB,iBAAA,CAAA;AAAA,iBAcA,eAAA,CAdiB,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAcjB,aAOJ,YAAA;EAAA,YAAA,GAAY,sBAAA;EA+DR,QAAA,GAAA,kBAAgB;EAAA,QAAA,GAAA,kBAAA;EAAA,aACR,GAAA,gBAAA;EAAQ,GAAvB,GAAA,aAAA;EAAM,MACZ,GAAA,gBAAA;EAAM,SAAA,GAAA,gBAAA;EA0BG,WAAA,GAAA,kBAAY;EAAA,eAAA,GAAA,sBAAA;EAAA,WACjB,GAAA,kBAAa;EAAY,gBAAgB,GAAA,gBAAA;EAAe,QAAA,GAAA,kBAAA;AAI/D;AAAoB,iBAjCJ,gBAAA,CAiCI,MAAA,EAhCX,MAgCW,CAAA,MAAA,EAhCI,QAgCJ,GAAA,MAAA,CAAA,CAAA,EA/BjB,MA+BiB,CAAA,MAAA,EAAA,MAAA,CAAA;;;AAKmC;AAIvD;;;;AAEuD;AAG3C,KAnBA,YAAA,GAmBM;EAAA,IAAA,EAlBX,YAAA,CAAa,YAkBF,GAlBiB,YAAA,CAAa,eAkB9B;EAAA,GAEX,EAAA,MAAA;CAA0B;AAAgC,KAhBrD,QAAA,GAgBqD;EAGrD,QAAA,EAAA,MAAQ;EAKR,IAAA,EAAG,MAAA;EAKH,QAAA,EAAM,MAAA;EAAA,IAAA,EAAA,MAAA;EAAA,IACX,EAzBA,YAAA,CAAa,QAyBA,GAzBW,YAAA,CAAa,WAyBxB;EAAM,QAAG,EAAA,MAAa;AAAS,CAAA;AAIvC,KAzBA,QAAA,GAyBQ;EAAA,IAAA,EAAA,MAAA;EAAA,IACjB,EAxBI,YAAA,CAAa,QAwBjB,GAxB4B,YAAA,CAAa,WAwBzC;CAAY;AAEZ,KAvBS,MAAA,GAuBT;EAAQ,IACR,EAAA,MAAA;EAAM,IACN,EAvBI,YAAA,CAAa,aAuBjB,GAvBiC,YAAA,CAAa,gBAuB9C;CAAQ;AAER,KAtBS,QAAA,GAsBT;EAAM,GAAA,EAAA,MAAA;EAEG,IAAA,EAtBL,YAAA,CAAa,QAsBS;CAAA;AAAW,KAnB5B,GAAA,GAmB4B;EAAQ,OAExC,EAAA,MAAA;EAAC,IACJ,EApBE,YAAA,CAAa,GAoBf;AAAM,CAAA;KAjBC,MAAA;QACL,YAAA,CAAa,SAAS,YAAA,CAAa;;;KAI9B,QAAA,GACT,eACA,WACA,WACA,SACA,WACA,MACA;KAES,4BAA4B,kCAEhC,MACH"}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
//#region src/utils/index.d.ts
|
|
2
|
-
declare function getLocalIpAddress(): string | null;
|
|
3
|
-
declare function environmentCase(name: string): string;
|
|
4
|
-
declare enum ResourceType {
|
|
5
|
-
ApiGatewayV2 = "sst.aws.ApiGatewayV2",
|
|
6
|
-
Postgres = "sst.aws.Postgres",
|
|
7
|
-
Function = "sst.aws.Function",
|
|
8
|
-
ObjectStorage = "sst.aws.Bucket",
|
|
9
|
-
Vpc = "sst.aws.Vpc",
|
|
10
|
-
Secret = "sst.sst.Secret",
|
|
11
|
-
SSTSecret = "sst:sst:Secret",
|
|
12
|
-
SSTFunction = "sst:sst:Function",
|
|
13
|
-
SSTApiGatewayV2 = "sst:aws:ApiGatewayV2",
|
|
14
|
-
SSTPostgres = "sst:aws:Postgres",
|
|
15
|
-
SSTObjectStorage = "sst:aws:Bucket",
|
|
16
|
-
SnsTopic = "sst:aws:SnsTopic",
|
|
17
|
-
}
|
|
18
|
-
declare function buildResourceEnv(record: Record<string, Resource | string>): Record<string, string>;
|
|
19
|
-
/**
|
|
20
|
-
* SST links a resource under one of two spellings — `sst.aws.Bucket` when it
|
|
21
|
-
* comes from the state file, `sst:aws:Bucket` when it comes from the runtime —
|
|
22
|
-
* and `processors` has always handled both. Only the dotted half was admitted
|
|
23
|
-
* here, so the colon half could not be passed to `buildResourceEnv` without a
|
|
24
|
-
* cast, and `SnsTopic` was missing from `Resource` altogether despite being the
|
|
25
|
-
* only thing that produces `*_ARN`.
|
|
26
|
-
*/
|
|
27
|
-
type ApiGatewayV2 = {
|
|
28
|
-
type: ResourceType.ApiGatewayV2 | ResourceType.SSTApiGatewayV2;
|
|
29
|
-
url: string;
|
|
30
|
-
};
|
|
31
|
-
type Postgres = {
|
|
32
|
-
database: string;
|
|
33
|
-
host: string;
|
|
34
|
-
password: string;
|
|
35
|
-
port: number;
|
|
36
|
-
type: ResourceType.Postgres | ResourceType.SSTPostgres;
|
|
37
|
-
username: string;
|
|
38
|
-
};
|
|
39
|
-
type Function = {
|
|
40
|
-
name: string;
|
|
41
|
-
type: ResourceType.Function | ResourceType.SSTFunction;
|
|
42
|
-
};
|
|
43
|
-
type Bucket = {
|
|
44
|
-
name: string;
|
|
45
|
-
type: ResourceType.ObjectStorage | ResourceType.SSTObjectStorage;
|
|
46
|
-
};
|
|
47
|
-
type SnsTopic = {
|
|
48
|
-
arn: string;
|
|
49
|
-
type: ResourceType.SnsTopic;
|
|
50
|
-
};
|
|
51
|
-
type Vpc = {
|
|
52
|
-
bastion: string;
|
|
53
|
-
type: ResourceType.Vpc;
|
|
54
|
-
};
|
|
55
|
-
type Secret = {
|
|
56
|
-
type: ResourceType.Secret | ResourceType.SSTSecret;
|
|
57
|
-
value: string;
|
|
58
|
-
};
|
|
59
|
-
type Resource = ApiGatewayV2 | Postgres | Function | Bucket | SnsTopic | Vpc | Secret;
|
|
60
|
-
type ResourceProcessor<K extends Resource> = (name: string, value: K) => Record<string, string | number>;
|
|
61
|
-
//# sourceMappingURL=index.d.ts.map
|
|
62
|
-
//#endregion
|
|
63
|
-
export { ApiGatewayV2, Bucket, Function, Postgres, Resource, ResourceProcessor, ResourceType, Secret, SnsTopic, Vpc, buildResourceEnv, environmentCase, getLocalIpAddress };
|
|
64
|
-
//# sourceMappingURL=index-DhHRjduZ.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-DhHRjduZ.d.mts","names":[],"sources":["../src/utils/index.ts"],"sourcesContent":[],"mappings":";iBAGgB,iBAAA,CAAA;AAAA,iBAcA,eAAA,CAdiB,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAcjB,aAOJ,YAAA;EAAA,YAAA,GAAY,sBAAA;EA+DR,QAAA,GAAA,kBAAgB;EAAA,QAAA,GAAA,kBAAA;EAAA,aACR,GAAA,gBAAA;EAAQ,GAAvB,GAAA,aAAA;EAAM,MACZ,GAAA,gBAAA;EAAM,SAAA,GAAA,gBAAA;EA0BG,WAAA,GAAA,kBAAY;EAAA,eAAA,GAAA,sBAAA;EAAA,WACjB,GAAA,kBAAa;EAAY,gBAAgB,GAAA,gBAAA;EAAe,QAAA,GAAA,kBAAA;AAI/D;AAAoB,iBAjCJ,gBAAA,CAiCI,MAAA,EAhCX,MAgCW,CAAA,MAAA,EAhCI,QAgCJ,GAAA,MAAA,CAAA,CAAA,EA/BjB,MA+BiB,CAAA,MAAA,EAAA,MAAA,CAAA;;;AAKmC;AAIvD;;;;AAEuD;AAG3C,KAnBA,YAAA,GAmBM;EAAA,IAAA,EAlBX,YAAA,CAAa,YAkBF,GAlBiB,YAAA,CAAa,eAkB9B;EAAA,GAEX,EAAA,MAAA;CAA0B;AAAgC,KAhBrD,QAAA,GAgBqD;EAGrD,QAAA,EAAA,MAAQ;EAKR,IAAA,EAAG,MAAA;EAKH,QAAA,EAAM,MAAA;EAAA,IAAA,EAAA,MAAA;EAAA,IACX,EAzBA,YAAA,CAAa,QAyBA,GAzBW,YAAA,CAAa,WAyBxB;EAAM,QAAG,EAAA,MAAa;AAAS,CAAA;AAIvC,KAzBA,QAAA,GAyBQ;EAAA,IAAA,EAAA,MAAA;EAAA,IACjB,EAxBI,YAAA,CAAa,QAwBjB,GAxB4B,YAAA,CAAa,WAwBzC;CAAY;AAEZ,KAvBS,MAAA,GAuBT;EAAQ,IACR,EAAA,MAAA;EAAM,IACN,EAvBI,YAAA,CAAa,aAuBjB,GAvBiC,YAAA,CAAa,gBAuB9C;CAAQ;AAER,KAtBS,QAAA,GAsBT;EAAM,GAAA,EAAA,MAAA;EAEG,IAAA,EAtBL,YAAA,CAAa,QAsBS;CAAA;AAAW,KAnB5B,GAAA,GAmB4B;EAAQ,OAExC,EAAA,MAAA;EAAC,IACJ,EApBE,YAAA,CAAa,GAoBf;AAAM,CAAA;KAjBC,MAAA;QACL,YAAA,CAAa,SAAS,YAAA,CAAa;;;KAI9B,QAAA,GACT,eACA,WACA,WACA,SACA,WACA,MACA;KAES,4BAA4B,kCAEhC,MACH"}
|