@freestyle-sh/with-postgres 0.2.9 → 0.2.10
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/README.md +65 -73
- package/dist/index.d.ts +56 -27
- package/dist/index.js +185 -98
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,109 +1,101 @@
|
|
|
1
1
|
# @freestyle-sh/with-postgres
|
|
2
2
|
|
|
3
|
-
PostgreSQL runtime extension for Freestyle VMs.
|
|
3
|
+
PostgreSQL runtime extension for Freestyle VMs. Declaratively configure a PostgreSQL server, databases, and schema/seed scripts that run during VM snapshot setup.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @freestyle-sh/with-postgres freestyle
|
|
8
|
+
npm install @freestyle-sh/with-postgres freestyle
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import { freestyle } from "freestyle
|
|
14
|
+
import { freestyle, VmSpec } from "freestyle";
|
|
15
15
|
import { VmPostgres } from "@freestyle-sh/with-postgres";
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
const pg = new VmPostgres({ password: "secret" });
|
|
18
|
+
|
|
19
|
+
const db = pg.database({ name: "myapp", create: true });
|
|
20
|
+
|
|
21
|
+
const schema = db.script("schema", {
|
|
22
|
+
sql: `
|
|
23
|
+
CREATE TABLE users (
|
|
24
|
+
id SERIAL PRIMARY KEY,
|
|
25
|
+
name VARCHAR(100)
|
|
26
|
+
);
|
|
27
|
+
`,
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
name VARCHAR(100),
|
|
33
|
-
email VARCHAR(100)
|
|
34
|
-
)
|
|
35
|
-
`);
|
|
36
|
-
|
|
37
|
-
// Insert data
|
|
38
|
-
await vm.postgres.exec(`
|
|
39
|
-
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')
|
|
40
|
-
`);
|
|
41
|
-
|
|
42
|
-
// Query data
|
|
43
|
-
const result = await vm.postgres.query<{ id: number; name: string; email: string }>(`
|
|
44
|
-
SELECT * FROM users
|
|
45
|
-
`);
|
|
46
|
-
|
|
47
|
-
console.log(result.rows); // [{ id: 1, name: 'Alice', email: 'alice@example.com' }]
|
|
48
|
-
```
|
|
30
|
+
const seed = db.script("seed", {
|
|
31
|
+
sql: `INSERT INTO users (name) VALUES ('Alice'), ('Bob');`,
|
|
32
|
+
after: [schema],
|
|
33
|
+
});
|
|
49
34
|
|
|
50
|
-
|
|
35
|
+
const spec = new VmSpec()
|
|
36
|
+
.with("postgres", pg)
|
|
37
|
+
.with("db", db)
|
|
38
|
+
.with("schema", schema)
|
|
39
|
+
.with("seed", seed)
|
|
40
|
+
.snapshot();
|
|
51
41
|
|
|
52
|
-
|
|
42
|
+
const { vm } = await freestyle.vms.create({ spec });
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
const result = await vm.db.query<{ id: number; name: string }>(
|
|
45
|
+
`SELECT * FROM users`
|
|
46
|
+
);
|
|
47
|
+
console.log(result.rows); // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
|
|
48
|
+
```
|
|
58
49
|
|
|
59
|
-
|
|
50
|
+
The install, database creation, and each script run as ordered systemd oneshot services during snapshot setup, so everything is baked into the snapshot.
|
|
60
51
|
|
|
61
|
-
|
|
52
|
+
## API
|
|
62
53
|
|
|
63
|
-
|
|
54
|
+
### `new VmPostgres(options?)`
|
|
64
55
|
|
|
65
|
-
|
|
56
|
+
Configures the PostgreSQL server. Pure config — has no runtime methods.
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
`
|
|
71
|
-
|
|
58
|
+
| Option | Type | Default | Description |
|
|
59
|
+
|--------|------|---------|-------------|
|
|
60
|
+
| `version` | `string` | `"18"` | PostgreSQL major version (installed from the official PGDG apt repo) |
|
|
61
|
+
| `password` | `string` | `"postgres"` | Password for the postgres superuser |
|
|
62
|
+
| `user` | `string` | `"postgres"` | PostgreSQL superuser name |
|
|
72
63
|
|
|
73
|
-
|
|
64
|
+
### `pg.database({ name, create? })`
|
|
74
65
|
|
|
75
|
-
|
|
66
|
+
Declares a database. Returns a `Database` you can attach scripts to and query at runtime.
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`
|
|
81
|
-
```
|
|
68
|
+
| Option | Type | Default | Description |
|
|
69
|
+
|--------|------|---------|-------------|
|
|
70
|
+
| `name` | `string` | — | Database name |
|
|
71
|
+
| `create` | `boolean` | `false` | If true, idempotently creates the database during snapshot setup |
|
|
82
72
|
|
|
83
|
-
#### `
|
|
73
|
+
#### `vm.<name>.query<T>(sql)`
|
|
84
74
|
|
|
85
|
-
|
|
75
|
+
Run a SQL query against this database. Returns `{ rows: T[], rowCount, error? }`. Results are returned as a JSON array (psql wraps the query in `json_agg(row_to_json(...))`).
|
|
86
76
|
|
|
87
|
-
|
|
88
|
-
await vm.postgres.createDatabase("newdb");
|
|
89
|
-
```
|
|
77
|
+
#### `vm.<name>.exec(sql)`
|
|
90
78
|
|
|
91
|
-
|
|
79
|
+
Run a SQL command without returning rows. Returns `{ success, error? }`.
|
|
92
80
|
|
|
93
|
-
|
|
81
|
+
### `db.script(name, { sql, after? })`
|
|
94
82
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
Declares a SQL script that runs once during snapshot setup against this database.
|
|
84
|
+
|
|
85
|
+
| Option | Type | Default | Description |
|
|
86
|
+
|--------|------|---------|-------------|
|
|
87
|
+
| `sql` | `string` | — | Inline SQL to execute |
|
|
88
|
+
| `after` | `DatabaseScript[]` | `[]` | Scripts that must run before this one |
|
|
89
|
+
|
|
90
|
+
Each script becomes a systemd oneshot with `ON_ERROR_STOP=1`, so the snapshot fails fast if any SQL errors. Scripts depend automatically on the database's create service (or on `install-postgres` if `create: false`), and on every script listed in `after`.
|
|
98
91
|
|
|
99
|
-
|
|
92
|
+
#### `vm.<name>.logs()`
|
|
100
93
|
|
|
101
|
-
|
|
94
|
+
Returns the journalctl output for the script's systemd service as `string[]`.
|
|
102
95
|
|
|
103
|
-
|
|
104
|
-
2. Configures password authentication
|
|
105
|
-
3. Creates the default database (if specified)
|
|
106
|
-
4. Enables network connections
|
|
107
|
-
5. Starts PostgreSQL as a system service
|
|
96
|
+
## How it works
|
|
108
97
|
|
|
109
|
-
|
|
98
|
+
1. `VmPostgres` adds the official PGDG apt repository and installs the requested PostgreSQL version (see https://www.postgresql.org/download/linux/debian/), then sets the superuser password and enables md5 password auth on TCP and the local socket.
|
|
99
|
+
2. Each `database({ create: true })` adds a oneshot that creates the database if it doesn't exist.
|
|
100
|
+
3. Each `script(...)` writes its SQL to `/opt/pg-scripts/<db>/<name>.sql` and adds a oneshot that runs `psql -f` against the right database, in the order you declared via `after`.
|
|
101
|
+
4. All setup oneshots use `deleteAfterSuccess: true`, so they don't re-run on reboot from the snapshot.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,54 +1,83 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { VmWith, VmWithInstance, VmSpec } from 'freestyle';
|
|
2
2
|
|
|
3
3
|
interface PostgresOptions {
|
|
4
4
|
version?: string;
|
|
5
5
|
password?: string;
|
|
6
|
-
database?: string;
|
|
7
6
|
user?: string;
|
|
8
7
|
}
|
|
8
|
+
interface ResolvedPostgresOptions {
|
|
9
|
+
version: string;
|
|
10
|
+
password: string;
|
|
11
|
+
user: string;
|
|
12
|
+
}
|
|
9
13
|
interface QueryResult<T = any> {
|
|
10
14
|
rows: T[];
|
|
11
15
|
rowCount: number;
|
|
12
16
|
error?: string;
|
|
13
17
|
}
|
|
18
|
+
interface DatabaseOptions {
|
|
19
|
+
name: string;
|
|
20
|
+
create?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface ScriptOptions {
|
|
23
|
+
sql: string;
|
|
24
|
+
after?: DatabaseScript[];
|
|
25
|
+
}
|
|
26
|
+
declare class VmPostgres extends VmWith<VmPostgresInstance> {
|
|
27
|
+
options: ResolvedPostgresOptions;
|
|
28
|
+
constructor(options?: PostgresOptions);
|
|
29
|
+
database(options: DatabaseOptions): Database;
|
|
30
|
+
installServiceName(): string;
|
|
31
|
+
configureSnapshotSpec(spec: VmSpec): VmSpec;
|
|
32
|
+
createInstance(): VmPostgresInstance;
|
|
33
|
+
}
|
|
14
34
|
declare class VmPostgresInstance extends VmWithInstance {
|
|
15
|
-
|
|
35
|
+
}
|
|
36
|
+
declare class Database extends VmWith<DatabaseInstance> {
|
|
37
|
+
options: DatabaseOptions;
|
|
38
|
+
postgres: VmPostgres;
|
|
39
|
+
constructor(options: DatabaseOptions, postgres: VmPostgres);
|
|
40
|
+
script(name: string, options: ScriptOptions): DatabaseScript;
|
|
41
|
+
getCreateServiceName(): string;
|
|
42
|
+
configureSnapshotSpec(spec: VmSpec): VmSpec;
|
|
43
|
+
createInstance(): DatabaseInstance;
|
|
44
|
+
}
|
|
45
|
+
declare class DatabaseInstance extends VmWithInstance {
|
|
16
46
|
private database;
|
|
17
47
|
private user;
|
|
18
|
-
|
|
48
|
+
private password;
|
|
49
|
+
constructor(opts: {
|
|
50
|
+
database: string;
|
|
51
|
+
user: string;
|
|
52
|
+
password: string;
|
|
53
|
+
});
|
|
19
54
|
/**
|
|
20
|
-
* Execute a SQL query and return the results
|
|
55
|
+
* Execute a SQL query against this database and return the results.
|
|
21
56
|
*/
|
|
22
57
|
query<T = any>(sql: string): Promise<QueryResult<T>>;
|
|
23
58
|
/**
|
|
24
|
-
* Execute a SQL command without returning results (
|
|
59
|
+
* Execute a SQL command without returning results (CREATE, INSERT, UPDATE, DELETE, etc.).
|
|
25
60
|
*/
|
|
26
61
|
exec(sql: string): Promise<{
|
|
27
62
|
success: boolean;
|
|
28
63
|
error?: string;
|
|
29
64
|
}>;
|
|
30
|
-
/**
|
|
31
|
-
* Create a new database
|
|
32
|
-
*/
|
|
33
|
-
createDatabase(dbName: string): Promise<{
|
|
34
|
-
success: boolean;
|
|
35
|
-
error?: string;
|
|
36
|
-
}>;
|
|
37
|
-
/**
|
|
38
|
-
* Drop a database
|
|
39
|
-
*/
|
|
40
|
-
dropDatabase(dbName: string): Promise<{
|
|
41
|
-
success: boolean;
|
|
42
|
-
error?: string;
|
|
43
|
-
}>;
|
|
44
65
|
}
|
|
45
|
-
declare class
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
declare class DatabaseScript extends VmWith<DatabaseScriptInstance> {
|
|
67
|
+
name: string;
|
|
68
|
+
database: Database;
|
|
69
|
+
options: ScriptOptions;
|
|
70
|
+
constructor(name: string, database: Database, options: ScriptOptions);
|
|
71
|
+
getServiceName(): string;
|
|
72
|
+
getScriptPath(): string;
|
|
48
73
|
configureSnapshotSpec(spec: VmSpec): VmSpec;
|
|
49
|
-
createInstance():
|
|
50
|
-
|
|
74
|
+
createInstance(): DatabaseScriptInstance;
|
|
75
|
+
}
|
|
76
|
+
declare class DatabaseScriptInstance extends VmWithInstance {
|
|
77
|
+
private serviceName;
|
|
78
|
+
constructor(serviceName: string);
|
|
79
|
+
logs(): Promise<string[] | undefined>;
|
|
51
80
|
}
|
|
52
81
|
|
|
53
|
-
export { VmPostgres, VmPostgresInstance };
|
|
54
|
-
export type { PostgresOptions, QueryResult };
|
|
82
|
+
export { Database, DatabaseInstance, DatabaseScript, DatabaseScriptInstance, VmPostgres, VmPostgresInstance };
|
|
83
|
+
export type { DatabaseOptions, PostgresOptions, QueryResult, ScriptOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,149 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { VmWith, VmSpec, VmWithInstance } from 'freestyle';
|
|
2
2
|
|
|
3
|
+
class VmPostgres extends VmWith {
|
|
4
|
+
options;
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
super();
|
|
7
|
+
this.options = {
|
|
8
|
+
version: options.version || "18",
|
|
9
|
+
password: options.password || "postgres",
|
|
10
|
+
user: options.user || "postgres"
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
database(options) {
|
|
14
|
+
return new Database(options, this);
|
|
15
|
+
}
|
|
16
|
+
installServiceName() {
|
|
17
|
+
return "install-postgres.service";
|
|
18
|
+
}
|
|
19
|
+
configureSnapshotSpec(spec) {
|
|
20
|
+
const installScript = `#!/bin/bash
|
|
21
|
+
set -e
|
|
22
|
+
|
|
23
|
+
# Add the official PostgreSQL apt repository (PGDG) so any
|
|
24
|
+
# PostgreSQL version is available on any Debian release.
|
|
25
|
+
# See https://www.postgresql.org/download/linux/debian/
|
|
26
|
+
sudo apt-get update
|
|
27
|
+
sudo apt-get install -y curl ca-certificates
|
|
28
|
+
sudo install -d /usr/share/postgresql-common/pgdg
|
|
29
|
+
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc
|
|
30
|
+
. /etc/os-release
|
|
31
|
+
sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $VERSION_CODENAME-pgdg main' > /etc/apt/sources.list.d/pgdg.list"
|
|
32
|
+
|
|
33
|
+
# Install PostgreSQL
|
|
34
|
+
sudo apt-get update
|
|
35
|
+
sudo apt-get install -y postgresql-${this.options.version} postgresql-client-${this.options.version}
|
|
36
|
+
|
|
37
|
+
# Start PostgreSQL
|
|
38
|
+
sudo systemctl start postgresql
|
|
39
|
+
|
|
40
|
+
# Set password for postgres user
|
|
41
|
+
sudo -u postgres psql -c "ALTER USER postgres PASSWORD '${this.options.password}';"
|
|
42
|
+
|
|
43
|
+
# Configure PostgreSQL to accept password authentication
|
|
44
|
+
echo "host all all 0.0.0.0/0 md5" | sudo tee -a /etc/postgresql/${this.options.version}/main/pg_hba.conf
|
|
45
|
+
echo "local all all md5" | sudo tee -a /etc/postgresql/${this.options.version}/main/pg_hba.conf
|
|
46
|
+
|
|
47
|
+
# Allow connections from all addresses
|
|
48
|
+
sudo sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" /etc/postgresql/${this.options.version}/main/postgresql.conf
|
|
49
|
+
|
|
50
|
+
# Restart PostgreSQL to apply changes
|
|
51
|
+
sudo systemctl restart postgresql
|
|
52
|
+
|
|
53
|
+
# Enable PostgreSQL to start on boot
|
|
54
|
+
sudo systemctl enable postgresql
|
|
55
|
+
`;
|
|
56
|
+
return this.composeSpecs(
|
|
57
|
+
spec,
|
|
58
|
+
new VmSpec({
|
|
59
|
+
additionalFiles: {
|
|
60
|
+
"/opt/install-postgres.sh": { content: installScript }
|
|
61
|
+
},
|
|
62
|
+
systemd: {
|
|
63
|
+
services: [
|
|
64
|
+
{
|
|
65
|
+
name: "install-postgres",
|
|
66
|
+
mode: "oneshot",
|
|
67
|
+
deleteAfterSuccess: true,
|
|
68
|
+
exec: ["bash /opt/install-postgres.sh"],
|
|
69
|
+
timeoutSec: 600
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
createInstance() {
|
|
77
|
+
return new VmPostgresInstance();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
3
80
|
class VmPostgresInstance extends VmWithInstance {
|
|
4
|
-
|
|
81
|
+
}
|
|
82
|
+
class Database extends VmWith {
|
|
83
|
+
options;
|
|
84
|
+
postgres;
|
|
85
|
+
constructor(options, postgres) {
|
|
86
|
+
super();
|
|
87
|
+
this.options = options;
|
|
88
|
+
this.postgres = postgres;
|
|
89
|
+
}
|
|
90
|
+
script(name, options) {
|
|
91
|
+
return new DatabaseScript(name, this, options);
|
|
92
|
+
}
|
|
93
|
+
getCreateServiceName() {
|
|
94
|
+
return `pg-create-${this.options.name}`;
|
|
95
|
+
}
|
|
96
|
+
configureSnapshotSpec(spec) {
|
|
97
|
+
if (!this.options.create) return spec;
|
|
98
|
+
const { password, user } = this.postgres.options;
|
|
99
|
+
const dbName = this.options.name;
|
|
100
|
+
const psqlBase = `PGPASSWORD='${password}' psql -h 127.0.0.1 -U ${user} -d postgres`;
|
|
101
|
+
const createCmd = `${psqlBase} -tAc "SELECT 1 FROM pg_database WHERE datname='${dbName}'" | grep -q 1 || ${psqlBase} -c 'CREATE DATABASE ${dbName}'`;
|
|
102
|
+
return this.composeSpecs(
|
|
103
|
+
spec,
|
|
104
|
+
new VmSpec({
|
|
105
|
+
systemd: {
|
|
106
|
+
services: [
|
|
107
|
+
{
|
|
108
|
+
name: this.getCreateServiceName(),
|
|
109
|
+
mode: "oneshot",
|
|
110
|
+
deleteAfterSuccess: true,
|
|
111
|
+
after: [this.postgres.installServiceName()],
|
|
112
|
+
requires: [this.postgres.installServiceName()],
|
|
113
|
+
bash: createCmd,
|
|
114
|
+
timeoutSec: 60
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
createInstance() {
|
|
122
|
+
return new DatabaseInstance({
|
|
123
|
+
database: this.options.name,
|
|
124
|
+
user: this.postgres.options.user,
|
|
125
|
+
password: this.postgres.options.password
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
class DatabaseInstance extends VmWithInstance {
|
|
5
130
|
database;
|
|
6
131
|
user;
|
|
7
|
-
|
|
132
|
+
password;
|
|
133
|
+
constructor(opts) {
|
|
8
134
|
super();
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
135
|
+
this.database = opts.database;
|
|
136
|
+
this.user = opts.user;
|
|
137
|
+
this.password = opts.password;
|
|
12
138
|
}
|
|
13
139
|
/**
|
|
14
|
-
* Execute a SQL query and return the results
|
|
140
|
+
* Execute a SQL query against this database and return the results.
|
|
15
141
|
*/
|
|
16
142
|
async query(sql) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
});
|
|
143
|
+
const wrappedSql = `SELECT COALESCE(json_agg(row_to_json(t)), '[]'::json) FROM (${sql}) t`;
|
|
144
|
+
const escapedSql = wrappedSql.replace(/'/g, "'\\''");
|
|
145
|
+
const command = `PGPASSWORD='${this.password}' psql -h 127.0.0.1 -U ${this.user} -d ${this.database} -t -A -c '${escapedSql}'`;
|
|
146
|
+
const result = await this.vm.exec({ command });
|
|
22
147
|
if (result.statusCode !== 0) {
|
|
23
148
|
return {
|
|
24
149
|
rows: [],
|
|
@@ -41,14 +166,12 @@ class VmPostgresInstance extends VmWithInstance {
|
|
|
41
166
|
}
|
|
42
167
|
}
|
|
43
168
|
/**
|
|
44
|
-
* Execute a SQL command without returning results (
|
|
169
|
+
* Execute a SQL command without returning results (CREATE, INSERT, UPDATE, DELETE, etc.).
|
|
45
170
|
*/
|
|
46
171
|
async exec(sql) {
|
|
47
172
|
const escapedSql = sql.replace(/'/g, "'\\''");
|
|
48
|
-
const command = `PGPASSWORD='${this.password}' psql -U ${this.user} -d ${this.database} -c '${escapedSql}'`;
|
|
49
|
-
const result = await this.vm.exec({
|
|
50
|
-
command
|
|
51
|
-
});
|
|
173
|
+
const command = `PGPASSWORD='${this.password}' psql -h 127.0.0.1 -U ${this.user} -d ${this.database} -c '${escapedSql}'`;
|
|
174
|
+
const result = await this.vm.exec({ command });
|
|
52
175
|
if (result.statusCode !== 0) {
|
|
53
176
|
return {
|
|
54
177
|
success: false,
|
|
@@ -57,98 +180,53 @@ class VmPostgresInstance extends VmWithInstance {
|
|
|
57
180
|
}
|
|
58
181
|
return { success: true };
|
|
59
182
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Create a new database
|
|
62
|
-
*/
|
|
63
|
-
async createDatabase(dbName) {
|
|
64
|
-
const command = `PGPASSWORD='${this.password}' psql -U ${this.user} -d postgres -c 'CREATE DATABASE ${dbName}'`;
|
|
65
|
-
const result = await this.vm.exec({
|
|
66
|
-
command
|
|
67
|
-
});
|
|
68
|
-
if (result.statusCode !== 0) {
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
error: result.stderr || "Database creation failed"
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return { success: true };
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Drop a database
|
|
78
|
-
*/
|
|
79
|
-
async dropDatabase(dbName) {
|
|
80
|
-
const command = `PGPASSWORD='${this.password}' psql -U ${this.user} -d postgres -c 'DROP DATABASE IF EXISTS ${dbName}'`;
|
|
81
|
-
const result = await this.vm.exec({
|
|
82
|
-
command
|
|
83
|
-
});
|
|
84
|
-
if (result.statusCode !== 0) {
|
|
85
|
-
return {
|
|
86
|
-
success: false,
|
|
87
|
-
error: result.stderr || "Database drop failed"
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
return { success: true };
|
|
91
|
-
}
|
|
92
183
|
}
|
|
93
|
-
class
|
|
184
|
+
class DatabaseScript extends VmWith {
|
|
185
|
+
name;
|
|
186
|
+
database;
|
|
94
187
|
options;
|
|
95
|
-
constructor(
|
|
188
|
+
constructor(name, database, options) {
|
|
96
189
|
super();
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
190
|
+
this.name = name;
|
|
191
|
+
this.database = database;
|
|
192
|
+
this.options = options;
|
|
193
|
+
}
|
|
194
|
+
getServiceName() {
|
|
195
|
+
return `pg-script-${this.database.options.name}-${this.name}`;
|
|
196
|
+
}
|
|
197
|
+
getScriptPath() {
|
|
198
|
+
return `/opt/pg-scripts/${this.database.options.name}/${this.name}.sql`;
|
|
103
199
|
}
|
|
104
200
|
configureSnapshotSpec(spec) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# Create default database if not postgres
|
|
119
|
-
if [ "${this.options.database}" != "postgres" ]; then
|
|
120
|
-
sudo -u postgres psql -c "CREATE DATABASE ${this.options.database};"
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
# Configure PostgreSQL to accept password authentication
|
|
124
|
-
echo "host all all 0.0.0.0/0 md5" | sudo tee -a /etc/postgresql/${this.options.version}/main/pg_hba.conf
|
|
125
|
-
echo "local all all md5" | sudo tee -a /etc/postgresql/${this.options.version}/main/pg_hba.conf
|
|
126
|
-
|
|
127
|
-
# Allow connections from all addresses
|
|
128
|
-
sudo sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" /etc/postgresql/${this.options.version}/main/postgresql.conf
|
|
129
|
-
|
|
130
|
-
# Restart PostgreSQL to apply changes
|
|
131
|
-
sudo systemctl restart postgresql
|
|
132
|
-
|
|
133
|
-
# Enable PostgreSQL to start on boot
|
|
134
|
-
sudo systemctl enable postgresql
|
|
135
|
-
`;
|
|
201
|
+
const { password, user } = this.database.postgres.options;
|
|
202
|
+
const dbName = this.database.options.name;
|
|
203
|
+
const scriptPath = this.getScriptPath();
|
|
204
|
+
const deps = [];
|
|
205
|
+
if (this.database.options.create) {
|
|
206
|
+
deps.push(this.database.getCreateServiceName());
|
|
207
|
+
} else {
|
|
208
|
+
deps.push(this.database.postgres.installServiceName());
|
|
209
|
+
}
|
|
210
|
+
for (const dep of this.options.after ?? []) {
|
|
211
|
+
deps.push(dep.getServiceName());
|
|
212
|
+
}
|
|
213
|
+
const command = `PGPASSWORD='${password}' psql -h 127.0.0.1 -U ${user} -d ${dbName} -v ON_ERROR_STOP=1 -f ${scriptPath}`;
|
|
136
214
|
return this.composeSpecs(
|
|
137
215
|
spec,
|
|
138
216
|
new VmSpec({
|
|
139
217
|
additionalFiles: {
|
|
140
|
-
|
|
141
|
-
content: installScript
|
|
142
|
-
}
|
|
218
|
+
[scriptPath]: { content: this.options.sql }
|
|
143
219
|
},
|
|
144
220
|
systemd: {
|
|
145
221
|
services: [
|
|
146
222
|
{
|
|
147
|
-
name:
|
|
223
|
+
name: this.getServiceName(),
|
|
148
224
|
mode: "oneshot",
|
|
149
225
|
deleteAfterSuccess: true,
|
|
150
|
-
|
|
151
|
-
|
|
226
|
+
after: deps,
|
|
227
|
+
requires: deps,
|
|
228
|
+
bash: command,
|
|
229
|
+
timeoutSec: 300
|
|
152
230
|
}
|
|
153
231
|
]
|
|
154
232
|
}
|
|
@@ -156,11 +234,20 @@ sudo systemctl enable postgresql
|
|
|
156
234
|
);
|
|
157
235
|
}
|
|
158
236
|
createInstance() {
|
|
159
|
-
return new
|
|
237
|
+
return new DatabaseScriptInstance(this.getServiceName());
|
|
160
238
|
}
|
|
161
|
-
|
|
162
|
-
|
|
239
|
+
}
|
|
240
|
+
class DatabaseScriptInstance extends VmWithInstance {
|
|
241
|
+
serviceName;
|
|
242
|
+
constructor(serviceName) {
|
|
243
|
+
super();
|
|
244
|
+
this.serviceName = serviceName;
|
|
245
|
+
}
|
|
246
|
+
logs() {
|
|
247
|
+
return this.vm.exec({
|
|
248
|
+
command: `journalctl -u ${this.serviceName} --no-pager -n 100`
|
|
249
|
+
}).then((result) => result.stdout?.trim().split("\n"));
|
|
163
250
|
}
|
|
164
251
|
}
|
|
165
252
|
|
|
166
|
-
export { VmPostgres, VmPostgresInstance };
|
|
253
|
+
export { Database, DatabaseInstance, DatabaseScript, DatabaseScriptInstance, VmPostgres, VmPostgresInstance };
|
package/package.json
CHANGED