Change imput param keys to camelCase. Fetch entityrefs from catalog and get users and teams from github for env reviewers.
Signed-off-by: Raghunandan Balachandran <raghunandan@spotify.com>
This commit is contained in:
@@ -44,9 +44,11 @@
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/catalog-client": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/integration": "workspace:^",
|
||||
"@backstage/plugin-catalog-node": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-node": "workspace:^",
|
||||
"@octokit/webhooks": "^10.0.0",
|
||||
"libsodium-wrappers": "^0.7.11",
|
||||
|
||||
@@ -328,7 +328,7 @@ export const examples: TemplateExample[] = [
|
||||
input: {
|
||||
repoUrl: 'github.com?repo=repository&owner=owner',
|
||||
name: 'envname',
|
||||
prevent_self_review: true,
|
||||
preventSelfReview: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -344,16 +344,7 @@ export const examples: TemplateExample[] = [
|
||||
input: {
|
||||
repoUrl: 'github.com?repo=repository&owner=owner',
|
||||
name: 'envname',
|
||||
reviewers: [
|
||||
{
|
||||
Type: 'User',
|
||||
ID: 1,
|
||||
},
|
||||
{
|
||||
Type: 'Team',
|
||||
ID: 2,
|
||||
},
|
||||
],
|
||||
reviewers: ['group:defautl/team-a', 'user:defautl/johndoe'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
+2
-2
@@ -909,11 +909,11 @@ describe('github:environment:create examples', () => {
|
||||
wait_timer: 0,
|
||||
reviewers: [
|
||||
{
|
||||
Type: 'User',
|
||||
Type: 'Team',
|
||||
ID: 1,
|
||||
},
|
||||
{
|
||||
Type: 'Team',
|
||||
Type: 'User',
|
||||
ID: 2,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -19,6 +19,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
|
||||
const mockOctokit = {
|
||||
rest: {
|
||||
@@ -32,8 +33,19 @@ const mockOctokit = {
|
||||
createOrUpdateEnvironment: jest.fn(),
|
||||
get: jest.fn(),
|
||||
},
|
||||
teams: {
|
||||
getByName: jest.fn(),
|
||||
},
|
||||
users: {
|
||||
getByUsername: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockCatalogClient: Partial<CatalogClient> = {
|
||||
getEntitiesByRefs: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('octokit', () => ({
|
||||
Octokit: class {
|
||||
constructor() {
|
||||
@@ -42,6 +54,10 @@ jest.mock('octokit', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@backstage/catalog-client', () => ({
|
||||
CatalogClient: mockCatalogClient,
|
||||
}));
|
||||
|
||||
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
|
||||
|
||||
describe('github:environment:create', () => {
|
||||
@@ -76,9 +92,36 @@ describe('github:environment:create', () => {
|
||||
id: 'repoid',
|
||||
},
|
||||
});
|
||||
mockOctokit.rest.users.getByUsername.mockResolvedValue({
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
});
|
||||
mockOctokit.rest.teams.getByName.mockResolvedValue({
|
||||
data: {
|
||||
id: 2,
|
||||
},
|
||||
});
|
||||
(mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'johndoe',
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'team-a',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
action = createGithubEnvironmentAction({
|
||||
integrations,
|
||||
catalog: mockCatalogClient as unknown as CatalogClient,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -340,7 +383,7 @@ describe('github:environment:create', () => {
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
wait_timer: 1000,
|
||||
waitTimer: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -357,12 +400,12 @@ describe('github:environment:create', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with prevent_self_review set to true', async () => {
|
||||
it('should work with preventSelfReview set to true', async () => {
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
prevent_self_review: true,
|
||||
preventSelfReview: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -379,12 +422,12 @@ describe('github:environment:create', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with prevent_self_review set to false', async () => {
|
||||
it('should work with preventSelfReview set to false', async () => {
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
prevent_self_review: false,
|
||||
preventSelfReview: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -406,16 +449,7 @@ describe('github:environment:create', () => {
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
reviewers: [
|
||||
{
|
||||
Type: 'User',
|
||||
ID: 1,
|
||||
},
|
||||
{
|
||||
Type: 'Team',
|
||||
ID: 2,
|
||||
},
|
||||
],
|
||||
reviewers: ['group:defautl/team-a', 'user:defautl/johndoe'],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -430,12 +464,12 @@ describe('github:environment:create', () => {
|
||||
prevent_self_review: false,
|
||||
reviewers: [
|
||||
{
|
||||
Type: 'User',
|
||||
ID: 1,
|
||||
id: 1,
|
||||
type: 'User',
|
||||
},
|
||||
{
|
||||
Type: 'Team',
|
||||
ID: 2,
|
||||
id: 2,
|
||||
type: 'Team',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ import { getOctokitOptions } from './helpers';
|
||||
import { Octokit } from 'octokit';
|
||||
import Sodium from 'libsodium-wrappers';
|
||||
import { examples } from './gitHubEnvironment.examples';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
|
||||
/**
|
||||
* Creates an `github:environment:create` Scaffolder action that creates a Github Environment.
|
||||
@@ -32,8 +33,9 @@ import { examples } from './gitHubEnvironment.examples';
|
||||
*/
|
||||
export function createGithubEnvironmentAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
catalog: CatalogClient;
|
||||
}) {
|
||||
const { integrations } = options;
|
||||
const { integrations, catalog } = options;
|
||||
// For more information on how to define custom actions, see
|
||||
// https://backstage.io/docs/features/software-templates/writing-custom-actions
|
||||
return createTemplateAction<{
|
||||
@@ -48,9 +50,9 @@ export function createGithubEnvironmentAction(options: {
|
||||
environmentVariables?: { [key: string]: string };
|
||||
secrets?: { [key: string]: string };
|
||||
token?: string;
|
||||
wait_timer?: number;
|
||||
prevent_self_review?: boolean;
|
||||
reviewers?: Array<{ type?: 'User' | 'Team'; id?: number }> | null;
|
||||
waitTimer?: number;
|
||||
preventSelfReview?: boolean;
|
||||
reviewers?: string[];
|
||||
}>({
|
||||
id: 'github:environment:create',
|
||||
description: 'Creates Deployment Environments',
|
||||
@@ -123,13 +125,13 @@ export function createGithubEnvironmentAction(options: {
|
||||
type: 'string',
|
||||
description: 'The token to use for authorization to GitHub',
|
||||
},
|
||||
wait_timer: {
|
||||
waitTimer: {
|
||||
title: 'Wait Timer',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The time to wait before creating or updating the environment (in milliseconds)',
|
||||
},
|
||||
prevent_self_review: {
|
||||
preventSelfReview: {
|
||||
title: 'Prevent Self Review',
|
||||
type: 'boolean',
|
||||
description: 'Whether to prevent self-review for this environment',
|
||||
@@ -139,17 +141,7 @@ export function createGithubEnvironmentAction(options: {
|
||||
type: 'array',
|
||||
description: 'Reviewers for this environment',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
title: 'Type',
|
||||
type: 'string',
|
||||
},
|
||||
id: {
|
||||
title: 'ID',
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -165,15 +157,14 @@ export function createGithubEnvironmentAction(options: {
|
||||
environmentVariables,
|
||||
secrets,
|
||||
token: providedToken,
|
||||
wait_timer,
|
||||
prevent_self_review,
|
||||
waitTimer,
|
||||
preventSelfReview,
|
||||
reviewers,
|
||||
} = ctx.input;
|
||||
|
||||
// Wait for the specified time before creating or updating the environment
|
||||
if (wait_timer) {
|
||||
await new Promise(resolve => setTimeout(resolve, wait_timer));
|
||||
}
|
||||
// When environment creation step is executed right after a repo publish step, the repository might not be available immediately.
|
||||
// Add a 2-second delay before initiating the steps in this action.
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
const octokitOptions = await getOctokitOptions({
|
||||
integrations,
|
||||
@@ -193,14 +184,51 @@ export function createGithubEnvironmentAction(options: {
|
||||
repo: repo,
|
||||
});
|
||||
|
||||
// convert reviewers from catalog entity to Github user or team
|
||||
const githubReviewers: { type: 'User' | 'Team'; id: number }[] = [];
|
||||
if (reviewers) {
|
||||
// Fetch reviewers from Catalog
|
||||
const { items: reviewersEntityRefs } = await catalog.getEntitiesByRefs({
|
||||
entityRefs: reviewers,
|
||||
});
|
||||
for (const reviewerEntityRef of reviewersEntityRefs) {
|
||||
if (reviewerEntityRef?.kind === 'User') {
|
||||
try {
|
||||
const user = await client.rest.users.getByUsername({
|
||||
username: reviewerEntityRef.metadata.name,
|
||||
});
|
||||
githubReviewers.push({
|
||||
type: 'User',
|
||||
id: user.data.id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('User not found:', error);
|
||||
}
|
||||
} else if (reviewerEntityRef?.kind === 'Group') {
|
||||
try {
|
||||
const team = await client.rest.teams.getByName({
|
||||
org: owner,
|
||||
team_slug: reviewerEntityRef.metadata.name,
|
||||
});
|
||||
githubReviewers.push({
|
||||
type: 'Team',
|
||||
id: team.data.id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Team not found:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await client.rest.repos.createOrUpdateEnvironment({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
environment_name: name,
|
||||
deployment_branch_policy: deploymentBranchPolicy ?? null,
|
||||
wait_timer: wait_timer ?? 0,
|
||||
prevent_self_review: prevent_self_review ?? false,
|
||||
reviewers: reviewers ?? null,
|
||||
wait_timer: waitTimer ?? 0,
|
||||
prevent_self_review: preventSelfReview ?? false,
|
||||
reviewers: githubReviewers.length ? githubReviewers : null,
|
||||
});
|
||||
|
||||
if (customBranchPolicyNames) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
DefaultGithubCredentialsProvider,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -48,8 +49,9 @@ export const githubModule = createBackendModule({
|
||||
deps: {
|
||||
scaffolder: scaffolderActionsExtensionPoint,
|
||||
config: coreServices.rootConfig,
|
||||
catalog: catalogServiceRef,
|
||||
},
|
||||
async init({ scaffolder, config }) {
|
||||
async init({ scaffolder, config, catalog }) {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const githubCredentialsProvider =
|
||||
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
|
||||
@@ -68,6 +70,7 @@ export const githubModule = createBackendModule({
|
||||
}),
|
||||
createGithubEnvironmentAction({
|
||||
integrations,
|
||||
catalog,
|
||||
}),
|
||||
createGithubIssuesLabelAction({
|
||||
integrations,
|
||||
|
||||
Reference in New Issue
Block a user