Skip to content

Commit 9c164aa

Browse files
committed
feat(configure): add --instance preflight validation for configure
Add capability to validate instance names before proceeding with operations. The validation makes a request to the running instance's liveness check endpoint to verify that the instance exists and is accessible.
1 parent 16b3ab9 commit 9c164aa

File tree

5 files changed

+110
-1
lines changed

5 files changed

+110
-1
lines changed

src/configure.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import dotenv from 'dotenv';
1414
import { availableClients, ensureClientsLoaded } from './configure/index.js';
1515
import { VERSION } from './common/version.js';
1616
import { ensureAuthTokenPresence } from './auth/auth.js';
17-
import { trace } from './log/logger.js';
17+
import { trace, error } from './log/logger.js';
18+
import { validateInstance } from './util/preflight.js';
1819

1920
/**
2021
* Configure options interface
@@ -118,6 +119,16 @@ export async function configure(client: string, options: ConfigureOptions) {
118119
const homedir = os.homedir();
119120
const configFilePath = clientConfig.configFilePath(homedir);
120121

122+
if (options.instance && process.env._SKIP_INSTANCE_PREFLIGHT !== 'true') {
123+
trace(`Validating instance: ${options.instance}...`);
124+
if (!(await validateInstance(options.instance))) {
125+
error(`Error validating instance: ${options.instance}`);
126+
console.error(`Could not validate instance: ${options.instance}`);
127+
console.error('Please check the `--instance` and try again.');
128+
process.exit(1);
129+
}
130+
}
131+
121132
try {
122133
// If token is provided, use token auth
123134
if (options.token) {

src/test/cli.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ describe('CLI', () => {
3737
});
3838

3939
beforeEach(async () => {
40+
process.env._SKIP_INSTANCE_PREFLIGHT = 'true';
4041
project = await setupProject();
4142
});
4243

4344
afterEach(() => {
4445
teardownProject();
46+
delete process.env._SKIP_INSTANCE_PREFLIGHT;
4547
});
4648

4749
it('shows help when no arguments provided', async () => {

src/test/mocks/handlers.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
import { http, HttpResponse } from 'msw';
22

33
export const handlers = [
4+
http.get(
5+
'https://:instance-be.glean.com/liveness_check',
6+
async ({ params }) => {
7+
const { instance } = params;
8+
9+
if (instance === 'invalid-instance') {
10+
return new HttpResponse(null, {
11+
status: 404,
12+
statusText: 'Not Found',
13+
});
14+
}
15+
16+
if (instance === 'network-error') {
17+
const error = new Error('Network error');
18+
error.name = 'FetchError';
19+
throw error;
20+
}
21+
22+
return new HttpResponse(JSON.stringify({ status: 'ok' }), {
23+
status: 200,
24+
headers: {
25+
'Content-Type': 'application/json',
26+
},
27+
});
28+
},
29+
),
430
http.post(
531
'https://:instance-be.glean.com/rest/api/v1/search',
632
async ({ request }) => {

src/test/util/preflight.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, it, expect, beforeEach } from 'vitest';
2+
import { validateInstance } from '../../util/preflight.js';
3+
import '../mocks/setup';
4+
5+
describe('Preflight Validation', () => {
6+
beforeEach(() => {
7+
// Reset any environment variables that might affect the tests
8+
delete process.env.GLEAN_BASE_URL;
9+
delete process.env.GLEAN_INSTANCE;
10+
});
11+
12+
it('returns true for a valid instance name', async () => {
13+
const result = await validateInstance('valid-instance');
14+
expect(result).toBe(true);
15+
});
16+
17+
it('returns false for invalid instance name', async () => {
18+
expect(await validateInstance('invalid-instance')).toEqual(false);
19+
});
20+
21+
it('returns false for network errors', async () => {
22+
expect(await validateInstance('network-error')).toEqual(false);
23+
});
24+
25+
it('returns false if `instance` is missing', async () => {
26+
expect(await validateInstance('')).toEqual(false);
27+
});
28+
});

src/util/preflight.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { trace, error } from '../log/logger.js';
2+
3+
/**
4+
* Validates that the given instance name is valid by checking its liveness endpoint.
5+
* Makes a fetch request to https://{instance}-be.glean.com/liveness_check
6+
*
7+
* @param instance - The instance name to validate
8+
* @returns A Promise that resolves to true if the instance is valid
9+
*/
10+
export async function validateInstance(instance: string): Promise<boolean> {
11+
if (!instance) {
12+
trace('No instance provided for validation');
13+
return false;
14+
}
15+
16+
try {
17+
const url = `https://${instance}-be.glean.com/liveness_check`;
18+
trace(`Checking instance validity with: ${url}`);
19+
20+
const response = await fetch(url, {
21+
method: 'GET',
22+
headers: {
23+
Accept: 'application/json',
24+
},
25+
});
26+
27+
// We only care that the request succeeds, not about the response content
28+
if (!response.ok) {
29+
error(
30+
`Instance validation failed for ${instance}: ${response.status} ${response.statusText}`,
31+
);
32+
return false;
33+
}
34+
35+
return true;
36+
} catch (err) {
37+
const cause = err instanceof Error ? err : new Error(String(err));
38+
39+
error(`Instance validation failed: ${cause.message}`);
40+
return false;
41+
}
42+
}

0 commit comments

Comments
 (0)