Skip to content

Commit

Permalink
feat: new commands for VPC configurations (#304)
Browse files Browse the repository at this point in the history
Two new sub-commands:

```
neonctl vpc endpoint

Manage VPC endpoints.
See: https://neon.tech/docs/guides/neon-private-networking
After adding an endpoint to an organization, client connections will be accepted
from the corresponding VPC for all projects in the organization, unless overridden
by a project-level VPC endpoint restriction.

Commands:
  neonctl vpc endpoint list         List configured VPC endpoints for this organization.
  neonctl vpc endpoint assign <id>  Add or update a VPC endpoint for this organization.
                                    Note: Azure regions are not yet supported.  [aliases: update, add]
  neonctl vpc endpoint remove <id>  Remove a VPC endpoint from this organization.
  neonctl vpc endpoint status <id>  Get the status of a VPC endpoint for this organization.

Options:
      --org-id        Organization ID  [string]
      --region-id     The region ID. Possible values: aws-us-west-2, aws-ap-southeast-1, aws-ap-southeast-2, aws-eu-central-1, aws-us-east-2, aws-us-east-1, azure-eastus2  [string] [required]
```

(If the org ID is omitted, but the user has only 1 org, then that org
is used.)

and

```
neonctl vpc project

Manage project-level VPC endpoint restrictions.
By default, connections are accepted from any VPC configured at the organization level.
A project-level VPC endpoint restriction can be used to restrict connections to a specific VPC.

Commands:
  neonctl vpc project list           List VPC endpoint restrictions for this project.
  neonctl vpc project restrict <id>  Configure or update a VPC endpoint restriction for this project.  [aliases: update]
  neonctl vpc project remove <id>    Remove a VPC endpoint restriction from this project.

Options:
      --project-id    Project ID  [string]
```

(If the project ID is omitted, but the user has only 1 project, then
that project is used.)


For example:
```
% neonctl vpc endpoint status --region-id aws-us-east-2 vpc-test12
┌─────────────────┬───────┬───────┬─────────────────────────┬─────────────────────────────┐
│ Vpc Endpoint Id │ Label │ State │ Num Restricted Projects │ Example Restricted Projects │
├─────────────────┼───────┼───────┼─────────────────────────┼─────────────────────────────┤
│ vpc-test12      │ test  │ new   │ 0                       │                             │
└─────────────────┴───────┴───────┴─────────────────────────┴─────────────────────────────┘
```



Depends on #303,
neondatabase/cloud#21842
and also another PR (yet to be drafted) to upgrade the api-client in
neonctl once more.

Fixes neondatabase/cloud#21840
Informs neondatabase/cloud#18315
  • Loading branch information
knz authored and duskpoet committed Dec 19, 2024
1 parent e20d004 commit 3f9d6d8
Show file tree
Hide file tree
Showing 28 changed files with 549 additions and 11 deletions.
Binary file modified bun.lockb
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { expect } from 'vitest';

export default function (req, res) {
expect(req.body).toMatchObject({
label: 'newlabel',
});
res.status(200).send({});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"endpoints": [
{
"vpc_endpoint_id": "vpc-test",
"label": "test-label"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { expect } from 'vitest';

export default function (req, res) {
res.status(200).send({});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"vpc_endpoint_id": "vpc-test",
"state": "new"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { expect } from 'vitest';

export default function (req, res) {
expect(req.body).toMatchObject({
label: 'newlabel',
});
res.status(200).send({});
}
10 changes: 10 additions & 0 deletions mocks/single_org/projects/GET.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"projects": [
{
"id": "test-project-123456",
"name": "test-project-123456",
"created_at": "2019-01-01T00:00:00.000Z",
"updated_at": "2019-01-01T00:00:00.000Z"
}
]
}
14 changes: 14 additions & 0 deletions mocks/single_org/projects/test-project-123456/GET.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"project": {
"id": "test-project-123456",
"name": "test-project-123456",
"created_at": "2019-01-01T00:00:00.000Z",
"updated_at": "2019-01-01T00:00:00.000Z",
"settings": {
"allowed_ips": {
"ips": ["192.168.1.1"],
"protected_branches_only": false
}
}
}
}
11 changes: 11 additions & 0 deletions mocks/single_org/projects/test-project-123456/branches/GET.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"branches": [
{
"id": "br-main-branch-123456",
"name": "main",
"default": true,
"created_at": "2021-01-01T00:00:00.000Z",
"updated_at": "2021-01-01T00:00:00.000Z"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"databases": [{ "name": "db1", "owner_name": "user1" }]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"endpoints": [
{
"id": "ep-test-endpoint-123456",
"created_at": "2019-01-01T00:00:00Z",
"type": "read_write",
"branch_id": "br-main-branch-123456"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"roles": [{ "name": "test_role", "created_at": "2016-01-01T00:00:00Z" }]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"password": "password"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"endpoints": [
{
"vpc_endpoint_id": "vpc-test",
"label": "test-label-project"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { expect } from 'vitest';

export default function (req, res) {
res.status(200).send({});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { expect } from 'vitest';

export default function (req, res) {
expect(req.body).toMatchObject({
label: 'newlabel',
});
res.status(200).send({});
}
5 changes: 5 additions & 0 deletions mocks/single_org/users/me/GET.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "1",
"name": "John Doe",
"email": "[email protected]"
}
8 changes: 8 additions & 0 deletions mocks/single_org/users/me/organizations/GET.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"organizations": [
{
"name": "Organization 1",
"id": 1
}
]
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"vitest": "^1.6.0"
},
"dependencies": {
"@neondatabase/api-client": "1.11.1",
"@neondatabase/api-client": "1.11.2",
"@segment/analytics-node": "^1.0.0-beta.26",
"axios": "^1.4.0",
"axios-debug-log": "^1.0.0",
Expand Down
56 changes: 56 additions & 0 deletions src/commands/__snapshots__/vpc_endpoints.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`vpc-endpoints > delete org VPC endpoint 1`] = `
"{}
"
`;

exports[`vpc-endpoints > delete project VPC endpoint restrictions 1`] = `
"{}
"
`;

exports[`vpc-endpoints > get org VPC endpoint status 1`] = `
"vpc_endpoint_id: vpc-test
state: new
"
`;

exports[`vpc-endpoints > list org VPC endpoints 1`] = `
"- vpc_endpoint_id: vpc-test
label: test-label
"
`;

exports[`vpc-endpoints > list org VPC endpoints implicit org 1`] = `
"- vpc_endpoint_id: vpc-test
label: test-label
"
`;

exports[`vpc-endpoints > list project VPC endpoint restrictions 1`] = `
"- vpc_endpoint_id: vpc-test
label: test-label-project
"
`;

exports[`vpc-endpoints > list project VPC endpoint restrictions with implicit project 1`] = `
"- vpc_endpoint_id: vpc-test
label: test-label-project
"
`;

exports[`vpc-endpoints > set org VPC endpoint in azure 1`] = `
"{}
"
`;

exports[`vpc-endpoints > set project VPC endpoint restrictions 1`] = `
"{}
"
`;

exports[`vpc-endpoints > update org VPC endpoint 1`] = `
"{}
"
`;
2 changes: 2 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as auth from './auth.js';
import * as projects from './projects.js';
import * as ipAllow from './ip_allow.js';
import * as vpcEndpoints from './vpc_endpoints.js';
import * as users from './user.js';
import * as orgs from './orgs.js';
import * as branches from './branches.js';
Expand All @@ -17,6 +18,7 @@ export default [
orgs,
projects,
ipAllow,
vpcEndpoints,
branches,
databases,
roles,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const PROJECT_FIELDS = [
'created_at',
] as const;

const REGIONS = [
export const REGIONS = [
'aws-us-west-2',
'aws-ap-southeast-1',
'aws-ap-southeast-2',
Expand Down
105 changes: 105 additions & 0 deletions src/commands/vpc_endpoints.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { describe } from 'vitest';
import { test } from '../test_utils/fixtures';

describe('vpc-endpoints', () => {
test('list org VPC endpoints', async ({ testCliCommand }) => {
await testCliCommand(
['vpc', 'endpoint', 'list', '--org-id', '1', '--region-id', 'test'],
{ mockDir: 'single_org' },
);
});

test('list org VPC endpoints implicit org', async ({ testCliCommand }) => {
await testCliCommand(['vpc', 'endpoint', 'list', '--region-id', 'test'], {
mockDir: 'single_org',
});
});

test('update org VPC endpoint', async ({ testCliCommand }) => {
await testCliCommand(
[
'vpc',
'endpoint',
'update',
'vpc-test',
'--label',
'newlabel',
'--region-id',
'test',
],
{ mockDir: 'single_org' },
);
});

test('delete org VPC endpoint', async ({ testCliCommand }) => {
await testCliCommand(
['vpc', 'endpoint', 'remove', 'vpc-test', '--region-id', 'test'],
{ mockDir: 'single_org' },
);
});

test('get org VPC endpoint status', async ({ testCliCommand }) => {
await testCliCommand(
['vpc', 'endpoint', 'status', 'vpc-test', '--region-id', 'test'],
{ mockDir: 'single_org' },
);
});

test('set org VPC endpoint in azure', async ({ testCliCommand }) => {
await testCliCommand(
[
'vpc',
'endpoint',
'update',
'vpc-test',
'--label',
'newlabel',
'--region-id',
'azure-test',
],
{
mockDir: 'single_org',
stderr:
'INFO: VPC endpoint configuration is not supported for Azure regions',
},
);
});

test('list project VPC endpoint restrictions', async ({ testCliCommand }) => {
await testCliCommand(
[
'vpc',
'project',
'list',
'--region-id',
'test',
'--project-id',
'test-project-123456',
],
{ mockDir: 'single_org' },
);
});

test('list project VPC endpoint restrictions with implicit project', async ({
testCliCommand,
}) => {
await testCliCommand(['vpc', 'project', 'list', '--region-id', 'test'], {
mockDir: 'single_org',
});
});

test('set project VPC endpoint restrictions', async ({ testCliCommand }) => {
await testCliCommand(
['vpc', 'project', 'update', 'vpc-test', '--label', 'newlabel'],
{ mockDir: 'single_org' },
);
});

test('delete project VPC endpoint restrictions', async ({
testCliCommand,
}) => {
await testCliCommand(['vpc', 'project', 'remove', 'vpc-test'], {
mockDir: 'single_org',
});
});
});
Loading

0 comments on commit 3f9d6d8

Please sign in to comment.