refct(events,catalog/github): remove catalog api dependency
Signed-off-by: Rogerio Angeliski <angeliski@hotmail.com>
This commit is contained in:
@@ -76,7 +76,6 @@ Set up your provider
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/catalogEventBasedProviders.ts
|
||||
+import { CatalogClient } from '@backstage/catalog-client';
|
||||
+import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-node';
|
||||
import { EventSubscriber } from '@backstage/plugin-events-node';
|
||||
@@ -94,8 +93,6 @@ Set up your provider
|
||||
+ id: 'production',
|
||||
+ orgUrl: 'https://github.com/backstage',
|
||||
+ logger: env.logger,
|
||||
+ catalogApi: new CatalogClient({ discoveryApi: env.discovery }),
|
||||
+ tokenManager: env.tokenManager,
|
||||
+ schedule: env.scheduler.createScheduledTaskRunner({
|
||||
+ frequency: { minutes: 60 },
|
||||
+ timeout: { minutes: 15 },
|
||||
@@ -106,9 +103,6 @@ Set up your provider
|
||||
}
|
||||
```
|
||||
|
||||
**Attention:**
|
||||
`catalogApi` and `tokenManager` are required at this variant compared to the one without events support.
|
||||
|
||||
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks).
|
||||
The webhook will need to be configured to forward `organization`,`team` and `membership` events.
|
||||
|
||||
@@ -165,10 +159,6 @@ There is also a `defaultUserTransformer` and `defaultOrganizationTeamTransformer
|
||||
You could use these and simply decorate the response from the default
|
||||
transformation if you only need to change a few properties.
|
||||
|
||||
**Attention:**
|
||||
When you use the Events Support with a `TeamTransformer`, you need to ensure your entity will have the
|
||||
annotation `github.com/team-id` with the value from `team.databaseId`. Without this, the `team.edit` event could fail to update
|
||||
|
||||
### Resolving GitHub users via organization email
|
||||
|
||||
When you authenticate users you should resolve them to an entity within the
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
```ts
|
||||
import { AnalyzeOptions } from '@backstage/plugin-catalog-backend';
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { CatalogProcessor } from '@backstage/plugin-catalog-backend';
|
||||
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend';
|
||||
import { Config } from '@backstage/config';
|
||||
@@ -189,8 +188,6 @@ export class GithubOrgEntityProvider
|
||||
githubCredentialsProvider?: GithubCredentialsProvider;
|
||||
userTransformer?: UserTransformer;
|
||||
teamTransformer?: TeamTransformer;
|
||||
catalogApi?: CatalogApi;
|
||||
tokenManager?: TokenManager;
|
||||
});
|
||||
// (undocumented)
|
||||
connect(connection: EntityProviderConnection): Promise<void>;
|
||||
@@ -213,16 +210,12 @@ export type GitHubOrgEntityProviderOptions = GithubOrgEntityProviderOptions;
|
||||
|
||||
// @public
|
||||
export interface GithubOrgEntityProviderOptions {
|
||||
// (undocumented)
|
||||
catalogApi?: CatalogApi;
|
||||
githubCredentialsProvider?: GithubCredentialsProvider;
|
||||
id: string;
|
||||
logger: Logger;
|
||||
orgUrl: string;
|
||||
schedule?: 'manual' | TaskRunner;
|
||||
teamTransformer?: TeamTransformer;
|
||||
// (undocumented)
|
||||
tokenManager?: TokenManager;
|
||||
userTransformer?: UserTransformer;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,6 @@ import {
|
||||
GithubTeam,
|
||||
createAddEntitiesOperation,
|
||||
createRemoveEntitiesOperation,
|
||||
createReplaceEntitiesOperation,
|
||||
removeUserFromGroup,
|
||||
addUserToGroup,
|
||||
} from './github';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
@@ -638,173 +635,4 @@ describe('github', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createReplaceEntitiesOperation', () => {
|
||||
it('create a function to replace deferred entities to a delta operation', () => {
|
||||
const operation = createReplaceEntitiesOperation('my-id', 'host');
|
||||
|
||||
const userEntity: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'githubuser',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'github.com/user-login': 'githubuser',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['new-team'],
|
||||
},
|
||||
};
|
||||
expect(operation('org', [userEntity])).toEqual({
|
||||
removed: [
|
||||
{
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: userEntity,
|
||||
},
|
||||
],
|
||||
added: [
|
||||
{
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: userEntity,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeUserFromGroup', () => {
|
||||
it('should remove a user from the group', () => {
|
||||
const groupEntity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'new-team',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
members: ['user-1', 'githubuser'],
|
||||
},
|
||||
};
|
||||
|
||||
const userEntity: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'githubuser',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'github.com/user-login': 'githubuser',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['new-team'],
|
||||
},
|
||||
};
|
||||
|
||||
const { user, group } = removeUserFromGroup(userEntity, groupEntity);
|
||||
|
||||
expect(user.spec.memberOf).toEqual([]);
|
||||
expect(group.spec.members).toEqual(['user-1']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addUserToGroup', () => {
|
||||
it('should add a user to the group', () => {
|
||||
const groupEntity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'new-team',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
members: ['user-1'],
|
||||
},
|
||||
};
|
||||
|
||||
const userEntity: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'githubuser',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'github.com/user-login': 'githubuser',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
},
|
||||
};
|
||||
|
||||
const { user, group } = addUserToGroup(userEntity, groupEntity);
|
||||
expect(user.spec.memberOf).toEqual(['new-team']);
|
||||
expect(group.spec.members).toEqual(['user-1', 'githubuser']);
|
||||
});
|
||||
it('should add a user to the group when the fields are not defined', () => {
|
||||
const groupEntity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'new-team',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
|
||||
const userEntity: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'githubuser',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'github.com/user-login': 'githubuser',
|
||||
},
|
||||
},
|
||||
spec: {},
|
||||
};
|
||||
|
||||
const { user, group } = addUserToGroup(userEntity, groupEntity);
|
||||
|
||||
expect(user.spec.memberOf).toEqual(['new-team']);
|
||||
expect(group.spec.members).toEqual(['githubuser']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
import { withLocations } from '../providers/GithubOrgEntityProvider';
|
||||
|
||||
import { DeferredEntity } from '@backstage/plugin-catalog-backend';
|
||||
import { merge, omit } from 'lodash';
|
||||
|
||||
// Graphql types
|
||||
|
||||
@@ -419,11 +418,6 @@ export type DeferredEntitiesBuilder = (
|
||||
entities: Entity[],
|
||||
) => { added: DeferredEntity[]; removed: DeferredEntity[] };
|
||||
|
||||
export type EntityUpdateOperation = (
|
||||
user: UserEntity,
|
||||
group: GroupEntity,
|
||||
) => { user: UserEntity; group: GroupEntity };
|
||||
|
||||
export const createAddEntitiesOperation =
|
||||
(id: string, host: string) => (org: string, entities: Entity[]) => ({
|
||||
removed: [],
|
||||
@@ -441,52 +435,3 @@ export const createRemoveEntitiesOperation =
|
||||
entity: withLocations(`https://${host}`, org, entity),
|
||||
})),
|
||||
});
|
||||
|
||||
export const createReplaceEntitiesOperation =
|
||||
(id: string, host: string) => (org: string, entities: Entity[]) => {
|
||||
const entitiesToReplace = entities.map(entity => ({
|
||||
locationKey: `github-org-provider:${id}`,
|
||||
entity: withLocations(`https://${host}`, org, entity),
|
||||
}));
|
||||
|
||||
return {
|
||||
removed: entitiesToReplace,
|
||||
added: entitiesToReplace,
|
||||
};
|
||||
};
|
||||
|
||||
export function removeUserFromGroup(user: UserEntity, group: GroupEntity) {
|
||||
return {
|
||||
group: merge(omit(group, 'spec.members'), {
|
||||
spec: {
|
||||
members: group?.spec?.members?.filter(m => m !== user.metadata.name),
|
||||
},
|
||||
}),
|
||||
|
||||
user: merge(omit(user, 'spec.memberOf'), {
|
||||
spec: {
|
||||
memberOf: user?.spec?.memberOf?.filter(m => m !== group.metadata.name),
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function addUserToGroup(user: UserEntity, group: GroupEntity) {
|
||||
if (!group.spec?.members) {
|
||||
group.spec = {
|
||||
...group.spec,
|
||||
members: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!user.spec?.memberOf) {
|
||||
user.spec = {
|
||||
...user.spec,
|
||||
memberOf: [],
|
||||
};
|
||||
}
|
||||
|
||||
group.spec?.members?.push(user.metadata.name);
|
||||
user.spec?.memberOf?.push(group.metadata.name);
|
||||
return { group, user };
|
||||
}
|
||||
|
||||
+297
-279
@@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getVoidLogger, TokenManager } from '@backstage/backend-common';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import {
|
||||
GithubCredentialsProvider,
|
||||
GithubIntegrationConfig,
|
||||
@@ -28,19 +27,8 @@ import {
|
||||
GithubOrgEntityProvider,
|
||||
withLocations,
|
||||
} from './GithubOrgEntityProvider';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
jest.mock('@octokit/graphql');
|
||||
const tokenManager: jest.Mocked<TokenManager> = {
|
||||
getToken: jest.fn(),
|
||||
authenticate: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCatalogApi = {
|
||||
getEntityByRef: jest.fn(),
|
||||
getEntities: jest.fn(),
|
||||
};
|
||||
const catalogApi = mockCatalogApi as Partial<CatalogApi> as CatalogApi;
|
||||
|
||||
describe('GithubOrgEntityProvider', () => {
|
||||
describe('read', () => {
|
||||
@@ -273,7 +261,6 @@ describe('GithubOrgEntityProvider', () => {
|
||||
const githubCredentialsProvider: GithubCredentialsProvider = {
|
||||
getCredentials: mockGetCredentials,
|
||||
};
|
||||
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
|
||||
|
||||
const entityProvider = new GithubOrgEntityProvider({
|
||||
id: 'my-id',
|
||||
@@ -281,8 +268,6 @@ describe('GithubOrgEntityProvider', () => {
|
||||
orgUrl: 'https://github.com/backstage',
|
||||
gitHubConfig,
|
||||
logger,
|
||||
tokenManager,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
entityProvider.connect(entityProviderConnection);
|
||||
@@ -363,16 +348,12 @@ describe('GithubOrgEntityProvider', () => {
|
||||
getCredentials: mockGetCredentials,
|
||||
};
|
||||
|
||||
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
|
||||
|
||||
const entityProvider = new GithubOrgEntityProvider({
|
||||
id: 'my-id',
|
||||
githubCredentialsProvider,
|
||||
orgUrl: 'https://github.com/backstage',
|
||||
gitHubConfig,
|
||||
logger,
|
||||
tokenManager,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
entityProvider.connect(entityProviderConnection);
|
||||
@@ -453,15 +434,12 @@ describe('GithubOrgEntityProvider', () => {
|
||||
getCredentials: mockGetCredentials,
|
||||
};
|
||||
|
||||
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
|
||||
const entityProvider = new GithubOrgEntityProvider({
|
||||
id: 'my-id',
|
||||
githubCredentialsProvider,
|
||||
orgUrl: 'https://github.com/backstage',
|
||||
gitHubConfig,
|
||||
logger,
|
||||
tokenManager,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
entityProvider.connect(entityProviderConnection);
|
||||
@@ -547,15 +525,12 @@ describe('GithubOrgEntityProvider', () => {
|
||||
getCredentials: mockGetCredentials,
|
||||
};
|
||||
|
||||
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
|
||||
const entityProvider = new GithubOrgEntityProvider({
|
||||
id: 'my-id',
|
||||
githubCredentialsProvider,
|
||||
orgUrl: 'https://github.com/backstage',
|
||||
gitHubConfig,
|
||||
logger,
|
||||
tokenManager,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
entityProvider.connect(entityProviderConnection);
|
||||
@@ -642,17 +617,61 @@ describe('GithubOrgEntityProvider', () => {
|
||||
getCredentials: mockGetCredentials,
|
||||
};
|
||||
|
||||
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
|
||||
|
||||
const entityProvider = new GithubOrgEntityProvider({
|
||||
id: 'my-id',
|
||||
githubCredentialsProvider,
|
||||
orgUrl: 'https://github.com/backstage',
|
||||
gitHubConfig,
|
||||
logger,
|
||||
tokenManager,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
const mockClient = jest.fn();
|
||||
|
||||
mockClient
|
||||
.mockResolvedValueOnce({
|
||||
organization: {
|
||||
membersWithRole: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
login: 'a',
|
||||
name: 'b',
|
||||
bio: 'c',
|
||||
email: 'd',
|
||||
avatarUrl: 'e',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
organization: {
|
||||
teams: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
slug: 'team',
|
||||
combinedSlug: 'blah/team',
|
||||
name: 'Team',
|
||||
description: 'The one and only team',
|
||||
avatarUrl: 'http://example.com/team.jpeg',
|
||||
parentTeam: {
|
||||
slug: 'parent',
|
||||
combinedSlug: '',
|
||||
members: { pageInfo: { hasNextPage: false }, nodes: [] },
|
||||
},
|
||||
members: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [{ login: 'a' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(graphql.defaults as jest.Mock).mockReturnValue(mockClient);
|
||||
|
||||
entityProvider.connect(entityProviderConnection);
|
||||
|
||||
const event: EventParams = {
|
||||
@@ -680,71 +699,68 @@ describe('GithubOrgEntityProvider', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const entity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/mygroup"',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/mygroup"',
|
||||
},
|
||||
name: 'mygroup',
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
members: ['user-1'],
|
||||
},
|
||||
};
|
||||
|
||||
const expectedEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'new-team',
|
||||
description: 'description from the new team',
|
||||
annotations: {
|
||||
'backstage.io/edit-url':
|
||||
'https://github.com/orgs/test-org/teams/new-team/edit',
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'github.com/team-slug': 'test-org/new-team',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
members: ['user-1'],
|
||||
profile: {
|
||||
displayName: 'New Team',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockCatalogApi.getEntities.mockResolvedValue({
|
||||
items: [cloneDeep(entity)],
|
||||
});
|
||||
await entityProvider.onEvent(event);
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'delta',
|
||||
removed: [
|
||||
entities: [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/a',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/a',
|
||||
'github.com/user-login': 'a',
|
||||
},
|
||||
description: 'c',
|
||||
name: 'a',
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['team'],
|
||||
profile: {
|
||||
displayName: 'b',
|
||||
email: 'd',
|
||||
picture: 'e',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/backstage/teams/team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/backstage/teams/team',
|
||||
'github.com/team-slug': 'blah/team',
|
||||
},
|
||||
name: 'team',
|
||||
description: 'The one and only team',
|
||||
},
|
||||
spec: {
|
||||
children: [],
|
||||
parent: 'parent',
|
||||
profile: {
|
||||
displayName: 'Team',
|
||||
picture: 'http://example.com/team.jpeg',
|
||||
},
|
||||
type: 'team',
|
||||
members: ['a'],
|
||||
},
|
||||
},
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: entity,
|
||||
},
|
||||
],
|
||||
added: [
|
||||
{
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: expectedEntity,
|
||||
},
|
||||
],
|
||||
type: 'full',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -768,18 +784,60 @@ describe('GithubOrgEntityProvider', () => {
|
||||
getCredentials: mockGetCredentials,
|
||||
};
|
||||
|
||||
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
|
||||
|
||||
const entityProvider = new GithubOrgEntityProvider({
|
||||
id: 'my-id',
|
||||
githubCredentialsProvider,
|
||||
orgUrl: 'https://github.com/backstage',
|
||||
gitHubConfig,
|
||||
logger,
|
||||
tokenManager,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
const mockClient = jest.fn();
|
||||
|
||||
mockClient
|
||||
.mockResolvedValueOnce({
|
||||
organization: {
|
||||
membersWithRole: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
login: 'a',
|
||||
name: 'b',
|
||||
bio: 'c',
|
||||
email: 'd',
|
||||
avatarUrl: 'e',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
organization: {
|
||||
teams: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
slug: 'team',
|
||||
combinedSlug: 'blah/team',
|
||||
name: 'Team',
|
||||
description: 'The one and only team',
|
||||
avatarUrl: 'http://example.com/team.jpeg',
|
||||
parentTeam: {
|
||||
slug: 'parent',
|
||||
combinedSlug: '',
|
||||
members: { pageInfo: { hasNextPage: false }, nodes: [] },
|
||||
},
|
||||
members: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [{ login: 'a' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(graphql.defaults as jest.Mock).mockReturnValue(mockClient);
|
||||
entityProvider.connect(entityProviderConnection);
|
||||
|
||||
const event: EventParams = {
|
||||
@@ -804,109 +862,68 @@ describe('GithubOrgEntityProvider', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const groupEntity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'new-team',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
members: ['user-1'],
|
||||
},
|
||||
};
|
||||
|
||||
const userEntity: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'githubuser',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'github.com/user-login': 'githubuser',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
},
|
||||
};
|
||||
|
||||
const expectedGroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'new-team',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
members: ['user-1', 'githubuser'],
|
||||
},
|
||||
};
|
||||
|
||||
const expectedUserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'githubuser',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'github.com/user-login': 'githubuser',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['new-team'],
|
||||
},
|
||||
};
|
||||
|
||||
mockCatalogApi.getEntityByRef
|
||||
.mockResolvedValueOnce(cloneDeep(groupEntity))
|
||||
.mockResolvedValueOnce(cloneDeep(userEntity));
|
||||
|
||||
await entityProvider.onEvent(event);
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'delta',
|
||||
removed: [
|
||||
entities: [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/a',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/a',
|
||||
'github.com/user-login': 'a',
|
||||
},
|
||||
description: 'c',
|
||||
name: 'a',
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['team'],
|
||||
profile: {
|
||||
displayName: 'b',
|
||||
email: 'd',
|
||||
picture: 'e',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: groupEntity,
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/backstage/teams/team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/backstage/teams/team',
|
||||
'github.com/team-slug': 'blah/team',
|
||||
},
|
||||
name: 'team',
|
||||
description: 'The one and only team',
|
||||
},
|
||||
spec: {
|
||||
children: [],
|
||||
parent: 'parent',
|
||||
profile: {
|
||||
displayName: 'Team',
|
||||
picture: 'http://example.com/team.jpeg',
|
||||
},
|
||||
type: 'team',
|
||||
members: ['a'],
|
||||
},
|
||||
},
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: userEntity,
|
||||
},
|
||||
],
|
||||
added: [
|
||||
{
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: expectedGroupEntity,
|
||||
},
|
||||
{
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: expectedUserEntity,
|
||||
},
|
||||
],
|
||||
type: 'full',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -930,18 +947,60 @@ describe('GithubOrgEntityProvider', () => {
|
||||
getCredentials: mockGetCredentials,
|
||||
};
|
||||
|
||||
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
|
||||
|
||||
const entityProvider = new GithubOrgEntityProvider({
|
||||
id: 'my-id',
|
||||
githubCredentialsProvider,
|
||||
orgUrl: 'https://github.com/backstage',
|
||||
gitHubConfig,
|
||||
logger,
|
||||
tokenManager,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
const mockClient = jest.fn();
|
||||
|
||||
mockClient
|
||||
.mockResolvedValueOnce({
|
||||
organization: {
|
||||
membersWithRole: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
login: 'a',
|
||||
name: 'b',
|
||||
bio: 'c',
|
||||
email: 'd',
|
||||
avatarUrl: 'e',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
organization: {
|
||||
teams: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
slug: 'team',
|
||||
combinedSlug: 'blah/team',
|
||||
name: 'Team',
|
||||
description: 'The one and only team',
|
||||
avatarUrl: 'http://example.com/team.jpeg',
|
||||
parentTeam: {
|
||||
slug: 'parent',
|
||||
combinedSlug: '',
|
||||
members: { pageInfo: { hasNextPage: false }, nodes: [] },
|
||||
},
|
||||
members: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [{ login: 'a' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(graphql.defaults as jest.Mock).mockReturnValue(mockClient);
|
||||
entityProvider.connect(entityProviderConnection);
|
||||
|
||||
const event: EventParams = {
|
||||
@@ -966,109 +1025,68 @@ describe('GithubOrgEntityProvider', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const groupEntity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'new-team',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
members: ['user-1', 'githubuser'],
|
||||
},
|
||||
};
|
||||
|
||||
const userEntity: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'githubuser',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'github.com/user-login': 'githubuser',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['new-team'],
|
||||
},
|
||||
};
|
||||
|
||||
const expectedGroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'new-team',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/test-org/teams/new-team',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
children: [],
|
||||
members: ['user-1'],
|
||||
},
|
||||
};
|
||||
|
||||
const expectedUserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'githubuser',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/githubuser',
|
||||
'github.com/user-login': 'githubuser',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
},
|
||||
};
|
||||
|
||||
mockCatalogApi.getEntityByRef
|
||||
.mockResolvedValueOnce(cloneDeep(groupEntity))
|
||||
.mockResolvedValueOnce(cloneDeep(userEntity));
|
||||
|
||||
await entityProvider.onEvent(event);
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'delta',
|
||||
removed: [
|
||||
entities: [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/a',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/a',
|
||||
'github.com/user-login': 'a',
|
||||
},
|
||||
description: 'c',
|
||||
name: 'a',
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['team'],
|
||||
profile: {
|
||||
displayName: 'b',
|
||||
email: 'd',
|
||||
picture: 'e',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: groupEntity,
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://github.com/orgs/backstage/teams/team',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://github.com/orgs/backstage/teams/team',
|
||||
'github.com/team-slug': 'blah/team',
|
||||
},
|
||||
name: 'team',
|
||||
description: 'The one and only team',
|
||||
},
|
||||
spec: {
|
||||
children: [],
|
||||
parent: 'parent',
|
||||
profile: {
|
||||
displayName: 'Team',
|
||||
picture: 'http://example.com/team.jpeg',
|
||||
},
|
||||
type: 'team',
|
||||
members: ['a'],
|
||||
},
|
||||
},
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: userEntity,
|
||||
},
|
||||
],
|
||||
added: [
|
||||
{
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: expectedGroupEntity,
|
||||
},
|
||||
{
|
||||
locationKey: 'github-org-provider:my-id',
|
||||
entity: expectedUserEntity,
|
||||
},
|
||||
],
|
||||
type: 'full',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,10 +18,7 @@ import { TaskRunner } from '@backstage/backend-tasks';
|
||||
import {
|
||||
ANNOTATION_LOCATION,
|
||||
ANNOTATION_ORIGIN_LOCATION,
|
||||
DEFAULT_NAMESPACE,
|
||||
Entity,
|
||||
GroupEntity,
|
||||
UserEntity,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
@@ -43,12 +40,8 @@ import {
|
||||
OrganizationMemberAddedEvent,
|
||||
OrganizationMemberRemovedEvent,
|
||||
TeamEvent,
|
||||
TeamEditedEvent,
|
||||
MembershipEvent,
|
||||
} from '@octokit/webhooks-types';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
import { merge, omit } from 'lodash';
|
||||
import { merge } from 'lodash';
|
||||
import * as uuid from 'uuid';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
@@ -63,13 +56,9 @@ import {
|
||||
} from '../lib';
|
||||
import { TeamTransformer, UserTransformer } from '../lib';
|
||||
import {
|
||||
addUserToGroup,
|
||||
removeUserFromGroup,
|
||||
createAddEntitiesOperation,
|
||||
createRemoveEntitiesOperation,
|
||||
createReplaceEntitiesOperation,
|
||||
DeferredEntitiesBuilder,
|
||||
EntityUpdateOperation,
|
||||
} from '../lib/github';
|
||||
|
||||
/**
|
||||
@@ -126,9 +115,6 @@ export interface GithubOrgEntityProviderOptions {
|
||||
* Optionally include a team transformer for transforming from GitHub teams to Group Entities
|
||||
*/
|
||||
teamTransformer?: TeamTransformer;
|
||||
|
||||
catalogApi?: CatalogApi;
|
||||
tokenManager?: TokenManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,8 +129,6 @@ export class GithubOrgEntityProvider
|
||||
private connection?: EntityProviderConnection;
|
||||
private scheduleFn?: () => Promise<void>;
|
||||
|
||||
private eventConfigErrorThrown = false;
|
||||
|
||||
static fromConfig(config: Config, options: GithubOrgEntityProviderOptions) {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const gitHubConfig = integrations.github.byUrl(options.orgUrl)?.config;
|
||||
@@ -169,8 +153,6 @@ export class GithubOrgEntityProvider
|
||||
DefaultGithubCredentialsProvider.fromIntegrations(integrations),
|
||||
userTransformer: options.userTransformer,
|
||||
teamTransformer: options.teamTransformer,
|
||||
catalogApi: options.catalogApi,
|
||||
tokenManager: options.tokenManager,
|
||||
});
|
||||
|
||||
provider.schedule(options.schedule);
|
||||
@@ -187,8 +169,6 @@ export class GithubOrgEntityProvider
|
||||
githubCredentialsProvider?: GithubCredentialsProvider;
|
||||
userTransformer?: UserTransformer;
|
||||
teamTransformer?: TeamTransformer;
|
||||
catalogApi?: CatalogApi;
|
||||
tokenManager?: TokenManager;
|
||||
},
|
||||
) {
|
||||
this.credentialsProvider =
|
||||
@@ -266,11 +246,6 @@ export class GithubOrgEntityProvider
|
||||
const { logger } = this.options;
|
||||
logger.debug(`Received event from ${params.topic}`);
|
||||
|
||||
if (!this.canHandleEvents()) {
|
||||
logger.debug(`Skipping event ${params.topic}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const addEntitiesOperation = createAddEntitiesOperation(
|
||||
this.options.id,
|
||||
this.options.gitHubConfig.host,
|
||||
@@ -279,10 +254,6 @@ export class GithubOrgEntityProvider
|
||||
this.options.id,
|
||||
this.options.gitHubConfig.host,
|
||||
);
|
||||
const replaceEntitiesOperation = createReplaceEntitiesOperation(
|
||||
this.options.id,
|
||||
this.options.gitHubConfig.host,
|
||||
);
|
||||
|
||||
// handle change users in the org
|
||||
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#organization
|
||||
@@ -312,26 +283,14 @@ export class GithubOrgEntityProvider
|
||||
: removeEntitiesOperation;
|
||||
await this.onTeamChangeInOrganization(teamEvent, createDeltaOperation);
|
||||
} else if (teamEvent.action === 'edited') {
|
||||
await this.onTeamEditedInOrganization(
|
||||
teamEvent,
|
||||
replaceEntitiesOperation,
|
||||
);
|
||||
this.onEventToRebuildOrg();
|
||||
}
|
||||
}
|
||||
|
||||
// handle change membership in the org
|
||||
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#membership
|
||||
if (params.topic.includes('membership')) {
|
||||
const membershipEvent = params.eventPayload as MembershipEvent;
|
||||
const updateOperation =
|
||||
membershipEvent.action === 'added'
|
||||
? addUserToGroup
|
||||
: removeUserFromGroup;
|
||||
await this.onMemberChangeToTeam(
|
||||
membershipEvent,
|
||||
replaceEntitiesOperation,
|
||||
updateOperation,
|
||||
);
|
||||
this.onEventToRebuildOrg();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -342,22 +301,6 @@ export class GithubOrgEntityProvider
|
||||
return ['github.organization', 'github.team', 'github.membership'];
|
||||
}
|
||||
|
||||
private canHandleEvents(): boolean {
|
||||
if (this.options.catalogApi && this.options.tokenManager) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// throw only once
|
||||
if (!this.eventConfigErrorThrown) {
|
||||
this.eventConfigErrorThrown = true;
|
||||
throw new Error(
|
||||
`${this.getProviderName()} not well configured to handle ${this.supportsEventTopics()}. Missing CatalogApi and/or TokenManager.`,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async onTeamChangeInOrganization(
|
||||
event: TeamEvent,
|
||||
createDeltaOperation: DeferredEntitiesBuilder,
|
||||
@@ -412,71 +355,14 @@ export class GithubOrgEntityProvider
|
||||
});
|
||||
}
|
||||
|
||||
private async onTeamEditedInOrganization(
|
||||
event: TeamEditedEvent,
|
||||
createDeltaOperation: DeferredEntitiesBuilder,
|
||||
) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
const { name } = event.changes;
|
||||
const org = event.organization.login;
|
||||
|
||||
const { token } = await this.options.tokenManager!.getToken();
|
||||
const groups = await this.options.catalogApi!.getEntities(
|
||||
{
|
||||
filter: [
|
||||
{
|
||||
kind: 'Group',
|
||||
[`metadata.annotations.github.com/team-id`]: `${event.team.id}`,
|
||||
},
|
||||
],
|
||||
fields: ['apiVersion', 'kind', 'metadata', 'spec'],
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
|
||||
if (groups.items.length === 0) {
|
||||
this.options.logger.debug(
|
||||
`Skipping event because couldn't found group with 'github.com/node-id':${event.team.node_id}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const group = groups.items[0] as GroupEntity;
|
||||
const { removed } = createDeltaOperation(org, [group]);
|
||||
const { name: newTeamName, html_url: url, description, slug } = event.team;
|
||||
|
||||
if (name) {
|
||||
merge(group, {
|
||||
metadata: {
|
||||
name: slug,
|
||||
annotations: {
|
||||
[`backstage.io/edit-url`]: `${url}/edit`,
|
||||
[`github.com/team-slug`]: `${org}/${slug}`,
|
||||
[ANNOTATION_LOCATION]: `url:${url}`,
|
||||
[ANNOTATION_ORIGIN_LOCATION]: `url:${url}`,
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
profile: {
|
||||
displayName: newTeamName,
|
||||
},
|
||||
},
|
||||
private onEventToRebuildOrg() {
|
||||
try {
|
||||
this.read(this.options).then(() => {
|
||||
this.options.logger.debug('Event process finished');
|
||||
});
|
||||
} catch (e) {
|
||||
this.options.logger.debug(`Event process error ${e}`);
|
||||
}
|
||||
|
||||
if (description) {
|
||||
group.metadata.description = description;
|
||||
}
|
||||
|
||||
const { added } = createDeltaOperation(org, [group]);
|
||||
await this.connection.applyMutation({
|
||||
type: 'delta',
|
||||
removed,
|
||||
added,
|
||||
});
|
||||
}
|
||||
|
||||
private async onMemberChangeInOrganization(
|
||||
@@ -523,73 +409,6 @@ export class GithubOrgEntityProvider
|
||||
});
|
||||
}
|
||||
|
||||
private async onMemberChangeToTeam(
|
||||
event: MembershipEvent,
|
||||
createDeltaOperation: DeferredEntitiesBuilder,
|
||||
updateEntities: EntityUpdateOperation,
|
||||
) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
// The docs are saying I will receive the slug for the removed event, but the types don't reflect that,
|
||||
// so I will just check to be sure the slug is there
|
||||
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#membership
|
||||
if (!('slug' in event.team)) {
|
||||
return;
|
||||
}
|
||||
const teamSlug = event.team.slug;
|
||||
const userSlug = event.member.login;
|
||||
|
||||
const { token } = await this.options.tokenManager!.getToken();
|
||||
const org = event.organization.login;
|
||||
let group = (await this.options.catalogApi!.getEntityByRef(
|
||||
{
|
||||
kind: 'Group',
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
name: teamSlug,
|
||||
},
|
||||
{ token },
|
||||
)) as GroupEntity;
|
||||
|
||||
if (!group) {
|
||||
this.options.logger.debug(
|
||||
`Skipping event because couldn't found group ':${teamSlug} for namespace ${DEFAULT_NAMESPACE}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let user = (await this.options.catalogApi!.getEntityByRef(
|
||||
{
|
||||
kind: 'User',
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
name: userSlug,
|
||||
},
|
||||
{ token },
|
||||
)) as UserEntity;
|
||||
|
||||
group = omit(group, 'relations');
|
||||
|
||||
if (!user) {
|
||||
this.options.logger.debug(
|
||||
`Skipping event because couldn't found user ':${userSlug} for namespace ${DEFAULT_NAMESPACE}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
user = omit(user, 'relations');
|
||||
|
||||
const { removed } = createDeltaOperation(org, [group, user]);
|
||||
|
||||
const result = updateEntities(user, group);
|
||||
|
||||
const { added } = createDeltaOperation(org, [result.group, result.user]);
|
||||
await this.connection.applyMutation({
|
||||
type: 'delta',
|
||||
removed,
|
||||
added,
|
||||
});
|
||||
}
|
||||
|
||||
private schedule(schedule: GithubOrgEntityProviderOptions['schedule']) {
|
||||
if (!schedule || schedule === 'manual') {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user