@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { PuppetDBEntityProvider } from './PuppetDBEntityProvider';
|
||||
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
|
||||
import * as p from '../puppet/read';
|
||||
import { DEFAULT_OWNER } from './constants';
|
||||
import { ANNOTATION_PUPPET_CERTNAME } from '../puppet';
|
||||
import {
|
||||
ANNOTATION_LOCATION,
|
||||
ANNOTATION_ORIGIN_LOCATION,
|
||||
} from '@backstage/catalog-model/';
|
||||
import { ENDPOINT_NODES } from '../puppet/constants';
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
jest.mock('../puppet/read', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
readPuppetNodes: null,
|
||||
}));
|
||||
|
||||
class PersistingTaskRunner implements TaskRunner {
|
||||
private tasks: TaskInvocationDefinition[] = [];
|
||||
|
||||
getTasks() {
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
run(task: TaskInvocationDefinition): Promise<void> {
|
||||
this.tasks.push(task);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
describe('PuppetEntityProvider', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
puppetdb: {
|
||||
host: 'http://puppetdb:8080',
|
||||
schedule: {
|
||||
frequency: {
|
||||
minutes: 10,
|
||||
},
|
||||
timeout: {
|
||||
minutes: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('where there are no nodes', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
p.readPuppetNodes = jest.fn().mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it('creates no entities', async () => {
|
||||
const connection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const providers = PuppetDBEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule: new PersistingTaskRunner(),
|
||||
});
|
||||
|
||||
await providers[0].connect(connection);
|
||||
await providers[0].refresh(logger);
|
||||
|
||||
expect(connection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'full',
|
||||
entities: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('where there are nodes', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
p.readPuppetNodes = jest.fn().mockResolvedValue([
|
||||
{
|
||||
api_version: 'backstage.io/v1beta1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'node1',
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
|
||||
},
|
||||
tags: ['windows'],
|
||||
description: 'Description 1',
|
||||
spec: {
|
||||
type: 'virtual-machine',
|
||||
owner: DEFAULT_OWNER,
|
||||
dependsOn: [],
|
||||
dependencyOf: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
api_version: 'backstage.io/v1beta1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'node2',
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
[ANNOTATION_PUPPET_CERTNAME]: 'node2',
|
||||
},
|
||||
tags: ['linux'],
|
||||
description: 'Description 2',
|
||||
spec: {
|
||||
type: 'physical-server',
|
||||
owner: DEFAULT_OWNER,
|
||||
dependsOn: [],
|
||||
dependencyOf: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('creates entities', async () => {
|
||||
const connection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const providers = PuppetDBEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule: new PersistingTaskRunner(),
|
||||
});
|
||||
|
||||
await providers[0].connect(connection);
|
||||
await providers[0].refresh(logger);
|
||||
|
||||
expect(connection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'full',
|
||||
entities: [
|
||||
{
|
||||
locationKey: providers[0].getProviderName(),
|
||||
entity: {
|
||||
api_version: 'backstage.io/v1beta1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'node1',
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
|
||||
[ANNOTATION_LOCATION]: `url:${config.getString(
|
||||
'catalog.providers.puppetdb.host',
|
||||
)}${ENDPOINT_NODES}/node1`,
|
||||
[ANNOTATION_ORIGIN_LOCATION]: `url:${config.getString(
|
||||
'catalog.providers.puppetdb.host',
|
||||
)}${ENDPOINT_NODES}/node1`,
|
||||
},
|
||||
tags: ['windows'],
|
||||
description: 'Description 1',
|
||||
spec: {
|
||||
type: 'virtual-machine',
|
||||
owner: DEFAULT_OWNER,
|
||||
dependsOn: [],
|
||||
dependencyOf: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
locationKey: providers[0].getProviderName(),
|
||||
entity: {
|
||||
api_version: 'backstage.io/v1beta1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'node2',
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
[ANNOTATION_PUPPET_CERTNAME]: 'node2',
|
||||
[ANNOTATION_LOCATION]: `url:${config.getString(
|
||||
'catalog.providers.puppetdb.host',
|
||||
)}${ENDPOINT_NODES}/node2`,
|
||||
[ANNOTATION_ORIGIN_LOCATION]: `url:${config.getString(
|
||||
'catalog.providers.puppetdb.host',
|
||||
)}${ENDPOINT_NODES}/node2`,
|
||||
},
|
||||
tags: ['linux'],
|
||||
description: 'Description 2',
|
||||
spec: {
|
||||
type: 'physical-server',
|
||||
owner: DEFAULT_OWNER,
|
||||
dependsOn: [],
|
||||
dependencyOf: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { readProviderConfigs } from './PuppetDBEntityProviderConfig';
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
describe('readProviderConfigs', () => {
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
it('no provider config', () => {
|
||||
const config = new ConfigReader({});
|
||||
const providerConfigs = readProviderConfigs(config);
|
||||
|
||||
expect(providerConfigs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('single simple provider config', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
puppetdb: {
|
||||
host: 'https://puppetdb',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const providerConfigs = readProviderConfigs(config);
|
||||
|
||||
expect(providerConfigs).toHaveLength(1);
|
||||
expect(providerConfigs[0].id).toEqual('default');
|
||||
expect(providerConfigs[0].host).toEqual('https://puppetdb');
|
||||
});
|
||||
|
||||
it('single specific provider config', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
puppetdb: {
|
||||
'my-provider': {
|
||||
host: 'https://puppetdb',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const providerConfigs = readProviderConfigs(config);
|
||||
|
||||
expect(providerConfigs).toHaveLength(1);
|
||||
expect(providerConfigs[0].id).toEqual('my-provider');
|
||||
expect(providerConfigs[0].host).toEqual('https://puppetdb');
|
||||
});
|
||||
|
||||
it('multiple provider configs', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
puppetdb: {
|
||||
'my-provider': {
|
||||
host: 'https://my-puppet/',
|
||||
query: 'my-query',
|
||||
},
|
||||
'your-provider': {
|
||||
host: 'https://your-puppet',
|
||||
query: 'your-query',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const providerConfigs = readProviderConfigs(config);
|
||||
|
||||
expect(providerConfigs).toHaveLength(2);
|
||||
expect(providerConfigs[0]).toEqual({
|
||||
id: 'my-provider',
|
||||
host: 'https://my-puppet',
|
||||
query: 'my-query',
|
||||
});
|
||||
expect(providerConfigs[1]).toEqual({
|
||||
id: 'your-provider',
|
||||
host: 'https://your-puppet',
|
||||
query: 'your-query',
|
||||
});
|
||||
});
|
||||
|
||||
it('provider config with schedule', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
puppetdb: {
|
||||
host: 'https://puppetdb',
|
||||
schedule: {
|
||||
frequency: 'PT30M',
|
||||
timeout: {
|
||||
minutes: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const providerConfigs = readProviderConfigs(config);
|
||||
|
||||
expect(providerConfigs).toHaveLength(1);
|
||||
expect(providerConfigs[0].schedule).toEqual({
|
||||
frequency: Duration.fromISO('PT30M'),
|
||||
timeout: {
|
||||
minutes: 10,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { readPuppetNodes } from './read';
|
||||
import {
|
||||
DEFAULT_PROVIDER_ID,
|
||||
PuppetDBEntityProviderConfig,
|
||||
} from '../providers';
|
||||
import { DEFAULT_NAMESPACE } from '@backstage/catalog-model';
|
||||
import fetch from 'node-fetch';
|
||||
import { ANNOTATION_PUPPET_CERTNAME, ENDPOINT_FACTSETS } from './constants';
|
||||
|
||||
jest.mock('node-fetch', () => {
|
||||
const original = jest.requireActual('node-fetch');
|
||||
return {
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
Headers: original.Headers,
|
||||
};
|
||||
});
|
||||
(global as any).fetch = fetch;
|
||||
|
||||
describe('readPuppetNodes', () => {
|
||||
const mockFetch = fetch as unknown as jest.Mocked<any>;
|
||||
|
||||
describe('where no query is specified', () => {
|
||||
const config: PuppetDBEntityProviderConfig = {
|
||||
host: 'https://puppetdb',
|
||||
id: DEFAULT_PROVIDER_ID,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
mockFetch.mockReturnValueOnce(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify([
|
||||
{
|
||||
certname: 'node1',
|
||||
timestamp: 'time1',
|
||||
hash: 'hash1',
|
||||
producer_timestamp: 'producer_time1',
|
||||
producer: 'producer1',
|
||||
environment: 'environment1',
|
||||
facts: {
|
||||
data: [
|
||||
{
|
||||
name: 'is_virtual',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'kernel',
|
||||
value: 'Linux',
|
||||
},
|
||||
{
|
||||
name: 'ipaddress',
|
||||
value: 'ipaddress1',
|
||||
},
|
||||
{
|
||||
name: 'clientnoop',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'clientversion',
|
||||
value: 'clientversion1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
certname: 'node2',
|
||||
timestamp: 'time2',
|
||||
hash: 'hash2',
|
||||
producer_timestamp: 'producer_time2',
|
||||
producer: 'producer2',
|
||||
environment: 'environment2',
|
||||
facts: {
|
||||
data: [
|
||||
{
|
||||
name: 'is_virtual',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'kernel',
|
||||
value: 'Windows',
|
||||
},
|
||||
{
|
||||
name: 'ipaddress',
|
||||
value: 'ipaddress2',
|
||||
},
|
||||
{
|
||||
name: 'clientnoop',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'clientversion',
|
||||
value: 'clientversion2',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
describe('where custom transformer is used', () => {
|
||||
it('should use it for transforming puppet nodes', async () => {
|
||||
const entities = await readPuppetNodes(config, {
|
||||
transformer: async (node, _config) => {
|
||||
return {
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: `custom-${node.certname}`,
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
},
|
||||
spec: {
|
||||
type: 'Custom',
|
||||
owner: 'Custom',
|
||||
dependsOn: [],
|
||||
dependencyOf: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
expect(entities).toHaveLength(2);
|
||||
expect(entities[0]).toEqual({
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'custom-node1',
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
},
|
||||
spec: {
|
||||
type: 'Custom',
|
||||
owner: 'Custom',
|
||||
dependsOn: [],
|
||||
dependencyOf: [],
|
||||
},
|
||||
});
|
||||
expect(entities[1]).toEqual({
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'custom-node2',
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
},
|
||||
spec: {
|
||||
type: 'Custom',
|
||||
owner: 'Custom',
|
||||
dependsOn: [],
|
||||
dependencyOf: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('where default transformer is used', () => {
|
||||
it('should use it for transforming puppet nodes', async () => {
|
||||
const entities = await readPuppetNodes(config);
|
||||
|
||||
expect(entities).toHaveLength(2);
|
||||
expect(entities[0].metadata.annotations).toEqual({
|
||||
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
|
||||
});
|
||||
expect(entities[1].metadata.annotations).toEqual({
|
||||
[ANNOTATION_PUPPET_CERTNAME]: 'node2',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('where query is specified', () => {
|
||||
const config: PuppetDBEntityProviderConfig = {
|
||||
host: 'https://puppetdb',
|
||||
id: DEFAULT_PROVIDER_ID,
|
||||
query: '["=", "certname", "node1"]',
|
||||
};
|
||||
|
||||
describe('where no results are matched', () => {
|
||||
beforeEach(async () => {
|
||||
mockFetch.mockReturnValueOnce(
|
||||
Promise.resolve(new Response(JSON.stringify([]))),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty array', async () => {
|
||||
const entities = await readPuppetNodes(config);
|
||||
expect(entities).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('where results are matched', () => {
|
||||
beforeEach(async () => {
|
||||
mockFetch.mockReturnValueOnce(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify([
|
||||
{
|
||||
certname: 'node1',
|
||||
timestamp: 'time1',
|
||||
hash: 'hash1',
|
||||
producer_timestamp: 'producer_time1',
|
||||
producer: 'producer1',
|
||||
environment: 'environment1',
|
||||
},
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return matched results', async () => {
|
||||
const entities = await readPuppetNodes(config);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
`${config.host}${ENDPOINT_FACTSETS}?query=%5B%22%3D%22%2C+%22certname%22%2C+%22node1%22%5D`,
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
},
|
||||
);
|
||||
expect(entities).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PuppetDBEntityProviderConfig } from '../providers';
|
||||
import { PuppetNode } from './types';
|
||||
import { defaultResourceTransformer } from './transformers';
|
||||
import { DEFAULT_NAMESPACE } from '@backstage/catalog-model';
|
||||
import { DEFAULT_OWNER } from '../providers';
|
||||
import { ANNOTATION_PUPPET_CERTNAME } from './constants';
|
||||
|
||||
describe('defaultResourceTransformer', () => {
|
||||
it('should transform a puppet node to a resource entity', async () => {
|
||||
const config: PuppetDBEntityProviderConfig = {
|
||||
host: '',
|
||||
id: '',
|
||||
};
|
||||
const node: PuppetNode = {
|
||||
certname: 'node1',
|
||||
timestamp: 'time1',
|
||||
hash: 'hash1',
|
||||
producer_timestamp: 'producer_time1',
|
||||
producer: 'producer1',
|
||||
environment: 'environment1',
|
||||
facts: {
|
||||
href: 'facts1',
|
||||
data: [
|
||||
{
|
||||
name: 'kernel',
|
||||
value: 'Linux',
|
||||
},
|
||||
{
|
||||
name: 'ipaddress',
|
||||
value: 'ipaddress1',
|
||||
},
|
||||
{
|
||||
name: 'is_virtual',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'clientnoop',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'clientversion',
|
||||
value: 'clientversion1',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const entity = await defaultResourceTransformer(node, config);
|
||||
expect(entity).toEqual({
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'node1',
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
annotations: {
|
||||
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
|
||||
},
|
||||
description: 'ipaddress1',
|
||||
tags: ['linux'],
|
||||
},
|
||||
spec: {
|
||||
type: 'virtual-machine',
|
||||
owner: DEFAULT_OWNER,
|
||||
dependsOn: [],
|
||||
dependencyOf: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user