Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.11] [Fleet] Fix 500 in Fleet API when request to product versions endpoint throws ECONNREFUSED (#172850) #172864

Merged
merged 1 commit into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions x-pack/plugins/fleet/server/services/agents/versions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,29 @@ describe('getAvailableVersions', () => {
expect(mockedFetch).toBeCalledTimes(1);
expect(res2).not.toContain('300.0.0');
});

it('should gracefully handle 400 errors when fetching from product versions API', async () => {
mockKibanaVersion = '300.0.0';
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
mockedFetch.mockResolvedValue({
status: 400,
text: 'Bad request',
} as any);

const res = await getAvailableVersions({ ignoreCache: true });

// Should sort, uniquify and filter out versions < 7.17
expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']);
});

it('should gracefully handle network errors when fetching from product versions API', async () => {
mockKibanaVersion = '300.0.0';
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
mockedFetch.mockRejectedValue('ECONNREFUSED');

const res = await getAvailableVersions({ ignoreCache: true });

// Should sort, uniquify and filter out versions < 7.17
expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']);
});
});
37 changes: 23 additions & 14 deletions x-pack/plugins/fleet/server/services/agents/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const AGENT_VERSION_BUILD_FILE = 'x-pack/plugins/fleet/target/agent_versions_lis

// Endpoint maintained by the web-team and hosted on the elastic website
const PRODUCT_VERSIONS_URL = 'https://www.elastic.co/api/product_versions';
const MAX_REQUEST_TIMEOUT = 60 * 1000; // Only attempt to fetch product versions for one minute total

// Cache available versions in memory for 1 hour
const CACHE_DURATION = 1000 * 60 * 60;
Expand Down Expand Up @@ -118,21 +119,29 @@ async function fetchAgentVersionsFromApi() {
},
};

const response = await pRetry(() => fetch(PRODUCT_VERSIONS_URL, options), { retries: 1 });
const rawBody = await response.text();

// We need to handle non-200 responses gracefully here to support airgapped environments where
// Kibana doesn't have internet access to query this API
if (response.status >= 400) {
logger.debug(`Status code ${response.status} received from versions API: ${rawBody}`);
return [];
}
try {
const response = await pRetry(() => fetch(PRODUCT_VERSIONS_URL, options), {
retries: 1,
maxRetryTime: MAX_REQUEST_TIMEOUT,
});
const rawBody = await response.text();

// We need to handle non-200 responses gracefully here to support airgapped environments where
// Kibana doesn't have internet access to query this API
if (response.status >= 400) {
logger.debug(`Status code ${response.status} received from versions API: ${rawBody}`);
return [];
}

const jsonBody = JSON.parse(rawBody);
const jsonBody = JSON.parse(rawBody);

const versions: string[] = (jsonBody.length ? jsonBody[0] : [])
.filter((item: any) => item?.title?.includes('Elastic Agent'))
.map((item: any) => item?.version_number);
const versions: string[] = (jsonBody.length ? jsonBody[0] : [])
.filter((item: any) => item?.title?.includes('Elastic Agent'))
.map((item: any) => item?.version_number);

return versions;
return versions;
} catch (error) {
logger.debug(`Error fetching available versions from API: ${error.message}`);
return [];
}
}