Add repository variables and secrets

Signed-off-by: Andrew Ochsner <andrew.ochsner@cognizant.com>
This commit is contained in:
Andrew Ochsner
2023-05-22 12:35:17 -05:00
parent 41d2fb07df
commit df8411779d
10 changed files with 346 additions and 107 deletions
+20
View File
@@ -308,6 +308,16 @@ export function createGithubRepoCreateAction(options: {
hasIssues?: boolean | undefined;
token?: string | undefined;
topics?: string[] | undefined;
repoVariables?:
| {
[key: string]: string;
}
| undefined;
secrets?:
| {
[key: string]: string;
}
| undefined;
requireCommitSigning?: boolean | undefined;
},
JsonObject
@@ -552,6 +562,16 @@ export function createPublishGithubAction(options: {
hasIssues?: boolean | undefined;
token?: string | undefined;
topics?: string[] | undefined;
repoVariables?:
| {
[key: string]: string;
}
| undefined;
secrets?:
| {
[key: string]: string;
}
| undefined;
requiredCommitSigning?: boolean | undefined;
},
JsonObject
+3 -1
View File
@@ -81,12 +81,13 @@
"isomorphic-git": "^1.23.0",
"jsonschema": "^1.2.6",
"knex": "^2.0.0",
"libsodium-wrappers": "^0.7.11",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
"morgan": "^1.10.0",
"node-fetch": "^2.6.7",
"nunjucks": "^3.2.3",
"octokit": "^2.0.0",
"octokit": "^2.0.3",
"octokit-plugin-create-pull-request": "^3.10.0",
"p-limit": "^3.1.0",
"p-queue": "^6.6.2",
@@ -104,6 +105,7 @@
"@types/command-exists": "^1.2.0",
"@types/fs-extra": "^9.0.1",
"@types/git-url-parse": "^9.0.0",
"@types/libsodium-wrappers": "^0.7.10",
"@types/mock-fs": "^4.13.0",
"@types/nunjucks": "^3.1.4",
"@types/supertest": "^2.0.8",
@@ -30,6 +30,8 @@ import { PassThrough } from 'stream';
import { createGithubRepoCreateAction } from './githubRepoCreate';
import { entityRefToName } from '../helpers';
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
const mockOctokit = {
rest: {
users: {
@@ -45,6 +47,11 @@ const mockOctokit = {
addOrUpdateRepoPermissionsInOrg: jest.fn(),
getByName: jest.fn(),
},
actions: {
createRepoVariable: jest.fn(),
createOrUpdateRepoSecret: jest.fn(),
getRepoPublicKey: jest.fn(),
},
},
};
jest.mock('octokit', () => ({
@@ -91,6 +98,12 @@ describe('github:repo:create', () => {
githubCredentialsProvider,
});
(entityRefToName as jest.Mock).mockImplementation((s: string) => s);
mockOctokit.rest.actions.getRepoPublicKey.mockResolvedValue({
data: {
key: publicKey,
key_id: 'keyid',
},
});
});
afterEach(jest.resetAllMocks);
@@ -571,6 +584,69 @@ describe('github:repo:create', () => {
});
});
it('should add variables when provided', async () => {
mockOctokit.rest.users.getByUsername.mockResolvedValue({
data: { type: 'User' },
});
mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({
data: {
clone_url: 'https://github.com/clone/url.git',
html_url: 'https://github.com/html/url',
},
});
await action.handler({
...mockContext,
input: {
...mockContext.input,
repoVariables: {
foo: 'bar',
},
},
});
expect(mockOctokit.rest.actions.createRepoVariable).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
name: 'foo',
value: 'bar',
});
});
it('should add secrets when provided', async () => {
mockOctokit.rest.users.getByUsername.mockResolvedValue({
data: { type: 'User' },
});
mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({
data: {
clone_url: 'https://github.com/clone/url.git',
html_url: 'https://github.com/html/url',
},
});
await action.handler({
...mockContext,
input: {
...mockContext.input,
secrets: {
foo: 'bar',
},
},
});
expect(
mockOctokit.rest.actions.createOrUpdateRepoSecret,
).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
secret_name: 'foo',
key_id: 'keyid',
encrypted_value: expect.any(String),
});
});
it('should call output with the remoteUrl', async () => {
mockOctokit.rest.users.getByUsername.mockResolvedValue({
data: { type: 'User' },
@@ -90,6 +90,8 @@ export function createGithubRepoCreateAction(options: {
hasIssues?: boolean;
token?: string;
topics?: string[];
repoVariables?: { [key: string]: string };
secrets?: { [key: string]: string };
requireCommitSigning?: boolean;
}>({
id: 'github:repo:create',
@@ -125,6 +127,8 @@ export function createGithubRepoCreateAction(options: {
hasIssues: inputProps.hasIssues,
token: inputProps.token,
topics: inputProps.topics,
repoVariables: inputProps.repoVariables,
secrets: inputProps.secrets,
requiredCommitSigning: inputProps.requiredCommitSigning,
},
},
@@ -155,6 +159,8 @@ export function createGithubRepoCreateAction(options: {
hasWiki = undefined,
hasIssues = undefined,
topics,
repoVariables,
secrets,
token: providedToken,
} = ctx.input;
@@ -192,6 +198,8 @@ export function createGithubRepoCreateAction(options: {
hasWiki,
hasIssues,
topics,
repoVariables,
secrets,
ctx.logger,
);
@@ -30,6 +30,7 @@ import {
} from '../helpers';
import { getRepoSourceDirectory, parseRepoUrl } from '../publish/util';
import { entityRefToName } from '../../builtin/helpers';
import Sodium from 'libsodium-wrappers';
const DEFAULT_TIMEOUT_MS = 60_000;
@@ -129,6 +130,8 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
hasWiki: boolean | undefined,
hasIssues: boolean | undefined,
topics: string[] | undefined,
repoVariables: { [key: string]: string } | undefined,
secrets: { [key: string]: string } | undefined,
logger: Logger,
) {
// eslint-disable-next-line testing-library/no-await-sync-query
@@ -146,6 +149,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
name: repo,
org: owner,
private: repoVisibility === 'private',
// @ts-ignore
visibility: repoVisibility,
description: description,
delete_branch_on_merge: deleteBranchOnMerge,
@@ -254,6 +258,47 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
}
}
for (const [key, value] of Object.entries(repoVariables ?? {})) {
await client.rest.actions.createRepoVariable({
owner,
repo,
name: key,
value: value,
});
}
if (secrets) {
const publicKeyResponse = await client.rest.actions.getRepoPublicKey({
owner,
repo,
});
await Sodium.ready;
const binaryKey = Sodium.from_base64(
publicKeyResponse.data.key,
Sodium.base64_variants.ORIGINAL,
);
for (const [key, value] of Object.entries(secrets)) {
const binarySecret = Sodium.from_string(value);
const encryptedBinarySecret = Sodium.crypto_box_seal(
binarySecret,
binaryKey,
);
const encryptedBase64Secret = Sodium.to_base64(
encryptedBinarySecret,
Sodium.base64_variants.ORIGINAL,
);
await client.rest.actions.createOrUpdateRepoSecret({
owner,
repo,
secret_name: key,
encrypted_value: encryptedBase64Secret,
key_id: publicKeyResponse.data.key_id,
});
}
}
return newRepo;
}
@@ -265,6 +265,18 @@ const requiredCommitSigning = {
description: `Require commit signing so that you must sign commits on this branch.`,
};
const repoVariables = {
title: 'Repository Variables',
description: `Variables attached to the repository`,
type: 'object',
};
const secrets = {
title: 'Repository Secrets',
description: `Secrets attached to the repository`,
type: 'object',
};
export { access };
export { allowMergeCommit };
export { allowRebaseMerge };
@@ -299,3 +311,5 @@ export { sourcePath };
export { token };
export { topics };
export { requiredCommitSigning };
export { repoVariables };
export { secrets };
@@ -32,6 +32,8 @@ import {
} from '../helpers';
import { createPublishGithubAction } from './github';
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
const initRepoAndPushMocked = initRepoAndPush as jest.Mock<
Promise<{ commitHash: string }>
>;
@@ -51,6 +53,11 @@ const mockOctokit = {
getByName: jest.fn(),
addOrUpdateRepoPermissionsInOrg: jest.fn(),
},
actions: {
createRepoVariable: jest.fn(),
createOrUpdateRepoSecret: jest.fn(),
getRepoPublicKey: jest.fn(),
},
},
};
jest.mock('octokit', () => ({
@@ -107,6 +114,12 @@ describe('publish:github', () => {
(entityRefToName as jest.Mock).mockImplementation(
realFamiliarizeEntityName,
);
mockOctokit.rest.actions.getRepoPublicKey.mockResolvedValue({
data: {
key: publicKey,
key_id: 'keyid',
},
});
});
afterEach(jest.resetAllMocks);
@@ -836,6 +849,69 @@ describe('publish:github', () => {
});
});
it('should add variables when provided', async () => {
mockOctokit.rest.users.getByUsername.mockResolvedValue({
data: { type: 'User' },
});
mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({
data: {
clone_url: 'https://github.com/clone/url.git',
html_url: 'https://github.com/html/url',
},
});
await action.handler({
...mockContext,
input: {
...mockContext.input,
repoVariables: {
foo: 'bar',
},
},
});
expect(mockOctokit.rest.actions.createRepoVariable).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
name: 'foo',
value: 'bar',
});
});
it('should add secrets when provided', async () => {
mockOctokit.rest.users.getByUsername.mockResolvedValue({
data: { type: 'User' },
});
mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({
data: {
clone_url: 'https://github.com/clone/url.git',
html_url: 'https://github.com/html/url',
},
});
await action.handler({
...mockContext,
input: {
...mockContext.input,
secrets: {
foo: 'bar',
},
},
});
expect(
mockOctokit.rest.actions.createOrUpdateRepoSecret,
).toHaveBeenCalledWith({
owner: 'owner',
repo: 'repo',
secret_name: 'foo',
key_id: 'keyid',
encrypted_value: expect.any(String),
});
});
it('should call output with the remoteUrl and the repoContentsUrl', async () => {
mockOctokit.rest.users.getByUsername.mockResolvedValue({
data: { type: 'User' },
@@ -104,6 +104,8 @@ export function createPublishGithubAction(options: {
hasIssues?: boolean | undefined;
token?: string;
topics?: string[];
repoVariables?: { [key: string]: string };
secrets?: { [key: string]: string };
requiredCommitSigning?: boolean;
}>({
id: 'publish:github',
@@ -148,6 +150,8 @@ export function createPublishGithubAction(options: {
hasIssues: inputProps.hasIssues,
token: inputProps.token,
topics: inputProps.topics,
repoVariables: inputProps.repoVariables,
secrets: inputProps.secrets,
requiredCommitSigning: inputProps.requiredCommitSigning,
},
},
@@ -193,6 +197,8 @@ export function createPublishGithubAction(options: {
hasWiki = undefined,
hasIssues = undefined,
topics,
repoVariables,
secrets,
token: providedToken,
requiredCommitSigning = false,
} = ctx.input;
@@ -231,6 +237,8 @@ export function createPublishGithubAction(options: {
hasWiki,
hasIssues,
topics,
repoVariables,
secrets,
ctx.logger,
);