add github:environment:create and github:deployKey:create
Signed-off-by: Andrew Ochsner <andrew.ochsner@cognizant.com>
This commit is contained in:
@@ -42,6 +42,8 @@ import {
|
||||
} from './filesystem';
|
||||
import {
|
||||
createGithubActionsDispatchAction,
|
||||
createGithubDeployKeyAction,
|
||||
createGithubEnvironmentAction,
|
||||
createGithubIssuesLabelAction,
|
||||
createGithubRepoCreateAction,
|
||||
createGithubRepoPushAction,
|
||||
@@ -194,6 +196,12 @@ export const createBuiltinActions = (
|
||||
config,
|
||||
githubCredentialsProvider,
|
||||
}),
|
||||
createGithubEnvironmentAction({
|
||||
integrations,
|
||||
}),
|
||||
createGithubDeployKeyAction({
|
||||
integrations,
|
||||
}),
|
||||
];
|
||||
|
||||
return actions as TemplateAction[];
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PassThrough } from 'stream';
|
||||
import { createGithubDeployKeyAction } from './githubDeployKey';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
|
||||
const mockOctokit = {
|
||||
rest: {
|
||||
actions: {
|
||||
getRepoPublicKey: jest.fn(),
|
||||
createOrUpdateRepoSecret: jest.fn(),
|
||||
},
|
||||
repos: {
|
||||
createDeployKey: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
jest.mock('octokit', () => ({
|
||||
Octokit: class {
|
||||
constructor() {
|
||||
return mockOctokit;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
|
||||
|
||||
describe('github:deployKey:create', () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
{ host: 'github.com', token: 'tokenlols' },
|
||||
{ host: 'ghe.github.com' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
let action: TemplateAction<any>;
|
||||
|
||||
const mockContext = {
|
||||
input: {
|
||||
repoUrl: 'github.com?repo=repository&owner=owner',
|
||||
publicKey: 'pubkey',
|
||||
privateKey: 'privkey',
|
||||
deployKeyName: 'Push Tags',
|
||||
},
|
||||
workspacePath: 'lol',
|
||||
logger: getVoidLogger(),
|
||||
logStream: new PassThrough(),
|
||||
output: jest.fn(),
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
action = createGithubDeployKeyAction({
|
||||
integrations,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work happy path', async () => {
|
||||
mockOctokit.rest.actions.getRepoPublicKey.mockResolvedValue({
|
||||
data: {
|
||||
key: publicKey,
|
||||
key_id: 'keyid',
|
||||
},
|
||||
});
|
||||
|
||||
await action.handler(mockContext);
|
||||
|
||||
expect(mockOctokit.rest.repos.createDeployKey).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
title: 'Push Tags',
|
||||
key: 'pubkey',
|
||||
});
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.actions.createOrUpdateRepoSecret,
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
secret_name: 'PUSH_TAGS_PRIVATE_KEY',
|
||||
key_id: 'keyid',
|
||||
encrypted_value: expect.any(String),
|
||||
});
|
||||
|
||||
expect(mockContext.output).toHaveBeenCalledWith(
|
||||
'privateKeySecretName',
|
||||
'PUSH_TAGS_PRIVATE_KEY',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { parseRepoUrl } from '../publish/util';
|
||||
import { getOctokitOptions } from './helpers';
|
||||
import { Octokit } from 'octokit';
|
||||
import Sodium from 'libsodium-wrappers';
|
||||
|
||||
/**
|
||||
* Creates an `github:deployKey:create` Scaffolder action that creates a Deploy Key
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createGithubDeployKeyAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) {
|
||||
const { integrations } = options;
|
||||
// For more information on how to define custom actions, see
|
||||
// https://backstage.io/docs/features/software-templates/writing-custom-actions
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
deployKeyName: string;
|
||||
privateKeySecretName?: string;
|
||||
token?: string;
|
||||
}>({
|
||||
id: 'github:deployKey:create',
|
||||
description: 'Creates and stores Deploy Keys',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['repoUrl', 'publicKey', 'privateKey', 'deployKeyName'],
|
||||
properties: {
|
||||
repoUrl: {
|
||||
title: 'Repository Location',
|
||||
description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
|
||||
type: 'string',
|
||||
},
|
||||
publicKey: {
|
||||
title: 'SSH Public Key',
|
||||
description: `Generated from ssh-keygen. Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'.`,
|
||||
type: 'string',
|
||||
},
|
||||
privateKey: {
|
||||
title: 'SSH Private Key',
|
||||
description: `SSH Private Key generated from ssh-keygen`,
|
||||
type: 'string',
|
||||
},
|
||||
deployKeyName: {
|
||||
title: 'Deploy Key Name',
|
||||
description: `Name of the Deploy Key`,
|
||||
type: 'string',
|
||||
},
|
||||
privateKeySecretName: {
|
||||
title: 'Private Key GitHub Secret Name',
|
||||
description: `Name of the GitHub Secret to store the private key related to the Deploy Key. Defaults to: 'KEY_NAME_PRIVATE_KEY' where 'KEY_NAME' is the name of the Deploy Key`,
|
||||
type: 'string',
|
||||
},
|
||||
token: {
|
||||
title: 'Authentication Token',
|
||||
type: 'string',
|
||||
description: 'The token to use for authorization to GitHub',
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
privateKeySecretName: {
|
||||
title: 'The GitHub Action Repo Secret Name for the Private Key',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const {
|
||||
repoUrl,
|
||||
publicKey,
|
||||
privateKey,
|
||||
deployKeyName,
|
||||
privateKeySecretName = `${deployKeyName
|
||||
.split(' ')
|
||||
.join('_')
|
||||
.toLocaleUpperCase('en-US')}_PRIVATE_KEY`,
|
||||
token: providedToken,
|
||||
} = ctx.input;
|
||||
|
||||
const octokitOptions = await getOctokitOptions({
|
||||
integrations,
|
||||
token: providedToken,
|
||||
repoUrl: repoUrl,
|
||||
});
|
||||
|
||||
const { owner, repo } = parseRepoUrl(repoUrl, integrations);
|
||||
|
||||
if (!owner) {
|
||||
throw new InputError(`No owner provided for repo ${repoUrl}`);
|
||||
}
|
||||
|
||||
const client = new Octokit(octokitOptions);
|
||||
|
||||
await client.rest.repos.createDeployKey({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
title: deployKeyName,
|
||||
key: publicKey,
|
||||
});
|
||||
const publicKeyResponse = await client.rest.actions.getRepoPublicKey({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
});
|
||||
|
||||
await Sodium.ready;
|
||||
const binaryKey = Sodium.from_base64(
|
||||
publicKeyResponse.data.key,
|
||||
Sodium.base64_variants.ORIGINAL,
|
||||
);
|
||||
const binarySecret = Sodium.from_string(privateKey);
|
||||
const encryptedBinarySecret = Sodium.crypto_box_seal(
|
||||
binarySecret,
|
||||
binaryKey,
|
||||
);
|
||||
const encryptedBase64Secret = Sodium.to_base64(
|
||||
encryptedBinarySecret,
|
||||
Sodium.base64_variants.ORIGINAL,
|
||||
);
|
||||
|
||||
await client.rest.actions.createOrUpdateRepoSecret({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
secret_name: privateKeySecretName,
|
||||
encrypted_value: encryptedBase64Secret,
|
||||
key_id: publicKeyResponse.data.key_id,
|
||||
});
|
||||
|
||||
ctx.output('privateKeySecretName', privateKeySecretName);
|
||||
},
|
||||
});
|
||||
}
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PassThrough } from 'stream';
|
||||
import { createGithubEnvironmentAction } from './githubEnvironment';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
|
||||
const mockOctokit = {
|
||||
rest: {
|
||||
actions: {
|
||||
getRepoPublicKey: jest.fn(),
|
||||
createEnvironmentVariable: jest.fn(),
|
||||
createOrUpdateEnvironmentSecret: jest.fn(),
|
||||
},
|
||||
repos: {
|
||||
createDeploymentBranchPolicy: jest.fn(),
|
||||
createOrUpdateEnvironment: jest.fn(),
|
||||
get: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
jest.mock('octokit', () => ({
|
||||
Octokit: class {
|
||||
constructor() {
|
||||
return mockOctokit;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
|
||||
|
||||
describe('github:environment:create', () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
{ host: 'github.com', token: 'tokenlols' },
|
||||
{ host: 'ghe.github.com' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
let action: TemplateAction<any>;
|
||||
|
||||
const mockContext = {
|
||||
input: {
|
||||
repoUrl: 'github.com?repo=repository&owner=owner',
|
||||
name: 'envname',
|
||||
},
|
||||
workspacePath: 'lol',
|
||||
logger: getVoidLogger(),
|
||||
logStream: new PassThrough(),
|
||||
output: jest.fn(),
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockOctokit.rest.actions.getRepoPublicKey.mockResolvedValue({
|
||||
data: {
|
||||
key: publicKey,
|
||||
key_id: 'keyid',
|
||||
},
|
||||
});
|
||||
mockOctokit.rest.repos.get.mockResolvedValue({
|
||||
data: {
|
||||
id: 'repoid',
|
||||
},
|
||||
});
|
||||
|
||||
action = createGithubEnvironmentAction({
|
||||
integrations,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(jest.resetAllMocks);
|
||||
|
||||
it('should work happy path', async () => {
|
||||
await action.handler(mockContext);
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.repos.createOrUpdateEnvironment,
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
environment_name: 'envname',
|
||||
deployment_branch_policy: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work specify deploymentBranchPolicy protected', async () => {
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
deploymentBranchPolicy: {
|
||||
protected_branches: true,
|
||||
custom_branch_policies: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.repos.createOrUpdateEnvironment,
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
environment_name: 'envname',
|
||||
deployment_branch_policy: {
|
||||
protected_branches: true,
|
||||
custom_branch_policies: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should work specify deploymentBranchPolicy custom', async () => {
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
deploymentBranchPolicy: {
|
||||
protected_branches: false,
|
||||
custom_branch_policies: true,
|
||||
},
|
||||
customBranchPolicyNames: ['main', '*.*.*'],
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.repos.createOrUpdateEnvironment,
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
environment_name: 'envname',
|
||||
deployment_branch_policy: {
|
||||
protected_branches: false,
|
||||
custom_branch_policies: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.repos.createDeploymentBranchPolicy,
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
environment_name: 'envname',
|
||||
name: 'main',
|
||||
});
|
||||
expect(
|
||||
mockOctokit.rest.repos.createDeploymentBranchPolicy,
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
environment_name: 'envname',
|
||||
name: '*.*.*',
|
||||
});
|
||||
});
|
||||
|
||||
it('should work specify environment variables', async () => {
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
environmentVariables: {
|
||||
key1: 'val1',
|
||||
key2: 'val2',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.repos.createOrUpdateEnvironment,
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
environment_name: 'envname',
|
||||
deployment_branch_policy: null,
|
||||
});
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.actions.createEnvironmentVariable,
|
||||
).toHaveBeenCalledTimes(2);
|
||||
expect(
|
||||
mockOctokit.rest.actions.createEnvironmentVariable,
|
||||
).toHaveBeenCalledWith({
|
||||
repository_id: 'repoid',
|
||||
environment_name: 'envname',
|
||||
name: 'key1',
|
||||
value: 'val1',
|
||||
});
|
||||
expect(
|
||||
mockOctokit.rest.actions.createEnvironmentVariable,
|
||||
).toHaveBeenCalledWith({
|
||||
repository_id: 'repoid',
|
||||
environment_name: 'envname',
|
||||
name: 'key2',
|
||||
value: 'val2',
|
||||
});
|
||||
});
|
||||
|
||||
it('should work specify secrets', async () => {
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
secrets: {
|
||||
key1: 'val1',
|
||||
key2: 'val2',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.repos.createOrUpdateEnvironment,
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repository',
|
||||
environment_name: 'envname',
|
||||
deployment_branch_policy: null,
|
||||
});
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.actions.createOrUpdateEnvironmentSecret,
|
||||
).toHaveBeenCalledTimes(2);
|
||||
expect(
|
||||
mockOctokit.rest.actions.createOrUpdateEnvironmentSecret,
|
||||
).toHaveBeenCalledWith({
|
||||
repository_id: 'repoid',
|
||||
environment_name: 'envname',
|
||||
secret_name: 'key1',
|
||||
key_id: 'keyid',
|
||||
encrypted_value: expect.any(String),
|
||||
});
|
||||
expect(
|
||||
mockOctokit.rest.actions.createOrUpdateEnvironmentSecret,
|
||||
).toHaveBeenCalledWith({
|
||||
repository_id: 'repoid',
|
||||
environment_name: 'envname',
|
||||
secret_name: 'key2',
|
||||
key_id: 'keyid',
|
||||
encrypted_value: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { parseRepoUrl } from '../publish/util';
|
||||
import { getOctokitOptions } from './helpers';
|
||||
import { Octokit } from 'octokit';
|
||||
import Sodium from 'libsodium-wrappers';
|
||||
|
||||
/**
|
||||
* Creates an `github:environment:create` Scaffolder action that creates a Github Environment.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createGithubEnvironmentAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) {
|
||||
const { integrations } = options;
|
||||
// For more information on how to define custom actions, see
|
||||
// https://backstage.io/docs/features/software-templates/writing-custom-actions
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
name: string;
|
||||
deploymentBranchPolicy?: {
|
||||
protected_branches: boolean;
|
||||
custom_branch_policies: boolean;
|
||||
};
|
||||
customBranchPolicyNames?: string[];
|
||||
environmentVariables?: { [key: string]: string };
|
||||
secrets?: { [key: string]: string };
|
||||
token?: string;
|
||||
}>({
|
||||
id: 'github:environment:create',
|
||||
description: 'Creates Deployment Environments',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['repoUrl', 'name'],
|
||||
properties: {
|
||||
repoUrl: {
|
||||
title: 'Repository Location',
|
||||
description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
|
||||
type: 'string',
|
||||
},
|
||||
name: {
|
||||
title: 'Environment Name',
|
||||
description: `Name of the deployment environment to create`,
|
||||
type: 'string',
|
||||
},
|
||||
deploymentBranchPolicy: {
|
||||
title: 'Deployment Branch Policy',
|
||||
description: `The type of deployment branch policy for this environment. To allow all branches to deploy, set to null.`,
|
||||
type: 'object',
|
||||
required: ['protected_branches', 'custom_branch_policies'],
|
||||
properties: {
|
||||
protected_branches: {
|
||||
title: 'Protected Branches',
|
||||
description: `Whether only branches with branch protection rules can deploy to this environment. If protected_branches is true, custom_branch_policies must be false; if protected_branches is false, custom_branch_policies must be true.`,
|
||||
type: 'boolean',
|
||||
},
|
||||
custom_branch_policies: {
|
||||
title: 'Custom Branch Policies',
|
||||
description: `Whether only branches that match the specified name patterns can deploy to this environment. If custom_branch_policies is true, protected_branches must be false; if custom_branch_policies is false, protected_branches must be true.`,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
customBranchPolicyNames: {
|
||||
title: 'Custom Branch Policy Name',
|
||||
description: `The name pattern that branches must match in order to deploy to the environment.
|
||||
|
||||
Wildcard characters will not match /. For example, to match branches that begin with release/ and contain an additional single slash, use release/*/*. For more information about pattern matching syntax, see the Ruby File.fnmatch documentation.`,
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
environmentVariables: {
|
||||
title: 'Environment Variables',
|
||||
description: `Environment variables attached to the deployment environment`,
|
||||
type: 'object',
|
||||
},
|
||||
secrets: {
|
||||
title: 'Deployment Secrets',
|
||||
description: `Secrets attached to the deployment environment`,
|
||||
type: 'object',
|
||||
},
|
||||
token: {
|
||||
title: 'Authentication Token',
|
||||
type: 'string',
|
||||
description: 'The token to use for authorization to GitHub',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const {
|
||||
repoUrl,
|
||||
name,
|
||||
deploymentBranchPolicy,
|
||||
customBranchPolicyNames,
|
||||
environmentVariables,
|
||||
secrets,
|
||||
token: providedToken,
|
||||
} = ctx.input;
|
||||
|
||||
const octokitOptions = await getOctokitOptions({
|
||||
integrations,
|
||||
token: providedToken,
|
||||
repoUrl: repoUrl,
|
||||
});
|
||||
|
||||
const { owner, repo } = parseRepoUrl(repoUrl, integrations);
|
||||
|
||||
if (!owner) {
|
||||
throw new InputError(`No owner provided for repo ${repoUrl}`);
|
||||
}
|
||||
|
||||
const client = new Octokit(octokitOptions);
|
||||
const repository = await client.rest.repos.get({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
});
|
||||
|
||||
await client.rest.repos.createOrUpdateEnvironment({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
environment_name: name,
|
||||
deployment_branch_policy: deploymentBranchPolicy ?? null,
|
||||
});
|
||||
|
||||
if (customBranchPolicyNames) {
|
||||
for (const item of customBranchPolicyNames) {
|
||||
await client.rest.repos.createDeploymentBranchPolicy({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
environment_name: name,
|
||||
name: item,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(environmentVariables ?? {})) {
|
||||
await client.rest.actions.createEnvironmentVariable({
|
||||
repository_id: repository.data.id,
|
||||
environment_name: name,
|
||||
name: key,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
if (secrets) {
|
||||
const publicKeyResponse = await client.rest.actions.getRepoPublicKey({
|
||||
owner: owner,
|
||||
repo: 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.createOrUpdateEnvironmentSecret({
|
||||
repository_id: repository.data.id,
|
||||
environment_name: name,
|
||||
secret_name: key,
|
||||
encrypted_value: encryptedBase64Secret,
|
||||
key_id: publicKeyResponse.data.key_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
|
||||
client: Octokit,
|
||||
repo: string,
|
||||
owner: string,
|
||||
repoVisibility: 'private' | 'internal' | 'public',
|
||||
repoVisibility: 'private' | 'internal' | 'public' | undefined,
|
||||
description: string | undefined,
|
||||
homepage: string | undefined,
|
||||
deleteBranchOnMerge: boolean,
|
||||
@@ -149,7 +149,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
|
||||
name: repo,
|
||||
org: owner,
|
||||
private: repoVisibility === 'private',
|
||||
// @ts-ignore
|
||||
// @ts-ignore https://github.com/octokit/types.ts/issues/522
|
||||
visibility: repoVisibility,
|
||||
description: description,
|
||||
delete_branch_on_merge: deleteBranchOnMerge,
|
||||
|
||||
@@ -19,3 +19,5 @@ export { createGithubIssuesLabelAction } from './githubIssuesLabel';
|
||||
export { createGithubRepoCreateAction } from './githubRepoCreate';
|
||||
export { createGithubRepoPushAction } from './githubRepoPush';
|
||||
export { createGithubWebhookAction } from './githubWebhook';
|
||||
export { createGithubDeployKeyAction } from './githubDeployKey';
|
||||
export { createGithubEnvironmentAction } from './githubEnvironment';
|
||||
|
||||
Reference in New Issue
Block a user