GitLab Organizational Data
The Backstage catalog can be set up to ingest organizational data -- users and
groups -- directly from GitLab. The result is a hierarchy of
User
and
Group
entities that mirrors your org setup.
This provider can also be configured to ingest GitLab data based on GitLab System hooks. The events currently accepted are:
group_create
group_destroy
group_rename
user_create
user_destroy
user_add_to_group
user_remove_from_group
Installation
As this provider is not one of the default providers, you will first need to install the Gitlab provider plugin:
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitlab @backstage/plugin-catalog-backend-module-gitlab-org
Installation with New Backend System
Then add the following to your backend initialization:
// optional if you want HTTP endpoints to receive external events
// backend.add(import('@backstage/plugin-events-backend'));
// optional if you want to use AWS SQS instead of HTTP endpoints to receive external events
// backend.add(import('@backstage/plugin-events-backend-module-aws-sqs'));
// optional - event router for gitlab. See.: https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md
// backend.add(eventsModuleGitlabEventRouter);
// optional - token validator for the gitlab topic
// backend.add(eventsModuleGitlabWebhook);
backend.add(import('@backstage/plugin-catalog-backend-module-gitlab-org'));
You need to decide how you want to receive events from external sources like
Further documentation:
Installation with Legacy Backend System
Installation without Events Support
Add the plugin to the plugin catalog packages/backend/src/plugins/catalog.ts
:
/* packages/backend/src/plugins/catalog.ts */
import { GitlabOrgDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/** ... other processors and/or providers ... */
builder.addEntityProvider(
...GitlabOrgDiscoveryEntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
);
// ..
}
Installation with Events Support
Please follow the installation instructions at
Additionally, you need to decide how you want to receive events from external sources like
Set up your provider
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
import { GitlabOrgDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab';
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
builder.addProcessor(new ScaffolderEntitiesProcessor());
const gitlabOrgProvider = GitlabOrgDiscoveryEntityProvider.fromConfig(
env.config,
{
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
events: env.events,
},
);
builder.addEntityProvider(gitlabOrgProvider);
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}
Configuration
To use the entity provider, you'll need a Gitlab integration set up.
integrations:
gitlab:
- host: gitlab.com
token: ${GITLAB_TOKEN}
This will query all users and groups from your GitLab instance. Depending on the amount of data, this can take significant time and resources.
The token used must have the read_api
scope, and the Users and Groups fetched
will be those visible to the account which provisioned the token.
If you are using the New Backend System, the schedule
has to be setup in the config, as shown below.
catalog:
providers:
gitlab:
yourProviderId:
host: gitlab.com
orgEnabled: true
group: org/teams # Required for gitlab.com when `orgEnabled: true`. Optional for self managed. Must not end with slash. Accepts only this group and groups under the provided path (which will be stripped)
relations: # Optional
- INHERITED # Optional. Members of any ancestor groups will also be considered members of the current group.
- DESCENDANTS # Optional. Members of any descendant groups will also be considered members of the current group.
- SHARED_FROM_GROUPS # Optional. Members of any invited groups will also be considered members of the current group.
groupPattern: '[\s\S]*' # Optional. Filters found groups based on provided pattern. Defaults to `[\s\S]*`, which means to not filter anything
schedule: # Same options as in SchedulerServiceTaskScheduleDefinition. Optional for the Legacy Backend System.
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
timeout: { minutes: 3 }
Groups
When the group
parameter is provided, the corresponding path prefix will be
stripped out from each matching group when computing the unique entity name.
e.g. If group
is org/teams
, the name for org/teams/avengers/gotg
will be
avengers-gotg
.
For gitlab.com, when orgEnabled: true
, the group
parameter is required in
order to limit the ingestion to a group within your organisation. Group
entities will only be ingested for the configured group, or its descendant groups,
but not any ancestor groups higher than the configured group path. Only groups
which contain members will be ingested.
Subgroup Membership
GitLab groups and subgroups provide a hierarchical structure for organizing projects and users. Membership in a parent group extends automatically to its subgroups, ensuring consistent permissions at all levels. Additionally, membership can be managed using invited groups, where one group can be added to another. For Backstage users integrating with GitLab, understanding this inheritance model and the concept of invited groups is crucial for accurately mapping and managing group and user entities.
The GitLabOrgDiscoveryEntityProvider
mirrors GitLab's membership behavior as follows:
- By default, every direct member of a GitLab group is also a member of the corresponding group in Backstage.
- To include members of subgroups as members of the parent group, configure the
relations
array with theDESCENDANTS
option. - To include members of parent groups as members of their subgroups, configure the
relations
array with theINHERITED
option. This also has the effect that subgroups with no direct members will not be skipped in the group ingestion process and will be added as a group entity in Backstage; - To include members of invited groups as members of the inviting group, configure the
relations
array with theSHARED_FROM_GROUPS
option.
The previous allowInherited
will be deprecated in future versions. Use the relations
array with the INHERITED
option instead.
catalog:
providers:
gitlab:
development:
relations:
- INHERITED
- DESCENDANTS
- SHARED_FROM_GROUPS
Refer to the GitLab Group Member Relation documentation for more information.
Users
For self hosted, all User
entities are ingested from the entire instance by default.
For gitlab.com User
entities for users who have direct or inherited membership
of the top-level group for the configured group path will be ingested.
In both cases (SaaS & self hosted), you can limit the ingested users to users directly assigned to the group defined in your app-config.yaml
by setting the configuration key restrictUsersToGroup: true
. This is especially useful when you have a large user base that you don't want to import by default.
On SaaS, you can choose to include users to be ingested that do not have a paid seat. This can be useful when using a free version of Gitlab, or when you use Guest Users on Gitlab Ultimate. Unfortunately, this will also lead to some technical users that might be imported into your user base. While project & group access tokens are filtered, service accounts will remain. Learn more about Billable Users.
catalog:
providers:
gitlab:
yourProviderId:
host: gitlab.com ## Could also be self hosted.
orgEnabled: true
group: org/teams # Required for gitlab.com when `orgEnabled: true`. Optional for self managed. Must not end with slash. Accepts only this group and groups under the provided path (which will be stripped)
restrictUsersToGroup: true # Optional: Backstage will ingest only users directly assigned to org/teams.
includeUsersWithoutSeat: false # Optional: Set to true to include users without paid seat, only applicable for SaaS
Limiting User
and Group
entity ingestion in the provider
Optionally, you can limit the entity types ingested by the provider when using
orgEnabled: true
with the following rules
configuration to limit it to only
User
and Group
entities.
catalog:
providers:
gitlab:
yourOrgDataProviderId:
host: gitlab.com
orgEnabled: true
group: org/teams
rules:
- allow: [Group, User]
Custom Transformers
You can inject your own transformation logic to help map GitLab API responses into Backstage entities. You can do this on the user and group requests to enable you to do further processing or updates to these entities.
To enable this you pass a function into the GitlabOrgDiscoveryEntityProvider
. You can
pass a UserTransformer
, a GroupEntitiesTransformer
or a GroupNameTransformer
(or all of them)). The function is invoked
for each item (user or group) that is returned from the API.
The example below uses the groupNameTransformer option to change the metadata.name property of the Backstage Group Entity. Instead of populating it with the usual group.full_path
data that comes from GitLab, it uses the group.id
:
import { loggerToWinstonLogger } from '@backstage/backend-common';
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import {
GitlabOrgDiscoveryEntityProvider,
GroupNameTransformerOptions,
} from '@backstage/plugin-catalog-backend-module-gitlab';
function customGroupNameTransformer(
options: GroupNameTransformerOptions,
): string {
return `${options.group.id}`;
}
/**
* Registers the GitlabDiscoveryEntityProvider with the catalog processing extension point.
*
* @alpha
*/
export const catalogModuleGitlabOrgDiscoveryEntityProvider =
createBackendModule({
pluginId: 'catalog',
moduleId: 'gitlabOrgDiscoveryEntityProvider',
register(env) {
env.registerInit({
deps: {
config: coreServices.rootConfig,
catalog: catalogProcessingExtensionPoint,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
events: eventsServiceRef,
},
async init({ config, catalog, logger, scheduler, events }) {
const gitlabOrgDiscoveryEntityProvider =
GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
groupNameTransformer: customGroupNameTransformer,
logger: loggerToWinstonLogger(logger),
events,
scheduler,
});
catalog.addEntityProvider(gitlabOrgDiscoveryEntityProvider);
},
});
},
});
Troubleshooting
NOTE: If any groups that are being ingested are empty groups (i.e. do not
contain any projects) and the user which provisioned the token is shared with a
higher level group via group sharing
and you don't see the expected number of Group
entities in the catalog you may
be hitting this Gitlab issue.