Merge branch 'master' into add-support-for-plugin-specific-db
This commit is contained in:
@@ -30,9 +30,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@asyncapi/react-component": "^0.23.0",
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -49,7 +49,7 @@
|
||||
"swagger-ui-react": "^3.37.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/config-loader": "^0.6.1",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -40,7 +40,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^0.20.5",
|
||||
"supertest": "^6.1.3"
|
||||
|
||||
@@ -38,7 +38,9 @@ export async function startStandaloneServer(
|
||||
appPackageName: 'example-app',
|
||||
});
|
||||
|
||||
const service = createServiceBuilder(module).addRouter('', router);
|
||||
const service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('', router);
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.1",
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
@@ -68,7 +68,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.13",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.2",
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.1",
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -45,7 +45,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.13",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
|
||||
@@ -40,9 +40,12 @@ export async function startStandaloneServer(
|
||||
|
||||
const router = await createRouter({ config, discovery });
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/badges', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -34,7 +34,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -37,7 +37,7 @@
|
||||
"recharts": "^1.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @backstage/plugin-catalog-backend
|
||||
|
||||
## 0.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9c63be545: Restructure the next catalog types and files a bit
|
||||
- Updated dependencies [92963779b]
|
||||
- Updated dependencies [27a9b503a]
|
||||
- Updated dependencies [70bc30c5b]
|
||||
- Updated dependencies [db1c8f93b]
|
||||
- Updated dependencies [5aff84759]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/backend-common@0.8.2
|
||||
- @backstage/catalog-model@0.8.2
|
||||
- @backstage/catalog-client@0.3.13
|
||||
- @backstage/search-common@0.1.2
|
||||
- @backstage/plugin-search-backend-node@0.2.0
|
||||
- @backstage/integration@0.5.6
|
||||
|
||||
## 0.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
return (
|
||||
knex.schema
|
||||
//
|
||||
// locations
|
||||
//
|
||||
.createTable('locations', table => {
|
||||
table.comment(
|
||||
'Registered locations that shall be contiuously scanned for catalog item updates',
|
||||
);
|
||||
table
|
||||
.uuid('id')
|
||||
.primary()
|
||||
.notNullable()
|
||||
.comment('Auto-generated ID of the location');
|
||||
table.string('type').notNullable().comment('The type of location');
|
||||
table
|
||||
.string('target')
|
||||
.notNullable()
|
||||
.comment('The actual target of the location');
|
||||
})
|
||||
//
|
||||
// entities
|
||||
//
|
||||
.createTable('entities', table => {
|
||||
table.comment('All entities currently stored in the catalog');
|
||||
table.uuid('id').primary().comment('Auto-generated ID of the entity');
|
||||
table
|
||||
.uuid('location_id')
|
||||
.references('id')
|
||||
.inTable('locations')
|
||||
.nullable()
|
||||
.comment('The location that originated the entity');
|
||||
table
|
||||
.string('etag')
|
||||
.notNullable()
|
||||
.comment(
|
||||
'An opaque string that changes for each update operation to any part of the entity, including metadata.',
|
||||
);
|
||||
table
|
||||
.string('generation')
|
||||
.notNullable()
|
||||
.unsigned()
|
||||
.comment(
|
||||
'A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes.',
|
||||
);
|
||||
table
|
||||
.string('api_version')
|
||||
.notNullable()
|
||||
.comment('The apiVersion field of the entity');
|
||||
table
|
||||
.string('kind')
|
||||
.notNullable()
|
||||
.comment('The kind field of the entity');
|
||||
table
|
||||
.string('name')
|
||||
.nullable()
|
||||
.comment('The metadata.name field of the entity');
|
||||
table
|
||||
.string('namespace')
|
||||
.nullable()
|
||||
.comment('The metadata.namespace field of the entity');
|
||||
table
|
||||
.string('metadata')
|
||||
.notNullable()
|
||||
.comment('The entire metadata JSON blob of the entity');
|
||||
table
|
||||
.string('spec')
|
||||
.nullable()
|
||||
.comment('The entire spec JSON blob of the entity');
|
||||
})
|
||||
.alterTable('entities', table => {
|
||||
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
|
||||
table.unique(['kind', 'name', 'namespace'], 'entities_unique_name');
|
||||
})
|
||||
//
|
||||
// entities_search
|
||||
//
|
||||
.createTable('entities_search', table => {
|
||||
table.comment(
|
||||
'Flattened key-values from the entities, used for quick filtering',
|
||||
);
|
||||
table
|
||||
.uuid('entity_id')
|
||||
.references('id')
|
||||
.inTable('entities')
|
||||
.onDelete('CASCADE')
|
||||
.comment('The entity that matches this key/value');
|
||||
table
|
||||
.string('key')
|
||||
.notNullable()
|
||||
.comment('A key that occurs in the entity');
|
||||
table
|
||||
.string('value')
|
||||
.nullable()
|
||||
.comment('The corresponding value to match on');
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
return knex.schema
|
||||
.dropTable('entities_search')
|
||||
.alterTable('entities', table => {
|
||||
table.dropUnique([], 'entities_unique_name');
|
||||
})
|
||||
.dropTable('entities')
|
||||
.dropTable('locations');
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
return knex.schema.createTable('location_update_log', table => {
|
||||
table.uuid('id').primary();
|
||||
table.enum('status', ['success', 'fail']).notNullable();
|
||||
table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable();
|
||||
table.string('message');
|
||||
table
|
||||
.uuid('location_id')
|
||||
.references('id')
|
||||
.inTable('locations')
|
||||
.onUpdate('CASCADE')
|
||||
.onDelete('CASCADE');
|
||||
table.string('entity_name').nullable();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
return knex.schema.dropTableIfExists('location_update_log');
|
||||
};
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
// Get list sorted by created_at timestamp in descending order
|
||||
// Grouped by location_id
|
||||
return knex.schema.raw(`
|
||||
CREATE VIEW location_update_log_latest AS
|
||||
SELECT t1.* FROM location_update_log t1
|
||||
JOIN
|
||||
(
|
||||
SELECT location_id, MAX(created_at) AS MAXDATE
|
||||
FROM location_update_log
|
||||
GROUP BY location_id
|
||||
) t2
|
||||
ON t1.location_id = t2.location_id
|
||||
AND t1.created_at = t2.MAXDATE
|
||||
ORDER BY created_at DESC;
|
||||
`);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
return knex.schema.raw(`DROP VIEW location_update_log_latest;`);
|
||||
};
|
||||
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
// SQLite does not support FK and PK
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.dropForeign(['entity_id']);
|
||||
});
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.dropPrimary('entities_pkey');
|
||||
});
|
||||
}
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.dropUnique([], 'entities_unique_name');
|
||||
});
|
||||
// Setup temporary tables
|
||||
await knex.schema.renameTable('entities_search', 'tmp_entities_search');
|
||||
await knex.schema.renameTable('entities', 'tmp_entities');
|
||||
|
||||
//
|
||||
// entities
|
||||
//
|
||||
await knex.schema
|
||||
.createTable('entities', table => {
|
||||
table.comment('All entities currently stored in the catalog');
|
||||
table.uuid('id').primary().comment('Auto-generated ID of the entity');
|
||||
table
|
||||
.uuid('location_id')
|
||||
.references('id')
|
||||
.inTable('locations')
|
||||
.nullable()
|
||||
.comment('The location that originated the entity');
|
||||
table
|
||||
.string('etag')
|
||||
.notNullable()
|
||||
.comment(
|
||||
'An opaque string that changes for each update operation to any part of the entity, including metadata.',
|
||||
);
|
||||
table
|
||||
.string('generation')
|
||||
.notNullable()
|
||||
.unsigned()
|
||||
.comment(
|
||||
'A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes.',
|
||||
);
|
||||
table
|
||||
.string('api_version')
|
||||
.notNullable()
|
||||
.comment('The apiVersion field of the entity');
|
||||
table
|
||||
.string('kind')
|
||||
.notNullable()
|
||||
.comment('The kind field of the entity');
|
||||
table
|
||||
.string('name')
|
||||
.nullable()
|
||||
.comment('The metadata.name field of the entity');
|
||||
table
|
||||
.string('namespace')
|
||||
.nullable()
|
||||
.comment('The metadata.namespace field of the entity');
|
||||
table
|
||||
.text('metadata')
|
||||
.notNullable()
|
||||
.comment('The entire metadata JSON blob of the entity');
|
||||
table
|
||||
.text('spec')
|
||||
.nullable()
|
||||
.comment('The entire spec JSON blob of the entity');
|
||||
})
|
||||
.alterTable('entities', table => {
|
||||
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
|
||||
table.unique(['kind', 'name', 'namespace'], 'entities_unique_name');
|
||||
});
|
||||
|
||||
await knex.schema.raw(`INSERT INTO entities SELECT * FROM tmp_entities`);
|
||||
|
||||
//
|
||||
// entities_search
|
||||
//
|
||||
await knex.schema.createTable('entities_search', table => {
|
||||
table.comment(
|
||||
'Flattened key-values from the entities, used for quick filtering',
|
||||
);
|
||||
table
|
||||
.uuid('entity_id')
|
||||
.references('id')
|
||||
.inTable('entities')
|
||||
.onDelete('CASCADE')
|
||||
.comment('The entity that matches this key/value');
|
||||
table
|
||||
.string('key')
|
||||
.notNullable()
|
||||
.comment('A key that occurs in the entity');
|
||||
table
|
||||
.string('value')
|
||||
.nullable()
|
||||
.comment('The corresponding value to match on');
|
||||
});
|
||||
await knex.schema.raw(
|
||||
`INSERT INTO entities_search SELECT * FROM tmp_entities_search`,
|
||||
);
|
||||
|
||||
// Clean up
|
||||
await knex.schema.dropTable('tmp_entities');
|
||||
return knex.schema.dropTable('tmp_entities_search');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
// SQLite does not support FK and PK
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.dropForeign(['entity_id']);
|
||||
});
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.dropPrimary('entities_pkey');
|
||||
});
|
||||
}
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.dropUnique([], 'entities_unique_name');
|
||||
});
|
||||
|
||||
// Setup temporary tables
|
||||
await knex.schema.renameTable('entities_search', 'tmp_entities_search');
|
||||
await knex.schema.renameTable('entities', 'tmp_entities');
|
||||
|
||||
//
|
||||
// entities
|
||||
//
|
||||
await knex.schema
|
||||
.createTable('entities', table => {
|
||||
table.comment('All entities currently stored in the catalog');
|
||||
table.uuid('id').primary().comment('Auto-generated ID of the entity');
|
||||
table
|
||||
.uuid('location_id')
|
||||
.references('id')
|
||||
.inTable('locations')
|
||||
.nullable()
|
||||
.comment('The location that originated the entity');
|
||||
table
|
||||
.string('etag')
|
||||
.notNullable()
|
||||
.comment(
|
||||
'An opaque string that changes for each update operation to any part of the entity, including metadata.',
|
||||
);
|
||||
table
|
||||
.string('generation')
|
||||
.notNullable()
|
||||
.unsigned()
|
||||
.comment(
|
||||
'A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes.',
|
||||
);
|
||||
table
|
||||
.string('api_version')
|
||||
.notNullable()
|
||||
.comment('The apiVersion field of the entity');
|
||||
table
|
||||
.string('kind')
|
||||
.notNullable()
|
||||
.comment('The kind field of the entity');
|
||||
table
|
||||
.string('name')
|
||||
.nullable()
|
||||
.comment('The metadata.name field of the entity');
|
||||
table
|
||||
.string('namespace')
|
||||
.nullable()
|
||||
.comment('The metadata.namespace field of the entity');
|
||||
table
|
||||
.string('metadata')
|
||||
.notNullable()
|
||||
.comment('The entire metadata JSON blob of the entity');
|
||||
table
|
||||
.string('spec')
|
||||
.nullable()
|
||||
.comment('The entire spec JSON blob of the entity');
|
||||
})
|
||||
.alterTable('entities', table => {
|
||||
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
|
||||
table.unique(['kind', 'name', 'namespace'], 'entities_unique_name');
|
||||
});
|
||||
|
||||
await knex.schema.raw(`INSERT INTO entities SELECT * FROM tmp_entities`);
|
||||
|
||||
//
|
||||
// entities_search
|
||||
//
|
||||
await knex.schema.createTable('entities_search', table => {
|
||||
table.comment(
|
||||
'Flattened key-values from the entities, used for quick filtering',
|
||||
);
|
||||
table
|
||||
.uuid('entity_id')
|
||||
.references('id')
|
||||
.inTable('entities')
|
||||
.onDelete('CASCADE')
|
||||
.comment('The entity that matches this key/value');
|
||||
table
|
||||
.string('key')
|
||||
.notNullable()
|
||||
.comment('A key that occurs in the entity');
|
||||
table
|
||||
.string('value')
|
||||
.nullable()
|
||||
.comment('The corresponding value to match on');
|
||||
});
|
||||
await knex.schema.raw(
|
||||
`INSERT INTO entities_search SELECT * FROM tmp_entities_search`,
|
||||
);
|
||||
|
||||
// Clean up
|
||||
await knex.schema.dropTable('tmp_entities');
|
||||
return knex.schema.dropTable('tmp_entities_search');
|
||||
};
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = function up(knex) {
|
||||
return knex.schema.raw(`DROP VIEW location_update_log_latest;`).raw(`
|
||||
CREATE VIEW location_update_log_latest AS
|
||||
SELECT t1.* FROM location_update_log t1
|
||||
JOIN
|
||||
(
|
||||
SELECT location_id, MAX(created_at) AS MAXDATE
|
||||
FROM location_update_log
|
||||
GROUP BY location_id
|
||||
) t2
|
||||
ON t1.location_id = t2.location_id
|
||||
AND t1.created_at = t2.MAXDATE
|
||||
GROUP BY t1.location_id, t1.id
|
||||
ORDER BY created_at DESC;
|
||||
`);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = function down(knex) {
|
||||
knex.schema.raw(`DROP VIEW location_update_log_latest;`);
|
||||
};
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = function up(knex) {
|
||||
return knex.schema
|
||||
.raw('DROP VIEW location_update_log_latest;')
|
||||
.dropTable('location_update_log')
|
||||
.createTable('location_update_log', table => {
|
||||
table.bigIncrements('id').primary(); // instead of uuid, so we can MAX it
|
||||
table.enum('status', ['success', 'fail']).notNullable();
|
||||
table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable();
|
||||
table.string('message');
|
||||
table
|
||||
.uuid('location_id')
|
||||
.references('id')
|
||||
.inTable('locations')
|
||||
.onUpdate('CASCADE')
|
||||
.onDelete('CASCADE');
|
||||
table.string('entity_name').nullable();
|
||||
}).raw(`
|
||||
CREATE VIEW location_update_log_latest AS
|
||||
SELECT t1.* FROM location_update_log t1
|
||||
JOIN
|
||||
(
|
||||
SELECT location_id, MAX(id) AS MAXID
|
||||
FROM location_update_log
|
||||
GROUP BY location_id
|
||||
) t2
|
||||
ON t1.location_id = t2.location_id
|
||||
AND t1.id = t2.MAXID
|
||||
GROUP BY t1.location_id, t1.id
|
||||
ORDER BY created_at DESC;
|
||||
`);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = function down(knex) {
|
||||
return knex.schema
|
||||
.raw('DROP VIEW location_update_log_latest;')
|
||||
.dropTable('location_update_log')
|
||||
.createTable('location_update_log', table => {
|
||||
table.uuid('id').primary();
|
||||
table.enum('status', ['success', 'fail']).notNullable();
|
||||
table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable();
|
||||
table.string('message');
|
||||
table
|
||||
.uuid('location_id')
|
||||
.references('id')
|
||||
.inTable('locations')
|
||||
.onUpdate('CASCADE')
|
||||
.onDelete('CASCADE');
|
||||
table.string('entity_name').nullable();
|
||||
}).raw(`
|
||||
CREATE VIEW location_update_log_latest AS
|
||||
SELECT t1.* FROM location_update_log t1
|
||||
JOIN
|
||||
(
|
||||
SELECT location_id, MAX(created_at) AS MAXDATE
|
||||
FROM location_update_log
|
||||
GROUP BY location_id
|
||||
) t2
|
||||
ON t1.location_id = t2.location_id
|
||||
AND t1.created_at = t2.MAXDATE
|
||||
GROUP BY t1.location_id, t1.id
|
||||
ORDER BY created_at DESC;
|
||||
`);
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
// Sqlite does not support alter column.
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.text('value').nullable().alter();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
// Sqlite does not support alter column.
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.string('value').nullable().alter();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
// Adds a single 'bootstrap' location that can be used to trigger work in processors.
|
||||
// This is primarily here to fulfill foreign key constraints.
|
||||
await knex('locations').insert({
|
||||
id: require('uuid').v4(),
|
||||
type: 'bootstrap',
|
||||
target: 'bootstrap',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex('locations')
|
||||
.where({
|
||||
type: 'bootstrap',
|
||||
target: 'bootstrap',
|
||||
})
|
||||
.del();
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex('entities')
|
||||
.where({ namespace: null })
|
||||
.update({ namespace: 'default' });
|
||||
await knex('entities_search').update({
|
||||
key: knex.raw('LOWER(key)'),
|
||||
value: knex.raw('LOWER(value)'),
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = async function down() {};
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.text('full_name').nullable();
|
||||
});
|
||||
|
||||
await knex('entities').update({
|
||||
full_name: knex.raw(
|
||||
"LOWER(kind) || ':' || LOWER(COALESCE(namespace, 'default')) || '/' || LOWER(name)",
|
||||
),
|
||||
});
|
||||
|
||||
// SQLite does not support alter column
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.text('full_name').notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
|
||||
table.unique(['full_name'], 'entities_unique_full_name');
|
||||
table.dropUnique([], 'entities_unique_name');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
|
||||
table.dropUnique([], 'entities_unique_full_name');
|
||||
table.unique(['kind', 'namespace', 'name'], 'entities_unique_name');
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.dropColumn('full_name');
|
||||
});
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table
|
||||
.text('data')
|
||||
.nullable()
|
||||
.comment('The entire JSON data blob of the entity');
|
||||
});
|
||||
|
||||
await knex('entities').update({
|
||||
// apiVersion and kind should not contain any JSON unsafe chars, and both
|
||||
// metadata and spec are already valid serialized JSON
|
||||
data: knex.raw(
|
||||
`'{"apiVersion":"' || api_version || '","kind":"' || kind || '","metadata":' || metadata || COALESCE(',"spec":' || spec, '') || '}'`,
|
||||
),
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.dropColumn('metadata');
|
||||
table.dropColumn('spec');
|
||||
});
|
||||
|
||||
// SQLite does not support ALTER COLUMN.
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.text('data').notNullable().alter();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table
|
||||
.text('metadata')
|
||||
.notNullable()
|
||||
.comment('The entire metadata JSON blob of the entity');
|
||||
table
|
||||
.text('spec')
|
||||
.nullable()
|
||||
.comment('The entire spec JSON blob of the entity');
|
||||
table.dropColumn('data');
|
||||
});
|
||||
};
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.dropColumn('api_version');
|
||||
table.dropColumn('kind');
|
||||
table.dropColumn('name');
|
||||
table.dropColumn('namespace');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table
|
||||
.string('api_version')
|
||||
.notNullable()
|
||||
.comment('The apiVersion field of the entity');
|
||||
table.string('kind').notNullable().comment('The kind field of the entity');
|
||||
table
|
||||
.string('name')
|
||||
.nullable()
|
||||
.comment('The metadata.name field of the entity');
|
||||
table
|
||||
.string('namespace')
|
||||
.nullable()
|
||||
.comment('The metadata.namespace field of the entity');
|
||||
});
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.index(['key'], 'entities_search_key');
|
||||
table.index(['value'], 'entities_search_value');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.dropIndex('', 'entities_search_key');
|
||||
table.dropIndex('', 'entities_search_value');
|
||||
});
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.createTable('entities_relations', table => {
|
||||
table.comment('All relations between entities in the catalog');
|
||||
table
|
||||
.uuid('originating_entity_id')
|
||||
.references('id')
|
||||
.inTable('entities')
|
||||
.onDelete('CASCADE')
|
||||
.notNullable()
|
||||
.comment('The entity that provided the relation');
|
||||
table
|
||||
.string('source_full_name')
|
||||
.notNullable()
|
||||
.comment('The full name of the source entity of the relation');
|
||||
table
|
||||
.string('type')
|
||||
.notNullable()
|
||||
.comment('The type of the relation between the entities');
|
||||
table
|
||||
.string('target_full_name')
|
||||
.notNullable()
|
||||
.comment('The full name of the target entity of the relation');
|
||||
|
||||
table.primary(['source_full_name', 'type', 'target_full_name']);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.dropTable('entities_relations');
|
||||
};
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
if (knex.client.config.client === 'sqlite3') {
|
||||
// sqlite doesn't support dropPrimary so we recreate it properly instead
|
||||
await knex.schema.dropTable('entities_relations');
|
||||
await knex.schema.createTable('entities_relations', table => {
|
||||
table.comment('All relations between entities in the catalog');
|
||||
table
|
||||
.uuid('originating_entity_id')
|
||||
.references('id')
|
||||
.inTable('entities')
|
||||
.onDelete('CASCADE')
|
||||
.notNullable()
|
||||
.comment('The entity that provided the relation');
|
||||
table
|
||||
.string('source_full_name')
|
||||
.notNullable()
|
||||
.comment('The full name of the source entity of the relation');
|
||||
table
|
||||
.string('type')
|
||||
.notNullable()
|
||||
.comment('The type of the relation between the entities');
|
||||
table
|
||||
.string('target_full_name')
|
||||
.notNullable()
|
||||
.comment('The full name of the target entity of the relation');
|
||||
table.index('source_full_name', 'source_full_name_idx');
|
||||
});
|
||||
} else {
|
||||
await knex.schema.alterTable('entities_relations', table => {
|
||||
table.dropPrimary();
|
||||
table.index('source_full_name', 'source_full_name_idx');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
if (knex.client.config.client === 'sqlite3') {
|
||||
await knex.schema.dropTable('entities_relations');
|
||||
await knex.schema.createTable('entities_relations', table => {
|
||||
table.comment('All relations between entities in the catalog');
|
||||
table
|
||||
.uuid('originating_entity_id')
|
||||
.references('id')
|
||||
.inTable('entities')
|
||||
.onDelete('CASCADE')
|
||||
.notNullable()
|
||||
.comment('The entity that provided the relation');
|
||||
table
|
||||
.string('source_full_name')
|
||||
.notNullable()
|
||||
.comment('The full name of the source entity of the relation');
|
||||
table
|
||||
.string('type')
|
||||
.notNullable()
|
||||
.comment('The type of the relation between the entities');
|
||||
table
|
||||
.string('target_full_name')
|
||||
.notNullable()
|
||||
.comment('The full name of the target entity of the relation');
|
||||
|
||||
table.primary(['source_full_name', 'type', 'target_full_name']);
|
||||
});
|
||||
} else {
|
||||
await knex.schema.alterTable('entities_relations', table => {
|
||||
table.dropIndex([], 'source_full_name_idx');
|
||||
table.primary(['source_full_name', 'type', 'target_full_name']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities_relations', table => {
|
||||
table.index('originating_entity_id', 'originating_entity_id_idx');
|
||||
});
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.index('entity_id', 'entity_id_idx');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities_relations', table => {
|
||||
table.dropIndex([], 'originating_entity_id_idx');
|
||||
});
|
||||
await knex.schema.alterTable('entities_relations', table => {
|
||||
table.dropIndex([], 'entity_id_idx');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
// We actually just want to widen columns, but can't do that while a
|
||||
// view is dependent on them - so we just reconstruct it exactly as it was
|
||||
await knex.schema
|
||||
.raw('DROP VIEW location_update_log_latest;')
|
||||
.alterTable('location_update_log', table => {
|
||||
table.text('message').alter();
|
||||
table.text('entity_name').nullable().alter();
|
||||
}).raw(`
|
||||
CREATE VIEW location_update_log_latest AS
|
||||
SELECT t1.* FROM location_update_log t1
|
||||
JOIN
|
||||
(
|
||||
SELECT location_id, MAX(id) AS MAXID
|
||||
FROM location_update_log
|
||||
GROUP BY location_id
|
||||
) t2
|
||||
ON t1.location_id = t2.location_id
|
||||
AND t1.id = t2.MAXID
|
||||
GROUP BY t1.location_id, t1.id
|
||||
ORDER BY created_at DESC;
|
||||
`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema
|
||||
.raw('DROP VIEW location_update_log_latest;')
|
||||
.alterTable('location_update_log', table => {
|
||||
table.string('message').alter();
|
||||
table.string('entity_name').nullable().alter();
|
||||
}).raw(`
|
||||
CREATE VIEW location_update_log_latest AS
|
||||
SELECT t1.* FROM location_update_log t1
|
||||
JOIN
|
||||
(
|
||||
SELECT location_id, MAX(id) AS MAXID
|
||||
FROM location_update_log
|
||||
GROUP BY location_id
|
||||
) t2
|
||||
ON t1.location_id = t2.location_id
|
||||
AND t1.id = t2.MAXID
|
||||
GROUP BY t1.location_id, t1.id
|
||||
ORDER BY created_at DESC;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.index('location_id', 'entity_location_id_idx');
|
||||
});
|
||||
await knex.schema.alterTable('location_update_log', table => {
|
||||
table.index('location_id', 'update_log_location_id_idx');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
if (knex.client.config.client !== 'sqlite3') {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.dropIndex([], 'entity_location_id_idx');
|
||||
});
|
||||
await knex.schema.alterTable('location_update_log', table => {
|
||||
table.dropIndex([], 'update_log_location_id_idx');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"version": "0.10.1",
|
||||
"version": "0.10.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,14 +30,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/msal-node": "^1.0.0-beta.3",
|
||||
"@backstage/backend-common": "^0.8.1",
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.1",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.5",
|
||||
"@backstage/plugin-search-backend-node": "^0.1.4",
|
||||
"@backstage/search-common": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/plugin-search-backend-node": "^0.2.0",
|
||||
"@backstage/search-common": "^0.1.2",
|
||||
"@microsoft/microsoft-graph-types": "^1.25.0",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -65,8 +65,8 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^0.1.1",
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/backend-test-utils": "^0.1.2",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@types/core-js": "^2.5.4",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
} from '@backstage/catalog-model';
|
||||
import { serializeError } from '@backstage/errors';
|
||||
import { Logger } from 'winston';
|
||||
import { ProcessingDatabase } from './database/types';
|
||||
import { ProcessingDatabase, RefreshStateItem } from './database/types';
|
||||
import { CatalogProcessingOrchestrator } from './processing/types';
|
||||
import { Stitcher } from './stitching/Stitcher';
|
||||
import { startTaskPipeline } from './TaskPipeline';
|
||||
import {
|
||||
CatalogProcessingEngine,
|
||||
EntityProvider,
|
||||
@@ -80,7 +81,7 @@ class Connection implements EntityProviderConnection {
|
||||
}
|
||||
|
||||
export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
|
||||
private running = false;
|
||||
private stopFunc?: () => void;
|
||||
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
@@ -99,106 +100,98 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
|
||||
}),
|
||||
);
|
||||
}
|
||||
this.running = true;
|
||||
this.run();
|
||||
}
|
||||
|
||||
private async run() {
|
||||
while (this.running) {
|
||||
try {
|
||||
// TODO: We want to disconnect the queue popping and message processing
|
||||
// so that if the queue popping fails we exponentially back off in order to give the DB room to sort itself out.
|
||||
await this.process();
|
||||
} catch (e) {
|
||||
this.logger.warn('Processing failed with:', e);
|
||||
// TODO: this can be a little smarter as mentioned in the above comment.
|
||||
// But for now, if something fails, wait a brief time to pick up the next message.
|
||||
await this.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async process() {
|
||||
const { items } = await this.processingDatabase.transaction(async tx => {
|
||||
return this.processingDatabase.getProcessableEntities(tx, {
|
||||
processBatchSize: 1,
|
||||
});
|
||||
});
|
||||
|
||||
if (!items.length) {
|
||||
// No items to process, wait and try again.
|
||||
await this.wait();
|
||||
return;
|
||||
if (this.stopFunc) {
|
||||
throw new Error('Processing engine is already started');
|
||||
}
|
||||
|
||||
// TODO: replace Promise.all with something more sophisticated for parallel processing.
|
||||
await Promise.all(
|
||||
items.map(async item => {
|
||||
const { id, state, unprocessedEntity, entityRef } = item;
|
||||
const result = await this.orchestrator.process({
|
||||
entity: unprocessedEntity,
|
||||
state,
|
||||
});
|
||||
|
||||
for (const error of result.errors) {
|
||||
// TODO(freben): Try to extract the location out of the unprocessed
|
||||
// entity and add as meta to the log lines
|
||||
this.logger.warn(error.message, {
|
||||
entity: entityRef,
|
||||
});
|
||||
this.stopFunc = startTaskPipeline<RefreshStateItem>({
|
||||
lowWatermark: 5,
|
||||
highWatermark: 10,
|
||||
loadTasks: async count => {
|
||||
try {
|
||||
const { items } = await this.processingDatabase.transaction(
|
||||
async tx => {
|
||||
return this.processingDatabase.getProcessableEntities(tx, {
|
||||
processBatchSize: count,
|
||||
});
|
||||
},
|
||||
);
|
||||
return items;
|
||||
} catch (error) {
|
||||
this.logger.warn('Failed to load processing items', error);
|
||||
return [];
|
||||
}
|
||||
const errorsString = JSON.stringify(
|
||||
result.errors.map(e => serializeError(e)),
|
||||
);
|
||||
},
|
||||
processTask: async item => {
|
||||
try {
|
||||
const { id, state, unprocessedEntity, entityRef } = item;
|
||||
const result = await this.orchestrator.process({
|
||||
entity: unprocessedEntity,
|
||||
state,
|
||||
});
|
||||
|
||||
// If the result was marked as not OK, it signals that some part of the
|
||||
// processing pipeline threw an exception. This can happen both as part of
|
||||
// non-catastrophic things such as due to validation errors, as well as if
|
||||
// something fatal happens inside the processing for other reasons. In any
|
||||
// case, this means we can't trust that anything in the output is okay. So
|
||||
// just store the errors and trigger a stich so that they become visible to
|
||||
// the outside.
|
||||
if (!result.ok) {
|
||||
for (const error of result.errors) {
|
||||
// TODO(freben): Try to extract the location out of the unprocessed
|
||||
// entity and add as meta to the log lines
|
||||
this.logger.warn(error.message, {
|
||||
entity: entityRef,
|
||||
});
|
||||
}
|
||||
const errorsString = JSON.stringify(
|
||||
result.errors.map(e => serializeError(e)),
|
||||
);
|
||||
|
||||
// If the result was marked as not OK, it signals that some part of the
|
||||
// processing pipeline threw an exception. This can happen both as part of
|
||||
// non-catastrophic things such as due to validation errors, as well as if
|
||||
// something fatal happens inside the processing for other reasons. In any
|
||||
// case, this means we can't trust that anything in the output is okay. So
|
||||
// just store the errors and trigger a stich so that they become visible to
|
||||
// the outside.
|
||||
if (!result.ok) {
|
||||
await this.processingDatabase.transaction(async tx => {
|
||||
await this.processingDatabase.updateProcessedEntityErrors(tx, {
|
||||
id,
|
||||
errors: errorsString,
|
||||
});
|
||||
});
|
||||
await this.stitcher.stitch(
|
||||
new Set([stringifyEntityRef(unprocessedEntity)]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
result.completedEntity.metadata.uid = id;
|
||||
await this.processingDatabase.transaction(async tx => {
|
||||
await this.processingDatabase.updateProcessedEntityErrors(tx, {
|
||||
await this.processingDatabase.updateProcessedEntity(tx, {
|
||||
id,
|
||||
processedEntity: result.completedEntity,
|
||||
state: result.state,
|
||||
errors: errorsString,
|
||||
relations: result.relations,
|
||||
deferredEntities: result.deferredEntities,
|
||||
});
|
||||
});
|
||||
await this.stitcher.stitch(
|
||||
new Set([stringifyEntityRef(unprocessedEntity)]),
|
||||
);
|
||||
return;
|
||||
|
||||
const setOfThingsToStitch = new Set<string>([
|
||||
stringifyEntityRef(result.completedEntity),
|
||||
...result.relations.map(relation =>
|
||||
stringifyEntityRef(relation.source),
|
||||
),
|
||||
]);
|
||||
await this.stitcher.stitch(setOfThingsToStitch);
|
||||
} catch (error) {
|
||||
this.logger.warn('Processing failed with:', error);
|
||||
}
|
||||
|
||||
result.completedEntity.metadata.uid = id;
|
||||
await this.processingDatabase.transaction(async tx => {
|
||||
await this.processingDatabase.updateProcessedEntity(tx, {
|
||||
id,
|
||||
processedEntity: result.completedEntity,
|
||||
state: result.state,
|
||||
errors: errorsString,
|
||||
relations: result.relations,
|
||||
deferredEntities: result.deferredEntities,
|
||||
});
|
||||
});
|
||||
|
||||
const setOfThingsToStitch = new Set<string>([
|
||||
stringifyEntityRef(result.completedEntity),
|
||||
...result.relations.map(relation =>
|
||||
stringifyEntityRef(relation.source),
|
||||
),
|
||||
]);
|
||||
await this.stitcher.stitch(setOfThingsToStitch);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async wait() {
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 1000));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this.running = false;
|
||||
if (this.stopFunc) {
|
||||
this.stopFunc();
|
||||
this.stopFunc = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ export class NextCatalogBuilder {
|
||||
private processors: CatalogProcessor[];
|
||||
private processorsReplace: boolean;
|
||||
private parser: CatalogProcessorParser | undefined;
|
||||
private refreshIntervalSeconds = 100;
|
||||
|
||||
constructor(env: CatalogEnvironment) {
|
||||
this.env = env;
|
||||
@@ -136,6 +137,16 @@ export class NextCatalogBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh interval determines how often entities should be refreshed.
|
||||
* The default refresh duration is 100, setting this too low will potentially
|
||||
* deplete request quotas to upstream services.
|
||||
*/
|
||||
setRefreshIntervalSeconds(seconds: number): NextCatalogBuilder {
|
||||
this.refreshIntervalSeconds = seconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets what policies to use for validation of entities between the pre-
|
||||
* processing and post-processing stages. All such policies must pass for the
|
||||
@@ -246,13 +257,17 @@ export class NextCatalogBuilder {
|
||||
await dbClient.migrate.latest({
|
||||
directory: resolvePackagePath(
|
||||
'@backstage/plugin-catalog-backend',
|
||||
'migrationsv2',
|
||||
'migrations',
|
||||
),
|
||||
});
|
||||
|
||||
const db = new CommonDatabase(dbClient, logger);
|
||||
|
||||
const processingDatabase = new DefaultProcessingDatabase(dbClient, logger);
|
||||
const processingDatabase = new DefaultProcessingDatabase({
|
||||
database: dbClient,
|
||||
logger,
|
||||
refreshIntervalSeconds: this.refreshIntervalSeconds,
|
||||
});
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const orchestrator = new DefaultCatalogProcessingOrchestrator({
|
||||
processors,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { startTaskPipeline } from './TaskPipeline';
|
||||
|
||||
function createLimitedLoader(count: number, loadDelay?: number) {
|
||||
const items = new Array(count).fill(0).map((_, index) => index);
|
||||
const loadCounts = new Array<number>();
|
||||
const processedTasks = new Array<number>();
|
||||
|
||||
let resolveDone: (_: {
|
||||
loadCounts: number[];
|
||||
processedTasks: number[];
|
||||
}) => void;
|
||||
const done = new Promise<{ loadCounts: number[]; processedTasks: number[] }>(
|
||||
resolve => {
|
||||
resolveDone = resolve;
|
||||
},
|
||||
);
|
||||
|
||||
const loadTasks = async (loadCount: number) => {
|
||||
if (loadCounts.length < (loadDelay || 0)) {
|
||||
loadCounts.push(0);
|
||||
return [];
|
||||
}
|
||||
const loadedItems = items.splice(0, loadCount);
|
||||
loadCounts.push(loadedItems.length);
|
||||
return loadedItems;
|
||||
};
|
||||
const processTask = async (item: number) => {
|
||||
processedTasks.push(item);
|
||||
await new Promise(resolve => setTimeout(resolve)); // emulate a bit of work
|
||||
if (processedTasks.length === count) {
|
||||
resolveDone({ processedTasks, loadCounts });
|
||||
}
|
||||
};
|
||||
|
||||
return { loadTasks, processTask, done };
|
||||
}
|
||||
|
||||
describe('startTaskPipeline', () => {
|
||||
it('should process some tasks', async () => {
|
||||
const { loadTasks, processTask, done } = createLimitedLoader(6);
|
||||
const stop = startTaskPipeline<number>({
|
||||
loadTasks,
|
||||
processTask,
|
||||
lowWatermark: 1,
|
||||
highWatermark: 3,
|
||||
});
|
||||
|
||||
const { loadCounts, processedTasks } = await done;
|
||||
stop();
|
||||
|
||||
expect(loadCounts).toEqual([3, 2, 1]);
|
||||
expect(processedTasks).toEqual([0, 1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('should pick up processing after it runs dry', async () => {
|
||||
const { loadTasks, processTask, done } = createLimitedLoader(5, 2);
|
||||
const stop = startTaskPipeline<number>({
|
||||
loadTasks,
|
||||
processTask,
|
||||
lowWatermark: 2,
|
||||
highWatermark: 3,
|
||||
pollingIntervalMs: 1,
|
||||
});
|
||||
|
||||
const { loadCounts, processedTasks } = await done;
|
||||
stop();
|
||||
|
||||
expect(loadCounts).toEqual([0, 0, 3, 1, 1]);
|
||||
expect(processedTasks).toEqual([0, 1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('should process in parallel', async () => {
|
||||
const { loadTasks, processTask, done } = createLimitedLoader(13);
|
||||
const stop1 = startTaskPipeline<number>({
|
||||
loadTasks,
|
||||
processTask,
|
||||
lowWatermark: 2,
|
||||
highWatermark: 4,
|
||||
});
|
||||
const stop2 = startTaskPipeline<number>({
|
||||
loadTasks,
|
||||
processTask,
|
||||
lowWatermark: 2,
|
||||
highWatermark: 4,
|
||||
});
|
||||
|
||||
const { loadCounts, processedTasks } = await done;
|
||||
stop1();
|
||||
stop2();
|
||||
|
||||
expect(loadCounts).toEqual([4, 4, 2, 2, 1]);
|
||||
expect(processedTasks).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
||||
});
|
||||
|
||||
it('should require lowWatermark to be lower than highWatermark', async () => {
|
||||
expect(() => {
|
||||
startTaskPipeline<number>({
|
||||
loadTasks: async () => [],
|
||||
processTask: async () => {},
|
||||
lowWatermark: 3,
|
||||
highWatermark: 3,
|
||||
});
|
||||
}).toThrow('must be lower');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const DEFAULT_POLLING_INTERVAL_MS = 1000;
|
||||
|
||||
type Options<T> = {
|
||||
/**
|
||||
* The callback used to load in new tasks. The number of items returned
|
||||
* in the array must be at most `count` number of items, but may be lower.
|
||||
*
|
||||
* Any error thrown from this method fill be treated as an unhandled rejection.
|
||||
*/
|
||||
loadTasks: (count: number) => Promise<Array<T>>;
|
||||
|
||||
/**
|
||||
* The callback used to process a single item.
|
||||
*
|
||||
* Any error thrown from this method fill be treated as an unhandled rejection.
|
||||
*/
|
||||
processTask: (item: T) => Promise<void>;
|
||||
|
||||
/**
|
||||
* The target minimum number of items to process in parallel. Once the number
|
||||
* of in-flight tasks reaches this count, more tasks will be loaded in.
|
||||
*/
|
||||
lowWatermark: number;
|
||||
|
||||
/**
|
||||
* The maximum number of items to process in parallel.
|
||||
*/
|
||||
highWatermark: number;
|
||||
|
||||
/**
|
||||
* The interval at which tasks are polled for in the background when
|
||||
* there aren't enough tasks to load to satisfy the low watermark.
|
||||
*
|
||||
* @default 1000
|
||||
*/
|
||||
pollingIntervalMs?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a task processing pipeline which continuously loads in tasks to
|
||||
* keep the number of parallel in-flight tasks between a low and high watermark.
|
||||
*
|
||||
* @param options The options for the pipeline.
|
||||
* @returns A stop function which when called halts all processing.
|
||||
*/
|
||||
export function startTaskPipeline<T>(options: Options<T>) {
|
||||
const {
|
||||
loadTasks,
|
||||
processTask,
|
||||
lowWatermark,
|
||||
highWatermark,
|
||||
pollingIntervalMs = DEFAULT_POLLING_INTERVAL_MS,
|
||||
} = options;
|
||||
|
||||
if (lowWatermark >= highWatermark) {
|
||||
throw new Error('lowWatermark must be lower than highWatermark');
|
||||
}
|
||||
|
||||
let loading = false;
|
||||
let stopped = false;
|
||||
let inFlightCount = 0;
|
||||
|
||||
async function maybeLoadMore() {
|
||||
if (stopped || loading || inFlightCount > lowWatermark) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Once we hit the low watermark we load in enough items to reach the high watermark
|
||||
loading = true;
|
||||
const loadCount = highWatermark - inFlightCount;
|
||||
const loadedItems = await loadTasks(loadCount);
|
||||
loading = false;
|
||||
|
||||
// We might not reach the high watermark here, in case there weren't enough items to load
|
||||
inFlightCount += loadedItems.length;
|
||||
loadedItems.forEach(item => {
|
||||
processTask(item).finally(() => {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For each item we complete we check if it's time to load more
|
||||
inFlightCount -= 1;
|
||||
maybeLoadMore();
|
||||
});
|
||||
});
|
||||
|
||||
// We might have processed some tasks while we where loading, so check if we can load more
|
||||
if (loadedItems.length > 1) {
|
||||
maybeLoadMore();
|
||||
}
|
||||
}
|
||||
|
||||
// This interval makes sure that we load in new items if the loop runs
|
||||
// dry because of the lack of available tasks. As long as there are
|
||||
// enough items to process this will be a noop.
|
||||
const intervalId = setInterval(() => {
|
||||
maybeLoadMore();
|
||||
}, pollingIntervalMs);
|
||||
|
||||
return () => {
|
||||
stopped = true;
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export class DatabaseManager {
|
||||
): Promise<Database> {
|
||||
const migrationsDir = resolvePackagePath(
|
||||
'@backstage/plugin-catalog-backend',
|
||||
'migrationsv2',
|
||||
'migrations',
|
||||
);
|
||||
|
||||
await knex.migrate.latest({
|
||||
|
||||
@@ -39,7 +39,11 @@ describe('Default Processing Database', () => {
|
||||
await DatabaseManager.createDatabase(knex);
|
||||
return {
|
||||
knex,
|
||||
db: new DefaultProcessingDatabase(knex, logger),
|
||||
db: new DefaultProcessingDatabase({
|
||||
database: knex,
|
||||
logger,
|
||||
refreshIntervalSeconds: 100,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,11 @@ const BATCH_SIZE = 50;
|
||||
|
||||
export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
constructor(
|
||||
private readonly database: Knex,
|
||||
private readonly logger: Logger,
|
||||
private readonly options: {
|
||||
database: Knex;
|
||||
logger: Logger;
|
||||
refreshIntervalSeconds: number;
|
||||
},
|
||||
) {}
|
||||
|
||||
async updateProcessedEntity(
|
||||
@@ -272,7 +275,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
.whereIn('target_entity_ref', toRemove)
|
||||
.delete();
|
||||
|
||||
this.logger.debug(
|
||||
this.options.logger.debug(
|
||||
`removed, ${removedCount} entities: ${JSON.stringify(toRemove)}`,
|
||||
);
|
||||
}
|
||||
@@ -355,8 +358,16 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
): Promise<GetProcessableEntitiesResult> {
|
||||
const tx = txOpaque as Knex.Transaction;
|
||||
|
||||
const items = await tx<DbRefreshStateRow>('refresh_state')
|
||||
.select()
|
||||
let itemsQuery = tx<DbRefreshStateRow>('refresh_state').select();
|
||||
|
||||
// This avoids duplication of work because of race conditions and is
|
||||
// also fast because locked rows are ignored rather than blocking.
|
||||
// It's only available in MySQL and PostgreSQL
|
||||
if (['mysql', 'mysql2', 'pg'].includes(tx.client.config.client)) {
|
||||
itemsQuery = itemsQuery.forUpdate().skipLocked();
|
||||
}
|
||||
|
||||
const items = await itemsQuery
|
||||
.where('next_update_at', '<=', tx.fn.now())
|
||||
.limit(request.processBatchSize)
|
||||
.orderBy('next_update_at', 'asc');
|
||||
@@ -369,8 +380,14 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
.update({
|
||||
next_update_at:
|
||||
tx.client.config.client === 'sqlite3'
|
||||
? tx.raw(`datetime('now', ?)`, [`100 seconds`])
|
||||
: tx.raw(`now() + interval '100 seconds'`),
|
||||
? tx.raw(`datetime('now', ?)`, [
|
||||
`${this.options.refreshIntervalSeconds} seconds`,
|
||||
])
|
||||
: tx.raw(
|
||||
`now() + interval '${Number(
|
||||
this.options.refreshIntervalSeconds,
|
||||
)} seconds'`,
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -398,7 +415,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
try {
|
||||
let result: T | undefined = undefined;
|
||||
|
||||
await this.database.transaction(
|
||||
await this.options.database.transaction(
|
||||
async tx => {
|
||||
// We can't return here, as knex swallows the return type in case the transaction is rolled back:
|
||||
// https://github.com/knex/knex/blob/e37aeaa31c8ef9c1b07d2e4d3ec6607e557d800d/lib/transaction.js#L136
|
||||
@@ -412,7 +429,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
|
||||
return result!;
|
||||
} catch (e) {
|
||||
this.logger.debug(`Error during transaction, ${e}`);
|
||||
this.options.logger.debug(`Error during transaction, ${e}`);
|
||||
|
||||
if (
|
||||
/SQLITE_CONSTRAINT: UNIQUE/.test(e.message) ||
|
||||
|
||||
@@ -66,6 +66,7 @@ import {
|
||||
} from '../ingestion/processors/PlaceholderProcessor';
|
||||
import { defaultEntityDataParser } from '../ingestion/processors/util/parse';
|
||||
import { LocationAnalyzer } from '../ingestion/types';
|
||||
import { NextCatalogBuilder } from '../next';
|
||||
|
||||
export type CatalogEnvironment = {
|
||||
logger: Logger;
|
||||
@@ -103,6 +104,10 @@ export class CatalogBuilder {
|
||||
private processorsReplace: boolean;
|
||||
private parser: CatalogProcessorParser | undefined;
|
||||
|
||||
static async create(env: CatalogEnvironment): Promise<NextCatalogBuilder> {
|
||||
return new NextCatalogBuilder(env);
|
||||
}
|
||||
|
||||
constructor(env: CatalogEnvironment) {
|
||||
this.env = env;
|
||||
this.entityPolicies = [];
|
||||
@@ -112,6 +117,10 @@ export class CatalogBuilder {
|
||||
this.processors = [];
|
||||
this.processorsReplace = false;
|
||||
this.parser = undefined;
|
||||
|
||||
env.logger.warn(
|
||||
"Creating the catalog with 'new CatalogBuilder(env)' is deprecated! Use CatalogBuilder.create(env) instead",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ import { Logger } from 'winston';
|
||||
import yn from 'yn';
|
||||
import { EntitiesCatalog, LocationsCatalog } from '../catalog';
|
||||
import { HigherOrderOperation, LocationAnalyzer } from '../ingestion/types';
|
||||
import { LocationService } from '../next/types';
|
||||
import {
|
||||
basicEntityFilter,
|
||||
parseEntityFilterParams,
|
||||
@@ -45,6 +46,7 @@ export interface RouterOptions {
|
||||
locationsCatalog?: LocationsCatalog;
|
||||
higherOrderOperation?: HigherOrderOperation;
|
||||
locationAnalyzer?: LocationAnalyzer;
|
||||
locationService?: LocationService;
|
||||
logger: Logger;
|
||||
config: Config;
|
||||
}
|
||||
@@ -57,6 +59,7 @@ export async function createRouter(
|
||||
locationsCatalog,
|
||||
higherOrderOperation,
|
||||
locationAnalyzer,
|
||||
locationService,
|
||||
config,
|
||||
logger,
|
||||
} = options;
|
||||
@@ -145,6 +148,40 @@ export async function createRouter(
|
||||
});
|
||||
}
|
||||
|
||||
if (locationService) {
|
||||
router
|
||||
.post('/locations', async (req, res) => {
|
||||
const input = await validateRequestBody(req, locationSpecSchema);
|
||||
const dryRun = yn(req.query.dryRun, { default: false });
|
||||
|
||||
// when in dryRun addLocation is effectively a read operation so we don't
|
||||
// need to disallow readonly
|
||||
if (!dryRun) {
|
||||
disallowReadonlyMode(readonlyEnabled);
|
||||
}
|
||||
|
||||
const output = await locationService.createLocation(input, dryRun);
|
||||
res.status(201).json(output);
|
||||
})
|
||||
.get('/locations', async (_req, res) => {
|
||||
const locations = await locationService.listLocations();
|
||||
res.status(200).json(locations.map(l => ({ data: l })));
|
||||
})
|
||||
|
||||
.get('/locations/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const output = await locationService.getLocation(id);
|
||||
res.status(200).json(output);
|
||||
})
|
||||
.delete('/locations/:id', async (req, res) => {
|
||||
disallowReadonlyMode(readonlyEnabled);
|
||||
|
||||
const { id } = req.params;
|
||||
await locationService.deleteLocation(id);
|
||||
res.status(204).end();
|
||||
});
|
||||
}
|
||||
|
||||
if (higherOrderOperation) {
|
||||
router.post('/locations', async (req, res) => {
|
||||
const input = await validateRequestBody(req, locationSpecSchema);
|
||||
|
||||
@@ -63,9 +63,12 @@ export async function startStandaloneServer(
|
||||
logger,
|
||||
config,
|
||||
});
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/catalog', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@graphql-modules/core": "^0.7.17",
|
||||
"apollo-server": "^2.16.1",
|
||||
@@ -42,7 +42,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.13",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/test-utils": "^0.1.11",
|
||||
"@graphql-codegen/cli": "^1.21.3",
|
||||
"@graphql-codegen/typescript": "^1.17.7",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/plugin-catalog-import
|
||||
|
||||
## 0.5.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 70bc30c5b: Display preview result final step.
|
||||
- Updated dependencies [27a9b503a]
|
||||
- Updated dependencies [f4e3ac5ce]
|
||||
- Updated dependencies [7028ee1ca]
|
||||
- Updated dependencies [70bc30c5b]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/catalog-model@0.8.2
|
||||
- @backstage/integration-react@0.1.3
|
||||
- @backstage/plugin-catalog-react@0.2.2
|
||||
- @backstage/catalog-client@0.3.13
|
||||
- @backstage/integration@0.5.6
|
||||
|
||||
## 0.5.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-import",
|
||||
"version": "0.5.8",
|
||||
"version": "0.5.9",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,12 +30,12 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/integration": "^0.5.4",
|
||||
"@backstage/integration-react": "^0.1.2",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/integration-react": "^0.1.3",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -53,7 +53,7 @@
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -146,11 +146,15 @@ export const EntityListComponent = ({
|
||||
<List component="div" disablePadding dense>
|
||||
{sortEntities(r.entities).map(entity => (
|
||||
<ListItem
|
||||
component={withLinks ? EntityRefLink : 'div'}
|
||||
entityRef={withLinks ? entity : undefined}
|
||||
button={withLinks as any}
|
||||
key={formatEntityRefTitle(entity)}
|
||||
className={classes.nested}
|
||||
{...(withLinks
|
||||
? {
|
||||
component: EntityRefLink,
|
||||
entityRef: entity,
|
||||
button: withLinks as any,
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<ListItemIcon>{getEntityIcon(entity)}</ListItemIcon>
|
||||
<ListItemText primary={formatEntityRefTitle(entity)} />
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/plugin-catalog-react
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7028ee1ca: Expose `getEntitySourceLocation`, `getEntityMetadataViewUrl`, and
|
||||
`getEntityMetadataEditUrl` from `@backstage/plugin-catalog-react`.
|
||||
- Updated dependencies [75b8537ce]
|
||||
- Updated dependencies [27a9b503a]
|
||||
- Updated dependencies [da8cba44f]
|
||||
- Updated dependencies [70bc30c5b]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/core-plugin-api@0.1.2
|
||||
- @backstage/catalog-model@0.8.2
|
||||
- @backstage/catalog-client@0.3.13
|
||||
- @backstage/integration@0.5.6
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-react",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -28,11 +28,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.1",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/core-plugin-api": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.0",
|
||||
"@backstage/core-plugin-api": "^0.1.2",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -44,7 +44,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @backstage/plugin-catalog
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- db1c8f93b: A `<CatalogResultListItem />` component is now available for use in custom Search Experiences.
|
||||
- f4e3ac5ce: Move `ScmIntegrationIcon` from `@backstage/plugin-catalog` to
|
||||
`@backstage/integration-react` and make it customizable using
|
||||
`app.getSystemIcon()`.
|
||||
- 7028ee1ca: Expose `getEntitySourceLocation`, `getEntityMetadataViewUrl`, and
|
||||
`getEntityMetadataEditUrl` from `@backstage/plugin-catalog-react`.
|
||||
- 4fbb00707: A new card that shows components that depend on the active component
|
||||
- d5ad47bbb: Exported AboutCard contents and utility functions
|
||||
- Updated dependencies [27a9b503a]
|
||||
- Updated dependencies [f4e3ac5ce]
|
||||
- Updated dependencies [7028ee1ca]
|
||||
- Updated dependencies [70bc30c5b]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/catalog-model@0.8.2
|
||||
- @backstage/integration-react@0.1.3
|
||||
- @backstage/plugin-catalog-react@0.2.2
|
||||
- @backstage/catalog-client@0.3.13
|
||||
- @backstage/integration@0.5.6
|
||||
|
||||
## 0.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,13 +30,13 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.1",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.5",
|
||||
"@backstage/integration-react": "^0.1.2",
|
||||
"@backstage/plugin-catalog-react": "^0.2.1",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/integration-react": "^0.1.3",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -53,7 +53,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -21,7 +21,7 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const CatalogLayout = ({ children }: Props) => {
|
||||
export const CatalogLayout = ({ children }: Props) => {
|
||||
const orgName =
|
||||
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
|
||||
|
||||
|
||||
@@ -13,4 +13,5 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { CatalogLayout } from './CatalogLayout';
|
||||
export { CatalogPage } from './CatalogPage';
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
|
||||
export * from './components/AboutCard';
|
||||
export { CatalogLayout } from './components/CatalogPage';
|
||||
export { CatalogResultListItem } from './components/CatalogResultListItem';
|
||||
export { CatalogTable } from './components/CatalogTable';
|
||||
export { CreateComponentButton } from './components/CreateComponentButton';
|
||||
export { EntityLayout } from './components/EntityLayout';
|
||||
export * from './components/EntityOrphanWarning';
|
||||
export { EntityPageLayout } from './components/EntityPageLayout';
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
"postpack": "backstage-cli postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -50,7 +50,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.4",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@types/express": "^4.17.6",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"express": "^4.17.1",
|
||||
@@ -36,7 +36,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.13",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/express-xml-bodyparser": "^0.3.2",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^0.21.2",
|
||||
|
||||
@@ -61,9 +61,12 @@ export async function startStandaloneServer(
|
||||
logger,
|
||||
});
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/code-coverage', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -39,7 +39,7 @@
|
||||
"recharts": "^1.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-cost-insights
|
||||
|
||||
## 0.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d906c7a1: Move `canvas` package to `devDependencies`.
|
||||
|
||||
## 0.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-cost-insights",
|
||||
"version": "0.10.1",
|
||||
"version": "0.10.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -54,7 +54,7 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@backstage/core": "^0.7.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.14",
|
||||
"@backstage/test-utils": "^0.1.11",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
"start": "backstage-cli plugin:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/plugin-explore-react": "^0.0.5",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -45,7 +45,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -47,7 +47,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/integration": "^0.5.3",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"recharts": "^1.8.5",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -36,7 +36,7 @@
|
||||
"react": "^16.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @backstage/plugin-github-actions
|
||||
|
||||
## 0.4.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9bdd2cca8: Add missing token on job list call to GitHub API
|
||||
- Updated dependencies [27a9b503a]
|
||||
- Updated dependencies [7028ee1ca]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/catalog-model@0.8.2
|
||||
- @backstage/plugin-catalog-react@0.2.2
|
||||
- @backstage/integration@0.5.6
|
||||
|
||||
## 0.4.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-github-actions",
|
||||
"version": "0.4.8",
|
||||
"version": "0.4.9",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,10 +32,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/integration": "^0.5.4",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -50,7 +50,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @backstage/plugin-github-deployments
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ef2f928f4: Add deployment statuses to the GithubDeployments plugin
|
||||
- Updated dependencies [27a9b503a]
|
||||
- Updated dependencies [f4e3ac5ce]
|
||||
- Updated dependencies [7028ee1ca]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/catalog-model@0.8.2
|
||||
- @backstage/integration-react@0.1.3
|
||||
- @backstage/plugin-catalog-react@0.2.2
|
||||
- @backstage/integration@0.5.6
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-github-deployments",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,12 +20,12 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.4",
|
||||
"@backstage/integration-react": "^0.1.2",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/integration-react": "^0.1.3",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -37,7 +37,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/core-plugin-api": "^0.1.0",
|
||||
"@backstage/core-plugin-api": "^0.1.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -44,7 +44,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/plugin-catalog-graphql": "^0.2.9",
|
||||
"@graphql-modules/core": "^0.7.17",
|
||||
@@ -45,7 +45,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"eslint-plugin-graphql": "^4.0.0",
|
||||
"msw": "^0.20.5",
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@date-io/luxon": "1.x",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -37,7 +37,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -47,7 +47,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
@@ -42,7 +42,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.13",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/jest-when": "^2.7.2",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"jest-when": "^3.1.0",
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -33,7 +33,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/plugin-kubernetes-common": "^0.1.1",
|
||||
"@google-cloud/container": "^2.2.0",
|
||||
@@ -53,7 +53,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.13",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/aws4": "^1.5.1",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
"url": "https://github.com/backstage/backstage/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@kubernetes/client-node": "^0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.13"
|
||||
"@backstage/cli": "^0.7.0"
|
||||
},
|
||||
"jest": {
|
||||
"roots": [
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/plugin-kubernetes-common": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@kubernetes/client-node": "^0.14.0",
|
||||
@@ -49,7 +49,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
"start": "backstage-cli plugin:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -46,7 +46,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/core-api": "^0.2.20",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/core-api": "^0.2.22",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -35,7 +35,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -46,7 +46,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-proxy-backend
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 875809a59: Fixed proxy requests to the base URL of routes without a trailing slash redirecting to the `target` with the full path appended.
|
||||
- Updated dependencies [92963779b]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/backend-common@0.8.2
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-proxy-backend",
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -28,12 +28,12 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"http-proxy-middleware": "^0.19.1",
|
||||
"http-proxy-middleware": "^2.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"uuid": "^8.0.0",
|
||||
"winston": "^3.2.1",
|
||||
@@ -42,7 +42,7 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/http-proxy-middleware": "^0.19.3",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@types/uuid": "^8.0.0",
|
||||
|
||||
@@ -14,25 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { buildMiddleware, createRouter } from './router';
|
||||
import {
|
||||
getVoidLogger,
|
||||
loadBackendConfig,
|
||||
SingleHostDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import createProxyMiddleware, {
|
||||
Config as ProxyMiddlewareConfig,
|
||||
Proxy,
|
||||
} from 'http-proxy-middleware';
|
||||
import { Request, Response } from 'express';
|
||||
import * as http from 'http';
|
||||
import { createProxyMiddleware, Options } from 'http-proxy-middleware';
|
||||
import { buildMiddleware, createRouter } from './router';
|
||||
|
||||
jest.mock('http-proxy-middleware', () => {
|
||||
return jest.fn().mockImplementation(
|
||||
(): Proxy => {
|
||||
return () => undefined;
|
||||
},
|
||||
);
|
||||
});
|
||||
jest.mock('http-proxy-middleware', () => ({
|
||||
createProxyMiddleware: jest.fn(() => () => undefined),
|
||||
}));
|
||||
|
||||
const mockCreateProxyMiddleware = createProxyMiddleware as jest.MockedFunction<
|
||||
typeof createProxyMiddleware
|
||||
@@ -66,7 +60,7 @@ describe('buildMiddleware', () => {
|
||||
|
||||
const [filter, fullConfig] = mockCreateProxyMiddleware.mock.calls[0] as [
|
||||
(pathname: string, req: Partial<http.IncomingMessage>) => boolean,
|
||||
ProxyMiddlewareConfig,
|
||||
Options,
|
||||
];
|
||||
expect(filter('', { method: 'GET', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'POST', headers: {} })).toBe(true);
|
||||
@@ -86,7 +80,7 @@ describe('buildMiddleware', () => {
|
||||
|
||||
const [filter, fullConfig] = mockCreateProxyMiddleware.mock.calls[0] as [
|
||||
(pathname: string, req: Partial<http.IncomingMessage>) => boolean,
|
||||
ProxyMiddlewareConfig,
|
||||
Options,
|
||||
];
|
||||
expect(filter('', { method: 'GET', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'POST', headers: {} })).toBe(true);
|
||||
@@ -106,7 +100,7 @@ describe('buildMiddleware', () => {
|
||||
|
||||
const [filter, fullConfig] = mockCreateProxyMiddleware.mock.calls[0] as [
|
||||
(pathname: string, req: Partial<http.IncomingMessage>) => boolean,
|
||||
ProxyMiddlewareConfig,
|
||||
Options,
|
||||
];
|
||||
expect(filter('', { method: 'GET', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'POST', headers: {} })).toBe(true);
|
||||
@@ -129,7 +123,7 @@ describe('buildMiddleware', () => {
|
||||
|
||||
const [filter, fullConfig] = mockCreateProxyMiddleware.mock.calls[0] as [
|
||||
(pathname: string, req: Partial<http.IncomingMessage>) => boolean,
|
||||
ProxyMiddlewareConfig,
|
||||
Options,
|
||||
];
|
||||
expect(filter('', { method: 'GET', headers: {} })).toBe(true);
|
||||
expect(filter('', { method: 'POST', headers: {} })).toBe(false);
|
||||
@@ -254,8 +248,7 @@ describe('buildMiddleware', () => {
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
const config = mockCreateProxyMiddleware.mock
|
||||
.calls[0][1] as ProxyMiddlewareConfig;
|
||||
const config = mockCreateProxyMiddleware.mock.calls[0][1] as Options;
|
||||
|
||||
const testClientResponse = {
|
||||
headers: {
|
||||
@@ -275,8 +268,8 @@ describe('buildMiddleware', () => {
|
||||
|
||||
config.onProxyRes!(
|
||||
testClientResponse as http.IncomingMessage,
|
||||
{} as http.IncomingMessage,
|
||||
{} as http.ServerResponse,
|
||||
{} as Request,
|
||||
{} as Response,
|
||||
);
|
||||
|
||||
expect(Object.keys(testClientResponse.headers!)).toEqual([
|
||||
@@ -298,8 +291,7 @@ describe('buildMiddleware', () => {
|
||||
|
||||
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
|
||||
|
||||
const config = mockCreateProxyMiddleware.mock
|
||||
.calls[0][1] as ProxyMiddlewareConfig;
|
||||
const config = mockCreateProxyMiddleware.mock.calls[0][1] as Options;
|
||||
|
||||
const testClientResponse = {
|
||||
headers: {
|
||||
@@ -313,8 +305,8 @@ describe('buildMiddleware', () => {
|
||||
|
||||
config.onProxyRes!(
|
||||
testClientResponse as http.IncomingMessage,
|
||||
{} as http.IncomingMessage,
|
||||
{} as http.ServerResponse,
|
||||
{} as Request,
|
||||
{} as Response,
|
||||
);
|
||||
|
||||
expect(Object.keys(testClientResponse.headers!)).toEqual(['set-cookie']);
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import createProxyMiddleware, {
|
||||
Config as ProxyMiddlewareConfig,
|
||||
Proxy,
|
||||
import {
|
||||
createProxyMiddleware,
|
||||
Options,
|
||||
RequestHandler,
|
||||
} from 'http-proxy-middleware';
|
||||
import { Logger } from 'winston';
|
||||
import http from 'http';
|
||||
@@ -52,7 +53,7 @@ export interface RouterOptions {
|
||||
discovery: PluginEndpointDiscovery;
|
||||
}
|
||||
|
||||
export interface ProxyConfig extends ProxyMiddlewareConfig {
|
||||
export interface ProxyConfig extends Options {
|
||||
allowedMethods?: string[];
|
||||
allowedHeaders?: string[];
|
||||
}
|
||||
@@ -64,14 +65,17 @@ export function buildMiddleware(
|
||||
logger: Logger,
|
||||
route: string,
|
||||
config: string | ProxyConfig,
|
||||
): Proxy {
|
||||
): RequestHandler {
|
||||
const fullConfig =
|
||||
typeof config === 'string' ? { target: config } : { ...config };
|
||||
|
||||
// Validate that target is a valid URL.
|
||||
if (typeof fullConfig.target !== 'string') {
|
||||
throw new Error(`Proxy target must be a string`);
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(fullConfig.target!);
|
||||
new URL(fullConfig.target! as string);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Proxy target is not a valid URL: ${fullConfig.target ?? ''}`,
|
||||
@@ -131,7 +135,7 @@ export function buildMiddleware(
|
||||
// 2. Only permit the allowed HTTP methods if configured
|
||||
//
|
||||
// We are filtering the proxy request headers here rather than in
|
||||
// `onProxyReq` becuase when global-agent is enabled then `onProxyReq`
|
||||
// `onProxyReq` because when global-agent is enabled then `onProxyReq`
|
||||
// fires _after_ the agent has already sent the headers to the proxy
|
||||
// target, causing a ERR_HTTP_HEADERS_SENT crash
|
||||
const filter = (_pathname: string, req: http.IncomingMessage): boolean => {
|
||||
|
||||
@@ -43,9 +43,12 @@ export async function startStandaloneServer(
|
||||
logger,
|
||||
discovery,
|
||||
});
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/proxy', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
logger.debug('Starting application server...');
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -45,7 +45,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@types/express": "^4.17.6",
|
||||
"axios": "^0.21.1",
|
||||
@@ -47,7 +47,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
|
||||
@@ -38,9 +38,12 @@ export async function startStandaloneServer(
|
||||
|
||||
const router = await createRouter({ logger, config });
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/catalog', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.0",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -47,7 +47,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,55 @@
|
||||
# @backstage/plugin-scaffolder-backend
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 66c6bfebd: Scaffolding a repository in Bitbucket will now use the apiBaseUrl if it is provided instead of only the host parameter
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 27a9b503a: Introduce conditional steps in scaffolder templates.
|
||||
|
||||
A step can now include an `if` property that only executes a step if the
|
||||
condition is truthy. The condition can include handlebar templates.
|
||||
|
||||
```yaml
|
||||
- id: register
|
||||
if: '{{ not parameters.dryRun }}'
|
||||
name: Register
|
||||
action: catalog:register
|
||||
input:
|
||||
repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}'
|
||||
catalogInfoPath: '/catalog-info.yaml'
|
||||
```
|
||||
|
||||
Also introduces a `not` helper in handlebar templates that allows to negate
|
||||
boolean expressions.
|
||||
|
||||
- 55a253de2: Migrating old `backstage.io/v1alpha1` templates to `backstage.io/v1beta2`
|
||||
|
||||
Deprecating the `create-react-app` Template. We're planning on removing the `create-react-app` templater, as it's been a little tricky to support and takes 15mins to run in a container. We've currently cached a copy of the output for `create-react-app` and ship that under our sample templates folder. If you want to continue using it, we suggest copying the template out of there and putting it in your own repository as it will be removed in upcoming releases.
|
||||
|
||||
We also recommend removing this entry from your `app-config.yaml` if it exists:
|
||||
|
||||
```diff
|
||||
- - type: url
|
||||
- target: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml
|
||||
- rules:
|
||||
- - allow: [Template]
|
||||
```
|
||||
|
||||
- f26e6008f: Add `debug:log` action for debugging.
|
||||
- 4f8cf50fe: Update gitbeaker past the broken version without a dist folder
|
||||
- Updated dependencies [92963779b]
|
||||
- Updated dependencies [27a9b503a]
|
||||
- Updated dependencies [70bc30c5b]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/backend-common@0.8.2
|
||||
- @backstage/catalog-model@0.8.2
|
||||
- @backstage/catalog-client@0.3.13
|
||||
- @backstage/integration@0.5.6
|
||||
|
||||
## 0.11.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder-backend",
|
||||
"version": "0.11.5",
|
||||
"version": "0.12.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -29,19 +29,19 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.1",
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.1",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.5",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@gitbeaker/core": "^30.2.0",
|
||||
"@gitbeaker/node": "^30.2.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"azure-devops-node-api": "^10.1.1",
|
||||
"command-exists-promise": "^2.0.2",
|
||||
"command-exists": "^1.2.9",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"cross-fetch": "^3.0.6",
|
||||
@@ -64,8 +64,9 @@
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@types/command-exists": "^1.2.0",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
|
||||
+83
-1
@@ -140,7 +140,7 @@ describe('publish:bitbucket', () => {
|
||||
'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('Authorization')).toBe('Bearer thing');
|
||||
expect(req.body).toEqual({ is_private: true, name: 'repo' });
|
||||
expect(req.body).toEqual({ public: false, name: 'repo' });
|
||||
return res(
|
||||
ctx.status(201),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
@@ -174,6 +174,88 @@ describe('publish:bitbucket', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('LFS for hosted bitbucket', () => {
|
||||
const repoCreationResponse = {
|
||||
links: {
|
||||
self: [
|
||||
{
|
||||
href: 'https://bitbucket.mycompany.com/projects/project/repos/repo',
|
||||
},
|
||||
],
|
||||
clone: [
|
||||
{
|
||||
name: 'http',
|
||||
href: 'https://bitbucket.mycompany.com/scm/project/repo',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
it('should call the correct APIs to enable LFS if requested and the host is hosted bitbucket', async () => {
|
||||
expect.assertions(1);
|
||||
server.use(
|
||||
rest.post(
|
||||
'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos',
|
||||
(_, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(201),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(repoCreationResponse),
|
||||
);
|
||||
},
|
||||
),
|
||||
rest.put(
|
||||
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/owner/repos/repo/enabled',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('Authorization')).toBe('Bearer thing');
|
||||
return res(ctx.status(204));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo',
|
||||
enableLFS: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should report an error if enabling LFS fails', async () => {
|
||||
server.use(
|
||||
rest.post(
|
||||
'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos',
|
||||
(_, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(201),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(repoCreationResponse),
|
||||
);
|
||||
},
|
||||
),
|
||||
rest.put(
|
||||
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/owner/repos/repo/enabled',
|
||||
(_, res, ctx) => {
|
||||
return res(ctx.status(500));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo',
|
||||
enableLFS: true,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow(/Failed to enable LFS/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call initAndPush with the correct values', async () => {
|
||||
server.use(
|
||||
rest.post(
|
||||
|
||||
@@ -19,10 +19,10 @@ import {
|
||||
BitbucketIntegrationConfig,
|
||||
ScmIntegrationRegistry,
|
||||
} from '@backstage/integration';
|
||||
import { initRepoAndPush } from '../../../stages/publish/helpers';
|
||||
import { getRepoSourceDirectory, parseRepoUrl } from './util';
|
||||
import fetch from 'cross-fetch';
|
||||
import { initRepoAndPush } from '../../../stages/publish/helpers';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { getRepoSourceDirectory, parseRepoUrl } from './util';
|
||||
|
||||
const createBitbucketCloudRepository = async (opts: {
|
||||
owner: string;
|
||||
@@ -102,7 +102,7 @@ const createBitbucketServerRepository = async (opts: {
|
||||
body: JSON.stringify({
|
||||
name: repo,
|
||||
description: description,
|
||||
is_private: repoVisibility === 'private',
|
||||
public: repoVisibility === 'public',
|
||||
}),
|
||||
headers: {
|
||||
Authorization: authorization,
|
||||
@@ -156,6 +156,32 @@ const getAuthorizationHeader = (config: BitbucketIntegrationConfig) => {
|
||||
);
|
||||
};
|
||||
|
||||
const performEnableLFS = async (opts: {
|
||||
authorization: string;
|
||||
host: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
}) => {
|
||||
const { authorization, host, owner, repo } = opts;
|
||||
|
||||
const options: RequestInit = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
Authorization: authorization,
|
||||
},
|
||||
};
|
||||
|
||||
const { ok, status, statusText } = await fetch(
|
||||
`https://${host}/rest/git-lfs/admin/projects/${owner}/repos/${repo}/enabled`,
|
||||
options,
|
||||
);
|
||||
|
||||
if (!ok)
|
||||
throw new Error(
|
||||
`Failed to enable LFS in the repository, ${status}: ${statusText}`,
|
||||
);
|
||||
};
|
||||
|
||||
export function createPublishBitbucketAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) {
|
||||
@@ -166,6 +192,7 @@ export function createPublishBitbucketAction(options: {
|
||||
description: string;
|
||||
repoVisibility: 'private' | 'public';
|
||||
sourcePath?: string;
|
||||
enableLFS: boolean;
|
||||
}>({
|
||||
id: 'publish:bitbucket',
|
||||
description:
|
||||
@@ -193,6 +220,11 @@ export function createPublishBitbucketAction(options: {
|
||||
'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.',
|
||||
type: 'string',
|
||||
},
|
||||
enableLFS: {
|
||||
title:
|
||||
'Enable LFS for the repository. Only available for hosted Bitbucket.',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
@@ -210,7 +242,12 @@ export function createPublishBitbucketAction(options: {
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const { repoUrl, description, repoVisibility = 'private' } = ctx.input;
|
||||
const {
|
||||
repoUrl,
|
||||
description,
|
||||
repoVisibility = 'private',
|
||||
enableLFS = false,
|
||||
} = ctx.input;
|
||||
|
||||
const { owner, repo, host } = parseRepoUrl(repoUrl);
|
||||
|
||||
@@ -254,6 +291,10 @@ export function createPublishBitbucketAction(options: {
|
||||
logger: ctx.logger,
|
||||
});
|
||||
|
||||
if (enableLFS && host !== 'bitbucket.org') {
|
||||
await performEnableLFS({ authorization, host, owner, repo });
|
||||
}
|
||||
|
||||
ctx.output('remoteUrl', remoteUrl);
|
||||
ctx.output('repoContentsUrl', repoContentsUrl);
|
||||
},
|
||||
|
||||
@@ -174,7 +174,7 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
description: description,
|
||||
is_private: this.config.repoVisibility === 'private',
|
||||
public: this.config.repoVisibility === 'public',
|
||||
}),
|
||||
headers: {
|
||||
Authorization: this.getAuthorizationHeader(),
|
||||
|
||||
@@ -18,7 +18,7 @@ const runCommand = jest.fn();
|
||||
const commandExists = jest.fn();
|
||||
|
||||
jest.mock('./helpers', () => ({ runCommand }));
|
||||
jest.mock('command-exists-promise', () => commandExists);
|
||||
jest.mock('command-exists', () => commandExists);
|
||||
jest.mock('fs-extra');
|
||||
|
||||
import { ContainerRunner } from '@backstage/backend-common';
|
||||
|
||||
@@ -16,13 +16,12 @@
|
||||
|
||||
import { ContainerRunner } from '@backstage/backend-common';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import commandExists from 'command-exists';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { runCommand } from './helpers';
|
||||
import { TemplaterBase, TemplaterRunOptions } from './types';
|
||||
|
||||
const commandExists = require('command-exists-promise');
|
||||
|
||||
export class CookieCutter implements TemplaterBase {
|
||||
private readonly containerRunner: ContainerRunner;
|
||||
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @backstage/plugin-scaffolder
|
||||
|
||||
## 0.9.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 27a9b503a: Introduce conditional steps in scaffolder templates.
|
||||
|
||||
A step can now include an `if` property that only executes a step if the
|
||||
condition is truthy. The condition can include handlebar templates.
|
||||
|
||||
```yaml
|
||||
- id: register
|
||||
if: '{{ not parameters.dryRun }}'
|
||||
name: Register
|
||||
action: catalog:register
|
||||
input:
|
||||
repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}'
|
||||
catalogInfoPath: '/catalog-info.yaml'
|
||||
```
|
||||
|
||||
Also introduces a `not` helper in handlebar templates that allows to negate
|
||||
boolean expressions.
|
||||
|
||||
- 9b4010965: Provide a link to the template source on the `TemplateCard`.
|
||||
- Updated dependencies [27a9b503a]
|
||||
- Updated dependencies [f4e3ac5ce]
|
||||
- Updated dependencies [7028ee1ca]
|
||||
- Updated dependencies [70bc30c5b]
|
||||
- Updated dependencies [eda9dbd5f]
|
||||
- @backstage/catalog-model@0.8.2
|
||||
- @backstage/integration-react@0.1.3
|
||||
- @backstage/plugin-catalog-react@0.2.2
|
||||
- @backstage/catalog-client@0.3.13
|
||||
- @backstage/integration@0.5.6
|
||||
|
||||
## 0.9.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,14 +30,14 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.12",
|
||||
"@backstage/catalog-model": "^0.8.1",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/integration": "^0.5.5",
|
||||
"@backstage/integration-react": "^0.1.2",
|
||||
"@backstage/plugin-catalog-react": "^0.2.1",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/integration-react": "^0.1.3",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -61,7 +61,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.14",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,90 @@
|
||||
# @backstage/plugin-search-backend-node
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5aff84759: This release represents a move out of a pre-alpha phase of the Backstage Search
|
||||
plugin, into an alpha phase. With this release, you gain more control over the
|
||||
layout of your search page on the frontend, as well as the ability to extend
|
||||
search on the backend to encompass everything Backstage users may want to find.
|
||||
|
||||
If you are updating to version `v0.4.0` of `@backstage/plugin-search` from a
|
||||
prior release, you will need to make modifications to your app backend.
|
||||
|
||||
First, navigate to your backend package and install the two related search
|
||||
backend packages:
|
||||
|
||||
```sh
|
||||
cd packages/backend
|
||||
yarn add @backstage/plugin-search-backend @backstage/plugin-search-backend-node
|
||||
```
|
||||
|
||||
Wire up these new packages into your app backend by first creating a new
|
||||
`search.ts` file at `src/plugins/search.ts` with contents like the following:
|
||||
|
||||
```typescript
|
||||
import { useHotCleanup } from '@backstage/backend-common';
|
||||
import { createRouter } from '@backstage/plugin-search-backend';
|
||||
import {
|
||||
IndexBuilder,
|
||||
LunrSearchEngine,
|
||||
} from '@backstage/plugin-search-backend-node';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
discovery,
|
||||
}: PluginEnvironment) {
|
||||
// Initialize a connection to a search engine.
|
||||
const searchEngine = new LunrSearchEngine({ logger });
|
||||
const indexBuilder = new IndexBuilder({ logger, searchEngine });
|
||||
|
||||
// Collators are responsible for gathering documents known to plugins. This
|
||||
// particular collator gathers entities from the software catalog.
|
||||
indexBuilder.addCollator({
|
||||
defaultRefreshIntervalSeconds: 600,
|
||||
collator: new DefaultCatalogCollator({ discovery }),
|
||||
});
|
||||
|
||||
// The scheduler controls when documents are gathered from collators and sent
|
||||
// to the search engine for indexing.
|
||||
const { scheduler } = await indexBuilder.build();
|
||||
|
||||
// A 3 second delay gives the backend server a chance to initialize before
|
||||
// any collators are executed, which may attempt requests against the API.
|
||||
setTimeout(() => scheduler.start(), 3000);
|
||||
useHotCleanup(module, () => scheduler.stop());
|
||||
|
||||
return await createRouter({
|
||||
engine: indexBuilder.getSearchEngine(),
|
||||
logger,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Then, ensure the search plugin you configured above is initialized by modifying
|
||||
your backend's `index.ts` file in the following ways:
|
||||
|
||||
```diff
|
||||
+import search from './plugins/search';
|
||||
// ...
|
||||
+const searchEnv = useHotMemoize(module, () => createEnv('search'));
|
||||
// ...
|
||||
+apiRouter.use('/search', await search(searchEnv));
|
||||
// ...
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- db1c8f93b: The `<Search...Next /> set of components exported by the Search Plugin are now updated to use the Search Backend API. These will be made available as the default non-"next" versions in a follow-up release.
|
||||
|
||||
The interfaces for decorators and collators in the Search Backend have also seen minor, breaking revisions ahead of a general release. If you happen to be building on top of these interfaces, check and update your implementations accordingly. The APIs will be considered more stable in a follow-up release.
|
||||
|
||||
- Updated dependencies [db1c8f93b]
|
||||
- @backstage/search-common@0.1.2
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user