From 8c02d89dbec8a86af1d3dfffaa79524747a21a66 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 31 Jan 2023 14:12:44 +0000 Subject: [PATCH 1/7] add incremental entity provider post to microsite Signed-off-by: Paul Cowan --- .../2023-01-31-incremental-entity-provider.md | 44 ++++++++++++++++++ microsite/blog/assets/catalog-pipeline.png | Bin 0 -> 40892 bytes 2 files changed, 44 insertions(+) create mode 100644 microsite/blog/2023-01-31-incremental-entity-provider.md create mode 100644 microsite/blog/assets/catalog-pipeline.png diff --git a/microsite/blog/2023-01-31-incremental-entity-provider.md b/microsite/blog/2023-01-31-incremental-entity-provider.md new file mode 100644 index 0000000000..8ea74c01b2 --- /dev/null +++ b/microsite/blog/2023-01-31-incremental-entity-provider.md @@ -0,0 +1,44 @@ +--- +title: Scaling Backstage Ingestion with Incremental Entity Providers +author: Paul Cowan & Taras Mankovski +authorURL: https://frontside.com/ +--- +# Scaling Backstage Ingestion with Incremental Entity Providers + +At the heart of [Backstage](backstage.io) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. + +![catalog pipeline](./assets/catalog-pipeline.png) + +A common use case is for an organization to want to surface ownership and metadata about repositories. Backstage provides a mechanism for discovering and transforming repository information into a standard data structure and persisting it into the Backstage [Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview). This process is known as ingestion, where all data is transformed into a standard Backstage data structure known as an entity. Entities in the Catalog’s data store are accessible to the Backstage App via the REST API. + +Data is transformed into entities via what is known as the ingestion and processing loop, which can be thought of as an [extract, transform and load (ETL) pipeline](https://en.wikipedia.org/wiki/Extract,_transform,_load), where raw data such as GitHub repositories are loaded from GitHub, transformed into entities and outputted to the Catalog. + +## Entity Providers + +Backstage offers what are known as [entity providers](https://backstage.io/docs/features/software-catalog/life-of-an-entity) as a means for ingesting the raw data into the pipeline and transforming them into Backstage entities. For example, Backstage comes with a [GitHub Entity Provider](https://backstage.io/docs/reference/plugin-catalog-backend-module-github) that finds all catalog-info.yaml files in GitHub repositories. The processing loop transforms them into Backstage entities and subsequently persists them to the software catalog. + +Entity providers are a relatively new abstraction and the recommended way to ingest data into the catalog. The Backstage catalog engine starts each registered entity provider, which connects to its data source (e.g., the GitHub Entity Provider connects to GitHub). The entity provider will query the external data source and convert the data into the entity format. Finally, the entity provider issues what is known as a mutation to the catalog engine. A mutation is a signal from the entity provider to the catalog engine that entities are available to be processed and stored. + +A mutation can be either a full mutation or a delta mutation. A full mutation replaces all entities previously ingested by the entity provider with a new set of entities. The entity provider will remove all entities not found in the latest ingestion. A full mutation can be used to ingest relatively small datasets (less than 10,000 entities); however, ingesting more during a full ingestion may cause out-of-memory errors and delay the processing of entities from other entity providers. A delta mutation can surgically add and remove entities from the catalog. A delta mutation is useful when the data source provides events-based APIs like webhooks, which allows the Backstage catalog engine to ingest a small number of entities as they get added, updated and/or deleted. + +## Incremental entity providers + +A large organization typically deals with massive datasets. Until recently, ingesting large datasets with entity providers has been problematic because performing a full ingestion resulted in out-of-memory errors, and many data sources don’t provide webhooks or other events-based APIs. At the same time, the datasets were too large to efficiently manage through targeted delta mutations. + +This is a problem that [DevEx team at HP](http://hp.com) faced when building their software catalog with Backstage. Damon Kaswell, Senior Application Developer on the DevEx team at HP, shared their experience at [BackstageCon 2022](https://www.youtube.com/watch?v=5qHyZntKXRU&list=PLj6h78yzYM2OKySsTuiip3BqmdYZQRnSf&index=13), detailing the problem and the solution that Frontside created in collaboration with developers on HP’s DevEx team. + + +The solution HP and Frontside arrived at was to implement an incremental entity provider. An incremental entity provider effectively performs a full mutation using a series of delta mutations combined with a mark and sweep mechanism. It paginates through the dataset, tracking entities retrieved from each page and the cursor of the next page, pausing ingestion every few minutes to give the processing loop time to process existing entities. Once it reaches the end of the dataset, it determines which entities were not ingested during this ingestion cycle and emits a delta mutation to delete unmarked entities. + +Simply by adding a few new tables to the database schema, the incremental ingestion entity provider converts any existing entity provider into an incremental entity provider. These tables allow the incremental entity provider to be long-lived and keep track of its current location in the dataset by persisting a cursor that it uses to page through any large dataset. The larger the dataset, the more pages of data or bursts of work the incremental entity provider will ingest—but there will be no out-of-memory errors, effectively removing scalability problems. + +The results speak for themselves. Migrating from regular entity providers to incremental entity providers reduced ingestion time by 92% – from over 4 and a half hours to just 20 minutes. Incremental entity providers eliminated the ingestion maintenance burden from being a constant problem to a non-issue. Writing reliable integration with external services can now be done in days instead of weeks. + + +## Go forth and ingest! + +Backstage provides a robust framework for ingesting data from external sources, but HP needed to scale it beyond its design. The Backstage framework allowed Frontside and HP’s developers to extend it with a plugin to support HP’s scaling requirements. + +We're delighted to share that as of [this PR](https://github.com/backstage/backstage/pull/14356), the incremental ingestion backend is available for anyone to use with Backstage. The solution was released open source as [@backstage/plugin-catalog-backend-module-incremental-ingestion](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#backstageplugin-catalog-backend-module-incremental-ingestion) and contains a package for creating incremental entity providers. The plugin's [repository README](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion) has detailed configuration and usage outlined. + +The incremental ingestion entity provider is an excellent addition to the Backstage stack. Battle-tested on large datasets, the incremental entity provider is a significant step forward in smoothing the path to successful ingestion at scale. \ No newline at end of file diff --git a/microsite/blog/assets/catalog-pipeline.png b/microsite/blog/assets/catalog-pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..e21de99a7a9bd1088cc58a0056a18a86e0caf377 GIT binary patch literal 40892 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 jNOE5+@&7rZ_3ja!2t~%hQuN^i;G- Date: Tue, 31 Jan 2023 14:35:07 +0000 Subject: [PATCH 2/7] run prettier Signed-off-by: Paul Cowan --- microsite/blog/2023-01-31-incremental-entity-provider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/blog/2023-01-31-incremental-entity-provider.md b/microsite/blog/2023-01-31-incremental-entity-provider.md index 8ea74c01b2..991ac4db4a 100644 --- a/microsite/blog/2023-01-31-incremental-entity-provider.md +++ b/microsite/blog/2023-01-31-incremental-entity-provider.md @@ -25,7 +25,7 @@ A mutation can be either a full mutation or a delta mutation. A full mutation re A large organization typically deals with massive datasets. Until recently, ingesting large datasets with entity providers has been problematic because performing a full ingestion resulted in out-of-memory errors, and many data sources don’t provide webhooks or other events-based APIs. At the same time, the datasets were too large to efficiently manage through targeted delta mutations. -This is a problem that [DevEx team at HP](http://hp.com) faced when building their software catalog with Backstage. Damon Kaswell, Senior Application Developer on the DevEx team at HP, shared their experience at [BackstageCon 2022](https://www.youtube.com/watch?v=5qHyZntKXRU&list=PLj6h78yzYM2OKySsTuiip3BqmdYZQRnSf&index=13), detailing the problem and the solution that Frontside created in collaboration with developers on HP’s DevEx team. +This is a problem that [DevEx team at HP](http://hp.com) faced when building their software catalog with Backstage. [Damon Kaswell](https://github.com/dekoding), Senior Application Developer on the DevEx team at HP, shared their experience at [BackstageCon 2022](https://www.youtube.com/watch?v=5qHyZntKXRU&list=PLj6h78yzYM2OKySsTuiip3BqmdYZQRnSf&index=13), detailing the problem and the solution that Frontside created in collaboration with developers on HP’s DevEx team. The solution HP and Frontside arrived at was to implement an incremental entity provider. An incremental entity provider effectively performs a full mutation using a series of delta mutations combined with a mark and sweep mechanism. It paginates through the dataset, tracking entities retrieved from each page and the cursor of the next page, pausing ingestion every few minutes to give the processing loop time to process existing entities. Once it reaches the end of the dataset, it determines which entities were not ingested during this ingestion cycle and emits a delta mutation to delete unmarked entities. From 1048a31ef6ec190d32fdd284935d60183d403a79 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 31 Jan 2023 14:42:56 +0000 Subject: [PATCH 3/7] make Frontside links Signed-off-by: Paul Cowan --- microsite/blog/2023-01-31-incremental-entity-provider.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/blog/2023-01-31-incremental-entity-provider.md b/microsite/blog/2023-01-31-incremental-entity-provider.md index 991ac4db4a..cffe118deb 100644 --- a/microsite/blog/2023-01-31-incremental-entity-provider.md +++ b/microsite/blog/2023-01-31-incremental-entity-provider.md @@ -25,10 +25,10 @@ A mutation can be either a full mutation or a delta mutation. A full mutation re A large organization typically deals with massive datasets. Until recently, ingesting large datasets with entity providers has been problematic because performing a full ingestion resulted in out-of-memory errors, and many data sources don’t provide webhooks or other events-based APIs. At the same time, the datasets were too large to efficiently manage through targeted delta mutations. -This is a problem that [DevEx team at HP](http://hp.com) faced when building their software catalog with Backstage. [Damon Kaswell](https://github.com/dekoding), Senior Application Developer on the DevEx team at HP, shared their experience at [BackstageCon 2022](https://www.youtube.com/watch?v=5qHyZntKXRU&list=PLj6h78yzYM2OKySsTuiip3BqmdYZQRnSf&index=13), detailing the problem and the solution that Frontside created in collaboration with developers on HP’s DevEx team. +This is a problem that [DevEx team at HP](http://hp.com) faced when building their software catalog with Backstage. [Damon Kaswell](https://github.com/dekoding), Senior Application Developer on the DevEx team at HP, shared their experience at [BackstageCon 2022](https://www.youtube.com/watch?v=5qHyZntKXRU&list=PLj6h78yzYM2OKySsTuiip3BqmdYZQRnSf&index=13), detailing the problem and the solution that [Frontside](https://frontside.com/) created in collaboration with developers on HP’s DevEx team. -The solution HP and Frontside arrived at was to implement an incremental entity provider. An incremental entity provider effectively performs a full mutation using a series of delta mutations combined with a mark and sweep mechanism. It paginates through the dataset, tracking entities retrieved from each page and the cursor of the next page, pausing ingestion every few minutes to give the processing loop time to process existing entities. Once it reaches the end of the dataset, it determines which entities were not ingested during this ingestion cycle and emits a delta mutation to delete unmarked entities. +The solution HP and [Frontside](https://frontside.com/) arrived at was to implement an incremental entity provider. An incremental entity provider effectively performs a full mutation using a series of delta mutations combined with a mark and sweep mechanism. It paginates through the dataset, tracking entities retrieved from each page and the cursor of the next page, pausing ingestion every few minutes to give the processing loop time to process existing entities. Once it reaches the end of the dataset, it determines which entities were not ingested during this ingestion cycle and emits a delta mutation to delete unmarked entities. Simply by adding a few new tables to the database schema, the incremental ingestion entity provider converts any existing entity provider into an incremental entity provider. These tables allow the incremental entity provider to be long-lived and keep track of its current location in the dataset by persisting a cursor that it uses to page through any large dataset. The larger the dataset, the more pages of data or bursts of work the incremental entity provider will ingest—but there will be no out-of-memory errors, effectively removing scalability problems. @@ -37,7 +37,7 @@ The results speak for themselves. Migrating from regular entity providers to inc ## Go forth and ingest! -Backstage provides a robust framework for ingesting data from external sources, but HP needed to scale it beyond its design. The Backstage framework allowed Frontside and HP’s developers to extend it with a plugin to support HP’s scaling requirements. +Backstage provides a robust framework for ingesting data from external sources, but HP needed to scale it beyond its design. The Backstage framework allowed [Frontside](https://frontside.com/) and HP’s developers to extend it with a plugin to support HP’s scaling requirements. We're delighted to share that as of [this PR](https://github.com/backstage/backstage/pull/14356), the incremental ingestion backend is available for anyone to use with Backstage. The solution was released open source as [@backstage/plugin-catalog-backend-module-incremental-ingestion](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#backstageplugin-catalog-backend-module-incremental-ingestion) and contains a package for creating incremental entity providers. The plugin's [repository README](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion) has detailed configuration and usage outlined. From 6c81e72a21b8006552514ffeb10989046a6c73fa Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 31 Jan 2023 14:54:09 +0000 Subject: [PATCH 4/7] run prettier in microsite Signed-off-by: Paul Cowan --- .../2023-01-31-incremental-entity-provider.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/microsite/blog/2023-01-31-incremental-entity-provider.md b/microsite/blog/2023-01-31-incremental-entity-provider.md index cffe118deb..82fafc3b4f 100644 --- a/microsite/blog/2023-01-31-incremental-entity-provider.md +++ b/microsite/blog/2023-01-31-incremental-entity-provider.md @@ -3,19 +3,20 @@ title: Scaling Backstage Ingestion with Incremental Entity Providers author: Paul Cowan & Taras Mankovski authorURL: https://frontside.com/ --- + # Scaling Backstage Ingestion with Incremental Entity Providers -At the heart of [Backstage](backstage.io) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. +At the heart of [Backstage](backstage.io) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. ![catalog pipeline](./assets/catalog-pipeline.png) A common use case is for an organization to want to surface ownership and metadata about repositories. Backstage provides a mechanism for discovering and transforming repository information into a standard data structure and persisting it into the Backstage [Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview). This process is known as ingestion, where all data is transformed into a standard Backstage data structure known as an entity. Entities in the Catalog’s data store are accessible to the Backstage App via the REST API. -Data is transformed into entities via what is known as the ingestion and processing loop, which can be thought of as an [extract, transform and load (ETL) pipeline](https://en.wikipedia.org/wiki/Extract,_transform,_load), where raw data such as GitHub repositories are loaded from GitHub, transformed into entities and outputted to the Catalog. +Data is transformed into entities via what is known as the ingestion and processing loop, which can be thought of as an [extract, transform and load (ETL) pipeline](https://en.wikipedia.org/wiki/Extract,_transform,_load), where raw data such as GitHub repositories are loaded from GitHub, transformed into entities and outputted to the Catalog. ## Entity Providers -Backstage offers what are known as [entity providers](https://backstage.io/docs/features/software-catalog/life-of-an-entity) as a means for ingesting the raw data into the pipeline and transforming them into Backstage entities. For example, Backstage comes with a [GitHub Entity Provider](https://backstage.io/docs/reference/plugin-catalog-backend-module-github) that finds all catalog-info.yaml files in GitHub repositories. The processing loop transforms them into Backstage entities and subsequently persists them to the software catalog. +Backstage offers what are known as [entity providers](https://backstage.io/docs/features/software-catalog/life-of-an-entity) as a means for ingesting the raw data into the pipeline and transforming them into Backstage entities. For example, Backstage comes with a [GitHub Entity Provider](https://backstage.io/docs/reference/plugin-catalog-backend-module-github) that finds all catalog-info.yaml files in GitHub repositories. The processing loop transforms them into Backstage entities and subsequently persists them to the software catalog. Entity providers are a relatively new abstraction and the recommended way to ingest data into the catalog. The Backstage catalog engine starts each registered entity provider, which connects to its data source (e.g., the GitHub Entity Provider connects to GitHub). The entity provider will query the external data source and convert the data into the entity format. Finally, the entity provider issues what is known as a mutation to the catalog engine. A mutation is a signal from the entity provider to the catalog engine that entities are available to be processed and stored. @@ -27,13 +28,11 @@ A large organization typically deals with massive datasets. Until recently, inge This is a problem that [DevEx team at HP](http://hp.com) faced when building their software catalog with Backstage. [Damon Kaswell](https://github.com/dekoding), Senior Application Developer on the DevEx team at HP, shared their experience at [BackstageCon 2022](https://www.youtube.com/watch?v=5qHyZntKXRU&list=PLj6h78yzYM2OKySsTuiip3BqmdYZQRnSf&index=13), detailing the problem and the solution that [Frontside](https://frontside.com/) created in collaboration with developers on HP’s DevEx team. - The solution HP and [Frontside](https://frontside.com/) arrived at was to implement an incremental entity provider. An incremental entity provider effectively performs a full mutation using a series of delta mutations combined with a mark and sweep mechanism. It paginates through the dataset, tracking entities retrieved from each page and the cursor of the next page, pausing ingestion every few minutes to give the processing loop time to process existing entities. Once it reaches the end of the dataset, it determines which entities were not ingested during this ingestion cycle and emits a delta mutation to delete unmarked entities. -Simply by adding a few new tables to the database schema, the incremental ingestion entity provider converts any existing entity provider into an incremental entity provider. These tables allow the incremental entity provider to be long-lived and keep track of its current location in the dataset by persisting a cursor that it uses to page through any large dataset. The larger the dataset, the more pages of data or bursts of work the incremental entity provider will ingest—but there will be no out-of-memory errors, effectively removing scalability problems. - -The results speak for themselves. Migrating from regular entity providers to incremental entity providers reduced ingestion time by 92% – from over 4 and a half hours to just 20 minutes. Incremental entity providers eliminated the ingestion maintenance burden from being a constant problem to a non-issue. Writing reliable integration with external services can now be done in days instead of weeks. +Simply by adding a few new tables to the database schema, the incremental ingestion entity provider converts any existing entity provider into an incremental entity provider. These tables allow the incremental entity provider to be long-lived and keep track of its current location in the dataset by persisting a cursor that it uses to page through any large dataset. The larger the dataset, the more pages of data or bursts of work the incremental entity provider will ingest—but there will be no out-of-memory errors, effectively removing scalability problems. +The results speak for themselves. Migrating from regular entity providers to incremental entity providers reduced ingestion time by 92% – from over 4 and a half hours to just 20 minutes. Incremental entity providers eliminated the ingestion maintenance burden from being a constant problem to a non-issue. Writing reliable integration with external services can now be done in days instead of weeks. ## Go forth and ingest! @@ -41,4 +40,4 @@ Backstage provides a robust framework for ingesting data from external sources, We're delighted to share that as of [this PR](https://github.com/backstage/backstage/pull/14356), the incremental ingestion backend is available for anyone to use with Backstage. The solution was released open source as [@backstage/plugin-catalog-backend-module-incremental-ingestion](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#backstageplugin-catalog-backend-module-incremental-ingestion) and contains a package for creating incremental entity providers. The plugin's [repository README](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion) has detailed configuration and usage outlined. -The incremental ingestion entity provider is an excellent addition to the Backstage stack. Battle-tested on large datasets, the incremental entity provider is a significant step forward in smoothing the path to successful ingestion at scale. \ No newline at end of file +The incremental ingestion entity provider is an excellent addition to the Backstage stack. Battle-tested on large datasets, the incremental entity provider is a significant step forward in smoothing the path to successful ingestion at scale. From a9177c9ee8d24c981db26c5c68af3aac491e2b5b Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 31 Jan 2023 15:12:29 +0000 Subject: [PATCH 5/7] run prettier in microsite Signed-off-by: Paul Cowan --- .../2023-01-31-incremental-entity-provider.md | 4 +++- .../{ => 2023-01-31}/catalog-pipeline.png | Bin microsite/blog/assets/2023-01-31/damon.jpg | Bin 0 -> 10263 bytes 3 files changed, 3 insertions(+), 1 deletion(-) rename microsite/blog/assets/{ => 2023-01-31}/catalog-pipeline.png (100%) create mode 100644 microsite/blog/assets/2023-01-31/damon.jpg diff --git a/microsite/blog/2023-01-31-incremental-entity-provider.md b/microsite/blog/2023-01-31-incremental-entity-provider.md index 82fafc3b4f..7737582cbf 100644 --- a/microsite/blog/2023-01-31-incremental-entity-provider.md +++ b/microsite/blog/2023-01-31-incremental-entity-provider.md @@ -8,7 +8,7 @@ authorURL: https://frontside.com/ At the heart of [Backstage](backstage.io) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. -![catalog pipeline](./assets/catalog-pipeline.png) +![catalog pipeline](./assets/2023-01-31/catalog-pipeline.png) A common use case is for an organization to want to surface ownership and metadata about repositories. Backstage provides a mechanism for discovering and transforming repository information into a standard data structure and persisting it into the Backstage [Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview). This process is known as ingestion, where all data is transformed into a standard Backstage data structure known as an entity. Entities in the Catalog’s data store are accessible to the Backstage App via the REST API. @@ -28,6 +28,8 @@ A large organization typically deals with massive datasets. Until recently, inge This is a problem that [DevEx team at HP](http://hp.com) faced when building their software catalog with Backstage. [Damon Kaswell](https://github.com/dekoding), Senior Application Developer on the DevEx team at HP, shared their experience at [BackstageCon 2022](https://www.youtube.com/watch?v=5qHyZntKXRU&list=PLj6h78yzYM2OKySsTuiip3BqmdYZQRnSf&index=13), detailing the problem and the solution that [Frontside](https://frontside.com/) created in collaboration with developers on HP’s DevEx team. +![Damon Kaswell](./assets/2023-01-31/damon.jpg) + The solution HP and [Frontside](https://frontside.com/) arrived at was to implement an incremental entity provider. An incremental entity provider effectively performs a full mutation using a series of delta mutations combined with a mark and sweep mechanism. It paginates through the dataset, tracking entities retrieved from each page and the cursor of the next page, pausing ingestion every few minutes to give the processing loop time to process existing entities. Once it reaches the end of the dataset, it determines which entities were not ingested during this ingestion cycle and emits a delta mutation to delete unmarked entities. Simply by adding a few new tables to the database schema, the incremental ingestion entity provider converts any existing entity provider into an incremental entity provider. These tables allow the incremental entity provider to be long-lived and keep track of its current location in the dataset by persisting a cursor that it uses to page through any large dataset. The larger the dataset, the more pages of data or bursts of work the incremental entity provider will ingest—but there will be no out-of-memory errors, effectively removing scalability problems. diff --git a/microsite/blog/assets/catalog-pipeline.png b/microsite/blog/assets/2023-01-31/catalog-pipeline.png similarity index 100% rename from microsite/blog/assets/catalog-pipeline.png rename to microsite/blog/assets/2023-01-31/catalog-pipeline.png diff --git a/microsite/blog/assets/2023-01-31/damon.jpg b/microsite/blog/assets/2023-01-31/damon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1e37d354ab02911e65100bd2aa88509bc5a99fc GIT binary patch literal 10263 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 literal 0 HcmV?d00001 From 3116c1d38a53bec12dbadd3b54e897321557db1e Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 31 Jan 2023 15:26:48 +0000 Subject: [PATCH 6/7] update accept.txt Signed-off-by: Paul Cowan --- .github/vale/Vocab/Backstage/accept.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index d8c962ecc2..f24b701d36 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -417,3 +417,5 @@ allowlisted Dominik Henneke Kuang +Frontside +Kaswell \ No newline at end of file From 7a36e2ba229abfeab86dca33b0f7906f4ebcf355 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 31 Jan 2023 15:54:55 +0000 Subject: [PATCH 7/7] backstage link Signed-off-by: Paul Cowan --- microsite/blog/2023-01-31-incremental-entity-provider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/blog/2023-01-31-incremental-entity-provider.md b/microsite/blog/2023-01-31-incremental-entity-provider.md index 7737582cbf..64ac69c7a0 100644 --- a/microsite/blog/2023-01-31-incremental-entity-provider.md +++ b/microsite/blog/2023-01-31-incremental-entity-provider.md @@ -6,7 +6,7 @@ authorURL: https://frontside.com/ # Scaling Backstage Ingestion with Incremental Entity Providers -At the heart of [Backstage](backstage.io) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. +At the heart of [Backstage](https://backstage.io/) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. ![catalog pipeline](./assets/2023-01-31/catalog-pipeline.png)