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:
Raghunandan Balachandran
2024-09-15 23:23:43 +02:00
parent ce0d10cb48
commit d0ea1748aa
6 changed files with 117 additions and 59 deletions
@@ -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'],
},
},
],
@@ -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,