feat: add gke cluster locator resource label filtering (#10908)

* feat: add gke cluster locator resource label filtering

Signed-off-by: Matthew Clarke <mclarke@spotify.com>

* test: add gke label filter tests

Signed-off-by: Matthew Clarke <mclarke@spotify.com>

* docs: add gke label filtering changeset

Signed-off-by: Matthew Clarke <mclarke@spotify.com>

* style: prettier

Signed-off-by: Matthew Clarke <mclarke@spotify.com>

* docs: add gke label matching docs

Signed-off-by: Matthew Clarke <mclarke@spotify.com>

* style: prettier

Signed-off-by: Matthew Clarke <mclarke@spotify.com>
This commit is contained in:
Matthew Clarke
2022-04-20 13:36:54 -04:00
committed by GitHub
parent 40429e9695
commit b9f7ffb162
4 changed files with 169 additions and 22 deletions
@@ -202,6 +202,115 @@ describe('GkeClusterLocator', () => {
parent: 'projects/some-project/locations/some-region',
});
});
it('dont filter out clusters when no label matcher provided', async () => {
mockedListClusters.mockReturnValueOnce([
{
clusters: [
{
name: 'some-cluster',
endpoint: '1.2.3.4',
resourceLabels: {
foo: 'bar',
},
},
{
name: 'some-other-cluster',
endpoint: '6.7.8.9',
resourceLabels: {
something: 'other',
},
},
],
},
]);
const config: Config = new ConfigReader({
type: 'gke',
projectId: 'some-project',
region: 'some-region',
});
const sut = GkeClusterLocator.fromConfigWithClient(config, {
listClusters: mockedListClusters,
} as any);
const result = await sut.getClusters();
expect(result).toStrictEqual([
{
authProvider: 'google',
name: 'some-cluster',
url: 'https://1.2.3.4',
skipTLSVerify: false,
skipMetricsLookup: false,
},
{
authProvider: 'google',
name: 'some-other-cluster',
url: 'https://6.7.8.9',
skipTLSVerify: false,
skipMetricsLookup: false,
},
]);
expect(mockedListClusters).toBeCalledTimes(1);
expect(mockedListClusters).toHaveBeenCalledWith({
parent: 'projects/some-project/locations/some-region',
});
});
it('filter out clusters without matching resource labels', async () => {
mockedListClusters.mockReturnValueOnce([
{
clusters: [
{
name: 'some-cluster',
endpoint: '1.2.3.4',
resourceLabels: {
foo: 'bar',
},
},
{
name: 'some-other-cluster',
endpoint: '6.7.8.9',
resourceLabels: {
something: 'other',
},
},
],
},
]);
const config: Config = new ConfigReader({
type: 'gke',
projectId: 'some-project',
region: 'some-region',
matchingResourceLabels: [
{
key: 'foo',
value: 'bar',
},
],
});
const sut = GkeClusterLocator.fromConfigWithClient(config, {
listClusters: mockedListClusters,
} as any);
const result = await sut.getClusters();
expect(result).toStrictEqual([
{
authProvider: 'google',
name: 'some-cluster',
url: 'https://1.2.3.4',
skipTLSVerify: false,
skipMetricsLookup: false,
},
]);
expect(mockedListClusters).toBeCalledTimes(1);
expect(mockedListClusters).toHaveBeenCalledWith({
parent: 'projects/some-project/locations/some-region',
});
});
it('Handle errors gracefully', async () => {
mockedListClusters.mockImplementation(() => {
throw new Error('some error');
@@ -25,12 +25,18 @@ import {
KubernetesClustersSupplier,
} from '../types/types';
interface MatchResourceLabelEntry {
key: string;
value: string;
}
type GkeClusterLocatorOptions = {
projectId: string;
region?: string;
skipTLSVerify?: boolean;
skipMetricsLookup?: boolean;
exposeDashboard?: boolean;
matchingResourceLabels?: MatchResourceLabelEntry[];
};
export class GkeClusterLocator implements KubernetesClustersSupplier {
@@ -44,8 +50,13 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
static fromConfigWithClient(
config: Config,
client: container.v1.ClusterManagerClient,
refreshInterval: Duration | undefined = undefined,
refreshInterval?: Duration,
): GkeClusterLocator {
const matchingResourceLabels: MatchResourceLabelEntry[] =
config.getOptionalConfigArray('matchingResourceLabels')?.map(mrl => {
return { key: mrl.getString('key'), value: mrl.getString('value') };
}) ?? [];
const options = {
projectId: config.getString('projectId'),
region: config.getOptionalString('region') ?? '-',
@@ -53,6 +64,7 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
skipMetricsLookup:
config.getOptionalBoolean('skipMetricsLookup') ?? false,
exposeDashboard: config.getOptionalBoolean('exposeDashboard') ?? false,
matchingResourceLabels,
};
const gkeClusterLocator = new GkeClusterLocator(options, client);
if (refreshInterval) {
@@ -91,6 +103,7 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
skipTLSVerify,
skipMetricsLookup,
exposeDashboard,
matchingResourceLabels,
} = this.options;
const request = {
parent: `projects/${projectId}/locations/${region}`,
@@ -98,24 +111,33 @@ export class GkeClusterLocator implements KubernetesClustersSupplier {
try {
const [response] = await this.client.listClusters(request);
this.clusterDetails = (response.clusters ?? []).map(r => ({
// TODO filter out clusters which don't have name or endpoint
name: r.name ?? 'unknown',
url: `https://${r.endpoint ?? ''}`,
authProvider: 'google',
skipTLSVerify,
skipMetricsLookup,
...(exposeDashboard
? {
dashboardApp: 'gke',
dashboardParameters: {
projectId,
region,
clusterName: r.name,
},
this.clusterDetails = (response.clusters ?? [])
.filter(r => {
return matchingResourceLabels?.every(mrl => {
if (!r.resourceLabels) {
return false;
}
: {}),
}));
return r.resourceLabels[mrl.key] === mrl.value;
});
})
.map(r => ({
// TODO filter out clusters which don't have name or endpoint
name: r.name ?? 'unknown',
url: `https://${r.endpoint ?? ''}`,
authProvider: 'google',
skipTLSVerify,
skipMetricsLookup,
...(exposeDashboard
? {
dashboardApp: 'gke',
dashboardParameters: {
projectId,
region,
clusterName: r.name,
},
}
: {}),
}));
this.hasClusterDetails = true;
} catch (e) {
throw new ForwardedError(