neonctl 1.27.6 → 1.29.1
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/.bump +1 -0
- package/.editorconfig +7 -0
- package/.eslintrc.cjs +15 -0
- package/.github/workflows/commitlint.yml +46 -0
- package/.github/workflows/pr.yml +25 -0
- package/.github/workflows/release.yml +30 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +4 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +3 -0
- package/.releaserc.json +47 -0
- package/LICENSE +202 -0
- package/commitlint.config.cjs +7 -0
- package/generateOptionsFromSpec.ts +68 -0
- package/jest/setup.js +5 -0
- package/jest.config.ts +199 -0
- package/mocks/bin/psql.cjs +9 -0
- package/mocks/main/projects/GET.js +27 -0
- package/mocks/main/projects/POST.js +22 -0
- package/mocks/main/projects/shared/GET.js +16 -0
- package/mocks/main/projects/test/DELETE.json +7 -0
- package/mocks/main/projects/test/GET.json +13 -0
- package/mocks/main/projects/test/PATCH.js +18 -0
- package/mocks/main/projects/test/branches/GET.json +25 -0
- package/mocks/main/projects/test/branches/POST.js +83 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/DELETE.json +7 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/GET.json +9 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/PATCH.js +14 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/databases/GET.json +6 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/databases/POST.js +13 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/databases/test_db/DELETE.json +6 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/endpoints/GET.json +26 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/endpoints/POST.json +6 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/roles/GET.json +3 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/roles/POST.js +14 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/roles/test_role/DELETE.json +6 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/roles/test_role/reveal_password/GET.json +3 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/set_as_primary/POST.json +9 -0
- package/mocks/main/projects/test/branches/br-numbered-branch-123456/GET.json +10 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/DELETE.json +7 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/GET.json +10 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/PATCH.js +14 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/databases/GET.json +6 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/databases/POST.js +13 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/databases/test_db/DELETE.json +6 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/endpoints/GET.json +26 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/endpoints/POST.json +6 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/restore/POST.js +16 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/roles/GET.json +3 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/roles/POST.js +14 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/roles/test_role/DELETE.json +6 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/roles/test_role/reveal_password/GET.json +3 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/set_as_primary/POST.json +9 -0
- package/mocks/main/projects/test/endpoints/GET.json +9 -0
- package/mocks/main/projects/test/endpoints/POST.js +32 -0
- package/mocks/main/projects/test/endpoints/test_endpoint_id/DELETE.json +7 -0
- package/mocks/main/projects/test/endpoints/test_endpoint_id/GET.json +9 -0
- package/mocks/main/projects/test/endpoints/test_endpoint_id/PATCH.js +17 -0
- package/mocks/main/projects/test/operations/GET.json +22 -0
- package/mocks/main/users/me/GET.json +5 -0
- package/mocks/restore/projects/test/branches/GET.json +21 -0
- package/mocks/restore/projects/test/branches/br-another-branch-123456/GET.json +6 -0
- package/mocks/restore/projects/test/branches/br-another-branch-123456/restore/POST.js +13 -0
- package/mocks/restore/projects/test/branches/br-any-branch-123456/GET.json +6 -0
- package/mocks/restore/projects/test/branches/br-parent-tots-123456/GET.json +7 -0
- package/mocks/restore/projects/test/branches/br-parent-tots-123456/restore/POST.js +14 -0
- package/mocks/restore/projects/test/branches/br-self-tolsn-123456/GET.json +6 -0
- package/mocks/restore/projects/test/branches/br-self-tolsn-123456/restore/POST.js +15 -0
- package/mocks/single_project/projects/GET.json +10 -0
- package/mocks/single_project/projects/test-project-123456/GET.json +14 -0
- package/mocks/single_project/projects/test-project-123456/branches/GET.json +11 -0
- package/mocks/single_project/projects/test-project-123456/branches/br-main-branch-123456/databases/GET.json +3 -0
- package/mocks/single_project/projects/test-project-123456/branches/br-main-branch-123456/endpoints/GET.json +10 -0
- package/mocks/single_project/projects/test-project-123456/branches/br-main-branch-123456/roles/GET.json +3 -0
- package/mocks/single_project/projects/test-project-123456/branches/br-main-branch-123456/roles/test_role/reveal_password/GET.json +3 -0
- package/package.json +7 -6
- package/pkg.js +45 -3
- package/rollup.config.js +20 -0
- package/snapshots/commands/branches.test.snap +221 -0
- package/snapshots/commands/connection_string.test.snap +70 -0
- package/snapshots/commands/databases.test.snap +20 -0
- package/snapshots/commands/ip_allow.test.snap +55 -0
- package/snapshots/commands/operations.test.snap +17 -0
- package/snapshots/commands/projects.test.snap +141 -0
- package/snapshots/commands/roles.test.snap +19 -0
- package/snapshots/commands/set_context.test.snap +30 -0
- package/snapshots/writer.test.snap +60 -0
- package/snapshotsResolver.cjs +32 -0
- package/src/analytics.ts +95 -0
- package/src/api.ts +44 -0
- package/src/auth.ts +137 -0
- package/{cli.js → src/cli.ts} +1 -0
- package/src/commands/auth.test.ts +62 -0
- package/src/commands/auth.ts +148 -0
- package/src/commands/branches.test.ts +354 -0
- package/src/commands/branches.ts +451 -0
- package/src/commands/connection_string.test.ts +250 -0
- package/src/commands/connection_string.ts +210 -0
- package/src/commands/databases.test.ts +55 -0
- package/src/commands/databases.ts +129 -0
- package/src/commands/help.test.ts +13 -0
- package/{commands/index.js → src/commands/index.ts} +11 -10
- package/src/commands/ip_allow.test.ts +86 -0
- package/src/commands/ip_allow.ts +202 -0
- package/src/commands/operations.test.ts +13 -0
- package/src/commands/operations.ts +41 -0
- package/src/commands/projects.test.ts +147 -0
- package/src/commands/projects.ts +275 -0
- package/src/commands/roles.test.ts +46 -0
- package/src/commands/roles.ts +100 -0
- package/src/commands/set_context.test.ts +64 -0
- package/src/commands/set_context.ts +27 -0
- package/src/commands/user.ts +21 -0
- package/src/config.ts +22 -0
- package/src/context.ts +61 -0
- package/src/env.ts +7 -0
- package/src/errors.ts +24 -0
- package/src/help.ts +185 -0
- package/src/index.ts +180 -0
- package/src/log.ts +16 -0
- package/src/parameters.gen.ts +332 -0
- package/src/pkg.ts +9 -0
- package/src/test_utils/mock_server.ts +27 -0
- package/src/test_utils/oauth_server.ts +10 -0
- package/src/test_utils/test_cli_command.ts +117 -0
- package/src/types.ts +25 -0
- package/src/utils/enrichers.ts +73 -0
- package/src/utils/formats.test.ts +41 -0
- package/src/utils/formats.ts +11 -0
- package/src/utils/middlewares.ts +23 -0
- package/src/utils/point_in_time.ts +86 -0
- package/src/utils/psql.ts +29 -0
- package/src/utils/string.ts +8 -0
- package/src/utils/ui.ts +64 -0
- package/src/writer.test.ts +98 -0
- package/src/writer.ts +131 -0
- package/tsconfig.json +17 -0
- package/analytics.js +0 -78
- package/api.js +0 -35
- package/auth.js +0 -101
- package/commands/auth.js +0 -102
- package/commands/auth.test.js +0 -42
- package/commands/branches.js +0 -311
- package/commands/branches.test.js +0 -321
- package/commands/connection_string.js +0 -137
- package/commands/connection_string.test.js +0 -204
- package/commands/databases.js +0 -79
- package/commands/databases.test.js +0 -51
- package/commands/help.test.js +0 -11
- package/commands/ip_allow.js +0 -135
- package/commands/ip_allow.test.js +0 -78
- package/commands/operations.js +0 -28
- package/commands/operations.test.js +0 -11
- package/commands/projects.js +0 -156
- package/commands/projects.test.js +0 -116
- package/commands/roles.js +0 -57
- package/commands/roles.test.js +0 -42
- package/commands/set_context.js +0 -22
- package/commands/set_context.test.js +0 -53
- package/commands/user.js +0 -15
- package/config.js +0 -11
- package/context.js +0 -48
- package/env.js +0 -6
- package/errors.js +0 -16
- package/help.js +0 -146
- package/index.js +0 -168
- package/log.js +0 -15
- package/parameters.gen.js +0 -302
- package/test_utils/mock_server.js +0 -16
- package/test_utils/oauth_server.js +0 -9
- package/test_utils/test_cli_command.js +0 -80
- package/types.js +0 -1
- package/utils/enrichers.js +0 -49
- package/utils/formats.js +0 -5
- package/utils/formats.test.js +0 -32
- package/utils/middlewares.js +0 -20
- package/utils/point_in_time.js +0 -44
- package/utils/psql.js +0 -24
- package/utils/string.js +0 -5
- package/utils/ui.js +0 -59
- package/writer.js +0 -87
- package/writer.test.js +0 -86
- /package/{callback.html → src/callback.html} +0 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { Branch, EndpointType } from '@neondatabase/api-client';
|
|
2
|
+
import yargs from 'yargs';
|
|
3
|
+
|
|
4
|
+
import { IdOrNameProps, ProjectScopeProps } from '../types.js';
|
|
5
|
+
import { writer } from '../writer.js';
|
|
6
|
+
import { branchCreateRequest } from '../parameters.gen.js';
|
|
7
|
+
import { retryOnLock } from '../api.js';
|
|
8
|
+
import {
|
|
9
|
+
branchIdFromProps,
|
|
10
|
+
branchIdResolve,
|
|
11
|
+
fillSingleProject,
|
|
12
|
+
} from '../utils/enrichers.js';
|
|
13
|
+
import {
|
|
14
|
+
looksLikeBranchId,
|
|
15
|
+
looksLikeLSN,
|
|
16
|
+
looksLikeTimestamp,
|
|
17
|
+
} from '../utils/formats.js';
|
|
18
|
+
import { psql } from '../utils/psql.js';
|
|
19
|
+
import { parsePointInTime } from '../utils/point_in_time.js';
|
|
20
|
+
import { log } from '../log.js';
|
|
21
|
+
|
|
22
|
+
const BRANCH_FIELDS = [
|
|
23
|
+
'id',
|
|
24
|
+
'name',
|
|
25
|
+
'primary',
|
|
26
|
+
'created_at',
|
|
27
|
+
'updated_at',
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
const BRANCH_FIELDS_RESET = [
|
|
31
|
+
'id',
|
|
32
|
+
'name',
|
|
33
|
+
'primary',
|
|
34
|
+
'created_at',
|
|
35
|
+
'last_reset_at',
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
export const command = 'branches';
|
|
39
|
+
export const describe = 'Manage branches';
|
|
40
|
+
export const aliases = ['branch'];
|
|
41
|
+
export const builder = (argv: yargs.Argv) =>
|
|
42
|
+
argv
|
|
43
|
+
.usage('$0 branches <sub-command> [options]')
|
|
44
|
+
.options({
|
|
45
|
+
'project-id': {
|
|
46
|
+
describe: 'Project ID',
|
|
47
|
+
type: 'string',
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
.middleware(fillSingleProject as any)
|
|
51
|
+
.command(
|
|
52
|
+
'list',
|
|
53
|
+
'List branches',
|
|
54
|
+
(yargs) => yargs,
|
|
55
|
+
async (args) => await list(args as any),
|
|
56
|
+
)
|
|
57
|
+
.command(
|
|
58
|
+
'create',
|
|
59
|
+
'Create a branch',
|
|
60
|
+
(yargs) =>
|
|
61
|
+
yargs.options({
|
|
62
|
+
name: branchCreateRequest['branch.name'],
|
|
63
|
+
parent: {
|
|
64
|
+
describe:
|
|
65
|
+
'Parent branch name or id or timestamp or LSN. Defaults to the primary branch',
|
|
66
|
+
type: 'string',
|
|
67
|
+
},
|
|
68
|
+
compute: {
|
|
69
|
+
describe:
|
|
70
|
+
'Create a branch with or without a compute. By default branch is created with a read-write compute. To create a branch without compute use --no-compute',
|
|
71
|
+
type: 'boolean',
|
|
72
|
+
default: true,
|
|
73
|
+
},
|
|
74
|
+
type: {
|
|
75
|
+
describe: 'Type of compute to add',
|
|
76
|
+
type: 'string',
|
|
77
|
+
implies: 'compute',
|
|
78
|
+
default: EndpointType.ReadWrite,
|
|
79
|
+
choices: Object.values(EndpointType),
|
|
80
|
+
},
|
|
81
|
+
'suspend-timeout': {
|
|
82
|
+
describe:
|
|
83
|
+
'Duration of inactivity in seconds after which the compute endpoint is\nautomatically suspended. The value `0` means use the global default.\nThe value `-1` means never suspend. The default value is `300` seconds (5 minutes).\nThe maximum value is `604800` seconds (1 week).',
|
|
84
|
+
type: 'number',
|
|
85
|
+
implies: 'compute',
|
|
86
|
+
default: 0,
|
|
87
|
+
},
|
|
88
|
+
psql: {
|
|
89
|
+
type: 'boolean',
|
|
90
|
+
describe: 'Connect to a new branch via psql',
|
|
91
|
+
default: false,
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
async (args) => await create(args as any),
|
|
95
|
+
)
|
|
96
|
+
.command(
|
|
97
|
+
'reset <id|name>',
|
|
98
|
+
'Reset a branch',
|
|
99
|
+
(yargs) =>
|
|
100
|
+
yargs.options({
|
|
101
|
+
parent: {
|
|
102
|
+
describe: 'Reset to a parent branch',
|
|
103
|
+
type: 'boolean',
|
|
104
|
+
default: false,
|
|
105
|
+
},
|
|
106
|
+
'preserve-under-name': {
|
|
107
|
+
describe: 'Name under which to preserve the old branch',
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
async (args) => await reset(args as any),
|
|
111
|
+
)
|
|
112
|
+
.command(
|
|
113
|
+
'restore <target-id|name> <source>[@(timestamp|lsn)]',
|
|
114
|
+
'Restores a branch to a specific point in time\n<source> can be: ^self, ^parent, or <source-branch-id|name>',
|
|
115
|
+
(yargs) =>
|
|
116
|
+
yargs
|
|
117
|
+
// we want to show meaningful help for the command
|
|
118
|
+
// but it makes yargs to fail on parsing the command
|
|
119
|
+
// so we need to fill in the missing args manually
|
|
120
|
+
.middleware((args: any) => {
|
|
121
|
+
args.id = args.targetId;
|
|
122
|
+
args.pointInTime = args['source@(timestamp'];
|
|
123
|
+
})
|
|
124
|
+
.usage(
|
|
125
|
+
'$0 branches restore <target-id|name> <source>[@(timestamp|lsn)]',
|
|
126
|
+
)
|
|
127
|
+
.options({
|
|
128
|
+
'preserve-under-name': {
|
|
129
|
+
describe: 'Name under which to preserve the old branch',
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
.example([
|
|
133
|
+
[
|
|
134
|
+
'$0 branches restore main br-source-branch-123456',
|
|
135
|
+
'Restores main to the head of the branch with id br-source-branch-123456',
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
'$0 branches restore main source@2021-01-01T00:00:00Z',
|
|
139
|
+
'Restores main to the timestamp 2021-01-01T00:00:00Z of the source branch',
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
'$0 branches restore my-branch ^self@0/123456',
|
|
143
|
+
'Restores my-branch to the LSN 0/123456 from its own history',
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
'$0 branches restore my-branch ^parent',
|
|
147
|
+
'Restore my-branch to the head of its parent branch',
|
|
148
|
+
],
|
|
149
|
+
]),
|
|
150
|
+
async (args) => await restore(args as any),
|
|
151
|
+
)
|
|
152
|
+
.command(
|
|
153
|
+
'rename <id|name> <new-name>',
|
|
154
|
+
'Rename a branch',
|
|
155
|
+
(yargs) => yargs,
|
|
156
|
+
async (args) => await rename(args as any),
|
|
157
|
+
)
|
|
158
|
+
.command(
|
|
159
|
+
'set-primary <id|name>',
|
|
160
|
+
'Set a branch as primary',
|
|
161
|
+
(yargs) => yargs,
|
|
162
|
+
async (args) => await setPrimary(args as any),
|
|
163
|
+
)
|
|
164
|
+
.command(
|
|
165
|
+
'add-compute <id|name>',
|
|
166
|
+
'Add a compute to a branch',
|
|
167
|
+
(yargs) =>
|
|
168
|
+
yargs.options({
|
|
169
|
+
type: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
choices: Object.values(EndpointType),
|
|
172
|
+
describe: 'Type of compute to add',
|
|
173
|
+
default: EndpointType.ReadOnly,
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
async (args) => await addCompute(args as any),
|
|
177
|
+
)
|
|
178
|
+
.command(
|
|
179
|
+
'delete <id|name>',
|
|
180
|
+
'Delete a branch',
|
|
181
|
+
(yargs) => yargs,
|
|
182
|
+
async (args) => await deleteBranch(args as any),
|
|
183
|
+
)
|
|
184
|
+
.command(
|
|
185
|
+
'get <id|name>',
|
|
186
|
+
'Get a branch',
|
|
187
|
+
(yargs) => yargs,
|
|
188
|
+
async (args) => await get(args as any),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
export const handler = (args: yargs.Argv) => {
|
|
192
|
+
return args;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const list = async (props: ProjectScopeProps) => {
|
|
196
|
+
const { data } = await props.apiClient.listProjectBranches(props.projectId);
|
|
197
|
+
writer(props).end(data.branches, {
|
|
198
|
+
fields: BRANCH_FIELDS,
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const create = async (
|
|
203
|
+
props: ProjectScopeProps & {
|
|
204
|
+
name: string;
|
|
205
|
+
compute: boolean;
|
|
206
|
+
parent?: string;
|
|
207
|
+
type: EndpointType;
|
|
208
|
+
psql: boolean;
|
|
209
|
+
suspendTimeout: number;
|
|
210
|
+
'--'?: string[];
|
|
211
|
+
},
|
|
212
|
+
) => {
|
|
213
|
+
const parentProps = await (() => {
|
|
214
|
+
if (!props.parent) {
|
|
215
|
+
return props.apiClient
|
|
216
|
+
.listProjectBranches(props.projectId)
|
|
217
|
+
.then(({ data }) => {
|
|
218
|
+
const branch = data.branches.find((b) => b.primary);
|
|
219
|
+
if (!branch) {
|
|
220
|
+
throw new Error('No primary branch found');
|
|
221
|
+
}
|
|
222
|
+
return { parent_id: branch.id };
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (looksLikeLSN(props.parent)) {
|
|
227
|
+
return { parent_lsn: props.parent };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (looksLikeTimestamp(props.parent)) {
|
|
231
|
+
return { parent_timestamp: props.parent };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (looksLikeBranchId(props.parent)) {
|
|
235
|
+
return { parent_id: props.parent };
|
|
236
|
+
}
|
|
237
|
+
return props.apiClient
|
|
238
|
+
.listProjectBranches(props.projectId)
|
|
239
|
+
.then(({ data }) => {
|
|
240
|
+
const branch = data.branches.find((b) => b.name === props.parent);
|
|
241
|
+
if (!branch) {
|
|
242
|
+
throw new Error(`Branch ${props.parent} not found`);
|
|
243
|
+
}
|
|
244
|
+
return { parent_id: branch.id };
|
|
245
|
+
});
|
|
246
|
+
})();
|
|
247
|
+
|
|
248
|
+
const { data } = await retryOnLock(() =>
|
|
249
|
+
props.apiClient.createProjectBranch(props.projectId, {
|
|
250
|
+
branch: {
|
|
251
|
+
name: props.name,
|
|
252
|
+
...parentProps,
|
|
253
|
+
},
|
|
254
|
+
endpoints: props.compute
|
|
255
|
+
? [
|
|
256
|
+
{
|
|
257
|
+
type: props.type,
|
|
258
|
+
suspend_timeout_seconds:
|
|
259
|
+
props.suspendTimeout === 0 ? undefined : props.suspendTimeout,
|
|
260
|
+
},
|
|
261
|
+
]
|
|
262
|
+
: [],
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const out = writer(props);
|
|
267
|
+
out.write(data.branch, {
|
|
268
|
+
fields: BRANCH_FIELDS,
|
|
269
|
+
title: 'branch',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (data.endpoints?.length > 0) {
|
|
273
|
+
out.write(data.endpoints, {
|
|
274
|
+
fields: ['id', 'created_at'],
|
|
275
|
+
title: 'endpoints',
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (data.connection_uris && data.connection_uris?.length > 0) {
|
|
279
|
+
out.write(data.connection_uris, {
|
|
280
|
+
fields: ['connection_uri'],
|
|
281
|
+
title: 'connection_uris',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
out.end();
|
|
285
|
+
|
|
286
|
+
if (props.psql) {
|
|
287
|
+
if (!data.connection_uris || !data.connection_uris?.length) {
|
|
288
|
+
throw new Error(`Branch ${data.branch.id} doesn't have a connection uri`);
|
|
289
|
+
}
|
|
290
|
+
const connection_uri = data.connection_uris[0].connection_uri;
|
|
291
|
+
const psqlArgs = props['--'];
|
|
292
|
+
await psql(connection_uri, psqlArgs);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const rename = async (
|
|
297
|
+
props: ProjectScopeProps & IdOrNameProps & { newName: string },
|
|
298
|
+
) => {
|
|
299
|
+
const branchId = await branchIdFromProps(props);
|
|
300
|
+
const { data } = await retryOnLock(() =>
|
|
301
|
+
props.apiClient.updateProjectBranch(props.projectId, branchId, {
|
|
302
|
+
branch: {
|
|
303
|
+
name: props.newName,
|
|
304
|
+
},
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
writer(props).end(data.branch, {
|
|
308
|
+
fields: BRANCH_FIELDS,
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const setPrimary = async (props: ProjectScopeProps & IdOrNameProps) => {
|
|
313
|
+
const branchId = await branchIdFromProps(props);
|
|
314
|
+
const { data } = await retryOnLock(() =>
|
|
315
|
+
props.apiClient.setPrimaryProjectBranch(props.projectId, branchId),
|
|
316
|
+
);
|
|
317
|
+
writer(props).end(data.branch, {
|
|
318
|
+
fields: BRANCH_FIELDS,
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const deleteBranch = async (props: ProjectScopeProps & IdOrNameProps) => {
|
|
323
|
+
const branchId = await branchIdFromProps(props);
|
|
324
|
+
const { data } = await retryOnLock(() =>
|
|
325
|
+
props.apiClient.deleteProjectBranch(props.projectId, branchId),
|
|
326
|
+
);
|
|
327
|
+
writer(props).end(data.branch, {
|
|
328
|
+
fields: BRANCH_FIELDS,
|
|
329
|
+
});
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const get = async (props: ProjectScopeProps & IdOrNameProps) => {
|
|
333
|
+
const branchId = await branchIdFromProps(props);
|
|
334
|
+
const { data } = await props.apiClient.getProjectBranch(
|
|
335
|
+
props.projectId,
|
|
336
|
+
branchId,
|
|
337
|
+
);
|
|
338
|
+
writer(props).end(data.branch, {
|
|
339
|
+
fields: BRANCH_FIELDS,
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const addCompute = async (
|
|
344
|
+
props: ProjectScopeProps &
|
|
345
|
+
IdOrNameProps & {
|
|
346
|
+
type: EndpointType;
|
|
347
|
+
},
|
|
348
|
+
) => {
|
|
349
|
+
const branchId = await branchIdFromProps(props);
|
|
350
|
+
const { data } = await retryOnLock(() =>
|
|
351
|
+
props.apiClient.createProjectEndpoint(props.projectId, {
|
|
352
|
+
endpoint: {
|
|
353
|
+
branch_id: branchId,
|
|
354
|
+
type: props.type,
|
|
355
|
+
},
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
writer(props).end(data.endpoint, {
|
|
359
|
+
fields: ['id', 'host'],
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const reset = async (
|
|
364
|
+
props: ProjectScopeProps &
|
|
365
|
+
IdOrNameProps & {
|
|
366
|
+
parent: boolean;
|
|
367
|
+
preserveUnderName?: string;
|
|
368
|
+
},
|
|
369
|
+
) => {
|
|
370
|
+
if (!props.parent) {
|
|
371
|
+
throw new Error('Only resetting to parent is supported for now');
|
|
372
|
+
}
|
|
373
|
+
const branchId = await branchIdFromProps(props);
|
|
374
|
+
const {
|
|
375
|
+
data: {
|
|
376
|
+
branch: { parent_id },
|
|
377
|
+
},
|
|
378
|
+
} = await props.apiClient.getProjectBranch(props.projectId, branchId);
|
|
379
|
+
if (!parent_id) {
|
|
380
|
+
throw new Error('Branch has no parent');
|
|
381
|
+
}
|
|
382
|
+
const { data } = await retryOnLock(() =>
|
|
383
|
+
props.apiClient.restoreProjectBranch(props.projectId, branchId, {
|
|
384
|
+
source_branch_id: parent_id,
|
|
385
|
+
preserve_under_name: props.preserveUnderName || undefined,
|
|
386
|
+
}),
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const resultBranch = data.branch as Branch;
|
|
390
|
+
|
|
391
|
+
writer(props).end(resultBranch, {
|
|
392
|
+
// need to reset types until we expose reset api
|
|
393
|
+
fields: BRANCH_FIELDS_RESET as any,
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const restore = async (
|
|
398
|
+
props: ProjectScopeProps &
|
|
399
|
+
IdOrNameProps & { pointInTime: string; preserveUnderName?: string },
|
|
400
|
+
) => {
|
|
401
|
+
const targetBranchId = await branchIdResolve({
|
|
402
|
+
branch: props.id,
|
|
403
|
+
projectId: props.projectId,
|
|
404
|
+
apiClient: props.apiClient,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const pointInTime = await parsePointInTime({
|
|
408
|
+
pointInTime: props.pointInTime,
|
|
409
|
+
targetBranchId,
|
|
410
|
+
projectId: props.projectId,
|
|
411
|
+
api: props.apiClient,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
log.info(
|
|
415
|
+
`Restoring branch ${targetBranchId} to the branch ${pointInTime.branchId} ${
|
|
416
|
+
(pointInTime.tag === 'lsn' && 'LSN ' + pointInTime.lsn) ||
|
|
417
|
+
(pointInTime.tag === 'timestamp' &&
|
|
418
|
+
'timestamp ' + pointInTime.timestamp) ||
|
|
419
|
+
'head'
|
|
420
|
+
}`,
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const { data } = await retryOnLock(() =>
|
|
424
|
+
props.apiClient.restoreProjectBranch(props.projectId, targetBranchId, {
|
|
425
|
+
source_branch_id: pointInTime.branchId,
|
|
426
|
+
preserve_under_name: props.preserveUnderName || undefined,
|
|
427
|
+
...(pointInTime.tag === 'lsn' && { source_lsn: pointInTime.lsn }),
|
|
428
|
+
...(pointInTime.tag === 'timestamp' && {
|
|
429
|
+
source_timestamp: pointInTime.timestamp,
|
|
430
|
+
}),
|
|
431
|
+
}),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const branch = data.branch as Branch;
|
|
435
|
+
|
|
436
|
+
const writeInst = writer(props).write(branch as Branch, {
|
|
437
|
+
title: 'Restored branch',
|
|
438
|
+
fields: ['id', 'name', 'last_reset_at'],
|
|
439
|
+
});
|
|
440
|
+
if (props.preserveUnderName && branch.parent_id) {
|
|
441
|
+
const { data } = await props.apiClient.getProjectBranch(
|
|
442
|
+
props.projectId,
|
|
443
|
+
branch.parent_id,
|
|
444
|
+
);
|
|
445
|
+
writeInst.write(data.branch, {
|
|
446
|
+
title: 'Backup branch',
|
|
447
|
+
fields: ['id', 'name'],
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
writeInst.end();
|
|
451
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { describe } from '@jest/globals';
|
|
2
|
+
import { testCliCommand } from '../test_utils/test_cli_command';
|
|
3
|
+
|
|
4
|
+
describe('connection_string', () => {
|
|
5
|
+
testCliCommand({
|
|
6
|
+
name: 'connection_string',
|
|
7
|
+
args: [
|
|
8
|
+
'connection-string',
|
|
9
|
+
'test_branch',
|
|
10
|
+
'--project-id',
|
|
11
|
+
'test',
|
|
12
|
+
'--database-name',
|
|
13
|
+
'test_db',
|
|
14
|
+
'--role-name',
|
|
15
|
+
'test_role',
|
|
16
|
+
],
|
|
17
|
+
expected: {
|
|
18
|
+
snapshot: true,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
testCliCommand({
|
|
23
|
+
name: 'connection_string branch id',
|
|
24
|
+
args: [
|
|
25
|
+
'connection-string',
|
|
26
|
+
'br-sunny-branch-123456',
|
|
27
|
+
'--project-id',
|
|
28
|
+
'test',
|
|
29
|
+
'--database-name',
|
|
30
|
+
'test_db',
|
|
31
|
+
'--role-name',
|
|
32
|
+
'test_role',
|
|
33
|
+
],
|
|
34
|
+
expected: {
|
|
35
|
+
snapshot: true,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
testCliCommand({
|
|
40
|
+
name: 'connection_string branch id 8 digits',
|
|
41
|
+
args: [
|
|
42
|
+
'connection-string',
|
|
43
|
+
'br-cloudy-branch-12345678',
|
|
44
|
+
'--project-id',
|
|
45
|
+
'test',
|
|
46
|
+
'--database-name',
|
|
47
|
+
'test_db',
|
|
48
|
+
'--role-name',
|
|
49
|
+
'test_role',
|
|
50
|
+
],
|
|
51
|
+
expected: {
|
|
52
|
+
snapshot: true,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
testCliCommand({
|
|
57
|
+
name: 'connection_string pooled',
|
|
58
|
+
args: [
|
|
59
|
+
'connection-string',
|
|
60
|
+
'test_branch',
|
|
61
|
+
'--project-id',
|
|
62
|
+
'test',
|
|
63
|
+
'--database-name',
|
|
64
|
+
'test_db',
|
|
65
|
+
'--role-name',
|
|
66
|
+
'test_role',
|
|
67
|
+
'--pooled',
|
|
68
|
+
],
|
|
69
|
+
expected: {
|
|
70
|
+
snapshot: true,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
testCliCommand({
|
|
75
|
+
name: 'connection_string prisma',
|
|
76
|
+
args: [
|
|
77
|
+
'connection-string',
|
|
78
|
+
'test_branch',
|
|
79
|
+
'--project-id',
|
|
80
|
+
'test',
|
|
81
|
+
'--database-name',
|
|
82
|
+
'test_db',
|
|
83
|
+
'--role-name',
|
|
84
|
+
'test_role',
|
|
85
|
+
'--prisma',
|
|
86
|
+
],
|
|
87
|
+
expected: {
|
|
88
|
+
snapshot: true,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
testCliCommand({
|
|
93
|
+
name: 'connection_string prisma pooled',
|
|
94
|
+
args: [
|
|
95
|
+
'connection-string',
|
|
96
|
+
'test_branch',
|
|
97
|
+
'--project-id',
|
|
98
|
+
'test',
|
|
99
|
+
'--database-name',
|
|
100
|
+
'test_db',
|
|
101
|
+
'--role-name',
|
|
102
|
+
'test_role',
|
|
103
|
+
'--prisma',
|
|
104
|
+
'--pooled',
|
|
105
|
+
],
|
|
106
|
+
expected: {
|
|
107
|
+
snapshot: true,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
testCliCommand({
|
|
112
|
+
name: 'connection_string prisma pooled extended',
|
|
113
|
+
args: [
|
|
114
|
+
'connection-string',
|
|
115
|
+
'test_branch',
|
|
116
|
+
'--project-id',
|
|
117
|
+
'test',
|
|
118
|
+
'--database-name',
|
|
119
|
+
'test_db',
|
|
120
|
+
'--role-name',
|
|
121
|
+
'test_role',
|
|
122
|
+
'--prisma',
|
|
123
|
+
'--pooled',
|
|
124
|
+
'--extended',
|
|
125
|
+
],
|
|
126
|
+
expected: {
|
|
127
|
+
snapshot: true,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
testCliCommand({
|
|
132
|
+
name: 'connection_string without any args should pass',
|
|
133
|
+
args: ['connection-string'],
|
|
134
|
+
mockDir: 'single_project',
|
|
135
|
+
expected: {
|
|
136
|
+
snapshot: true,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
testCliCommand({
|
|
141
|
+
name: 'connection_string with psql',
|
|
142
|
+
args: [
|
|
143
|
+
'connection-string',
|
|
144
|
+
'test_branch',
|
|
145
|
+
'--project-id',
|
|
146
|
+
'test',
|
|
147
|
+
'--database-name',
|
|
148
|
+
'test_db',
|
|
149
|
+
'--role-name',
|
|
150
|
+
'test_role',
|
|
151
|
+
'--psql',
|
|
152
|
+
],
|
|
153
|
+
expected: {
|
|
154
|
+
snapshot: true,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
testCliCommand({
|
|
159
|
+
name: 'connection_string with psql and psql args',
|
|
160
|
+
args: [
|
|
161
|
+
'connection-string',
|
|
162
|
+
'test_branch',
|
|
163
|
+
'--project-id',
|
|
164
|
+
'test',
|
|
165
|
+
'--database-name',
|
|
166
|
+
'test_db',
|
|
167
|
+
'--role-name',
|
|
168
|
+
'test_role',
|
|
169
|
+
'--psql',
|
|
170
|
+
'--',
|
|
171
|
+
'-c',
|
|
172
|
+
'SELECT 1',
|
|
173
|
+
],
|
|
174
|
+
expected: {
|
|
175
|
+
snapshot: true,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
testCliCommand({
|
|
180
|
+
name: 'connection_string without ssl',
|
|
181
|
+
args: [
|
|
182
|
+
'connection-string',
|
|
183
|
+
'test_branch',
|
|
184
|
+
'--project-id',
|
|
185
|
+
'test',
|
|
186
|
+
'--database-name',
|
|
187
|
+
'test_db',
|
|
188
|
+
'--role-name',
|
|
189
|
+
'test_role',
|
|
190
|
+
'--ssl',
|
|
191
|
+
'omit',
|
|
192
|
+
],
|
|
193
|
+
expected: {
|
|
194
|
+
snapshot: true,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
testCliCommand({
|
|
199
|
+
name: 'connection_string with ssl verify full',
|
|
200
|
+
args: [
|
|
201
|
+
'connection-string',
|
|
202
|
+
'test_branch',
|
|
203
|
+
'--project-id',
|
|
204
|
+
'test',
|
|
205
|
+
'--database-name',
|
|
206
|
+
'test_db',
|
|
207
|
+
'--role-name',
|
|
208
|
+
'test_role',
|
|
209
|
+
'--ssl',
|
|
210
|
+
'verify-full',
|
|
211
|
+
],
|
|
212
|
+
expected: {
|
|
213
|
+
snapshot: true,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
testCliCommand({
|
|
218
|
+
name: 'connection_string with lsn',
|
|
219
|
+
args: [
|
|
220
|
+
'connection-string',
|
|
221
|
+
'test_branch@0/123456',
|
|
222
|
+
'--project-id',
|
|
223
|
+
'test',
|
|
224
|
+
'--database-name',
|
|
225
|
+
'test_db',
|
|
226
|
+
'--role-name',
|
|
227
|
+
'test_role',
|
|
228
|
+
],
|
|
229
|
+
expected: {
|
|
230
|
+
snapshot: true,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
testCliCommand({
|
|
235
|
+
name: 'connection_string with timestamp',
|
|
236
|
+
args: [
|
|
237
|
+
'connection-string',
|
|
238
|
+
'test_branch@2021-01-01T00:00:00Z',
|
|
239
|
+
'--project-id',
|
|
240
|
+
'test',
|
|
241
|
+
'--database-name',
|
|
242
|
+
'test_db',
|
|
243
|
+
'--role-name',
|
|
244
|
+
'test_role',
|
|
245
|
+
],
|
|
246
|
+
expected: {
|
|
247
|
+
snapshot: true,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
});
|