Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add codespaces support #114

Merged
merged 1 commit into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ If this value is set to the name of a valid environment in the target repositori

### `target`

Target where secrets should be stored: `actions` (default) or `dependabot`.
Target where secrets should be stored: `actions` (default), `codespaces` or `dependabot`.

## Usage

Expand Down
58 changes: 58 additions & 0 deletions __tests__/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ describe("setSecretForRepo", () => {

let actionsPublicKeyMock: nock.Scope;
let dependabotPublicKeyMock: nock.Scope;
let codespacesPublicKeyMock: nock.Scope;
let setActionsSecretMock: nock.Scope;
let setDependabotSecretMock: nock.Scope;
let setCodespacesSecretMock: nock.Scope;

beforeEach(() => {
nock.cleanAll();
Expand All @@ -151,6 +153,10 @@ describe("setSecretForRepo", () => {
.get(`/repos/${repo.full_name}/dependabot/secrets/public-key`)
.reply(200, publicKey);

codespacesPublicKeyMock = nock("https://api.github.com")
.get(`/repos/${repo.full_name}/codespaces/secrets/public-key`)
.reply(200, publicKey);

setActionsSecretMock = nock("https://api.github.com")
.put(`/repos/${repo.full_name}/actions/secrets/FOO`, (body) => {
expect(body.encrypted_value).toBeTruthy();
Expand All @@ -166,6 +172,14 @@ describe("setSecretForRepo", () => {
return body;
})
.reply(200);

setCodespacesSecretMock = nock("https://api.github.com")
.put(`/repos/${repo.full_name}/codespaces/secrets/FOO`, (body) => {
expect(body.encrypted_value).toBeTruthy();
expect(body.key_id).toEqual(publicKey.key_id);
return body;
})
.reply(200);
});

test("setSecretForRepo with Actions target should retrieve public key for Actions", async () => {
Expand Down Expand Up @@ -194,6 +208,19 @@ describe("setSecretForRepo", () => {
expect(dependabotPublicKeyMock.isDone()).toBeTruthy();
});

test("setSecretForRepo with Codespaces target should retrieve public key for Codespaces", async () => {
await setSecretForRepo(
octokit,
"FOO",
secrets.FOO,
repo,
"",
true,
"codespaces"
);
expect(codespacesPublicKeyMock.isDone()).toBeTruthy();
});

test("setSecretForRepo should not set secret with dry run", async () => {
await setSecretForRepo(
octokit,
Expand Down Expand Up @@ -233,6 +260,19 @@ describe("setSecretForRepo", () => {
);
expect(setDependabotSecretMock.isDone()).toBeTruthy();
});

test("setSecretForRepo with Codespaces target should call set secret endpoint for Codespaces", async () => {
await setSecretForRepo(
octokit,
"FOO",
secrets.FOO,
repo,
"",
false,
"codespaces"
);
expect(setCodespacesSecretMock.isDone()).toBeTruthy();
});
});

describe("setSecretForRepo with environment", () => {
Expand Down Expand Up @@ -336,6 +376,7 @@ describe("deleteSecretForRepo", () => {
const secrets = { FOO: "BAR" };
let deleteActionsSecretMock: nock.Scope;
let deleteDependabotSecretMock: nock.Scope;
let deleteCodespacesSecretMock: nock.Scope;

beforeEach(() => {
nock.cleanAll();
Expand All @@ -347,6 +388,10 @@ describe("deleteSecretForRepo", () => {
deleteDependabotSecretMock = nock("https://api.github.com")
.delete(`/repos/${repo.full_name}/dependabot/secrets/FOO`)
.reply(200);

deleteCodespacesSecretMock = nock("https://api.github.com")
.delete(`/repos/${repo.full_name}/codespaces/secrets/FOO`)
.reply(200);
});

test("deleteSecretForRepo should not delete secret with dry run", async () => {
Expand Down Expand Up @@ -387,6 +432,19 @@ describe("deleteSecretForRepo", () => {
);
expect(deleteDependabotSecretMock.isDone()).toBeTruthy();
});

test("deleteSecretForRepo with Codespaces target should call set secret endpoint for Codespaces", async () => {
await deleteSecretForRepo(
octokit,
"FOO",
secrets.FOO,
repo,
"",
false,
"codespaces"
);
expect(deleteCodespacesSecretMock.isDone()).toBeTruthy();
});
});

describe("deleteSecretForRepo with environment", () => {
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ inputs:
required: false
target:
description: |
Target where secrets should be stored: `actions` (default) or `dependabot`.
Target where secrets should be stored: `actions` (default), `codespaces` or `dependabot`.
default: "actions"
required: false
runs:
Expand Down
19 changes: 18 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,13 @@ function getPublicKey(octokit, repo, environment, target) {
else {
const [owner, name] = repo.full_name.split("/");
switch (target) {
case "codespaces":
publicKey = (yield octokit.codespaces.getRepoPublicKey({
owner,
repo: name,
})).data;
exports.publicKeyCache.set(repo, publicKey);
return publicKey;
case "dependabot":
publicKey = (yield octokit.dependabot.getRepoPublicKey({
owner,
Expand Down Expand Up @@ -266,6 +273,14 @@ function setSecretForRepo(octokit, name, secret, repo, environment, dry_run, tar
core.info(`Set \`${name} = ***\` on ${repo.full_name}`);
if (!dry_run) {
switch (target) {
case "codespaces":
return octokit.codespaces.createOrUpdateRepoSecret({
owner: repo_owner,
repo: repo_name,
secret_name: name,
key_id: publicKey.key_id,
encrypted_value,
});
case "dependabot":
return octokit.dependabot.createOrUpdateRepoSecret({
owner: repo_owner,
Expand Down Expand Up @@ -306,6 +321,8 @@ function deleteSecretForRepo(octokit, name, secret, repo, environment, dry_run,
if (!dry_run) {
const action = "DELETE";
switch (target) {
case "codespaces":
return octokit.request(`${action} /repos/${repo.full_name}/codespaces/secrets/${name}`);
case "dependabot":
return octokit.request(`${action} /repos/${repo.full_name}/dependabot/secrets/${name}`);
case "actions":
Expand Down Expand Up @@ -397,7 +414,7 @@ function run() {
core.setFailed(`Secrets: no matches with "${config.SECRETS.join(", ")}"`);
return;
}
const allowedTargets = ["dependabot", "actions"];
const allowedTargets = ["dependabot", "actions", "codespaces"];
if (!allowedTargets.some((x) => x === config.TARGET)) {
core.setFailed(`Target: Value not in supported targets: ${allowedTargets}`);
return;
Expand Down
23 changes: 23 additions & 0 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ export async function getPublicKey(
const [owner, name] = repo.full_name.split("/");

switch (target) {
case "codespaces":
publicKey = (
await octokit.codespaces.getRepoPublicKey({
owner,
repo: name,
})
).data as PublicKey;

publicKeyCache.set(repo, publicKey);

return publicKey;
case "dependabot":
publicKey = (
await octokit.dependabot.getRepoPublicKey({
Expand Down Expand Up @@ -232,6 +243,14 @@ export async function setSecretForRepo(

if (!dry_run) {
switch (target) {
case "codespaces":
return octokit.codespaces.createOrUpdateRepoSecret({
owner: repo_owner,
repo: repo_name,
secret_name: name,
key_id: publicKey.key_id,
encrypted_value,
});
case "dependabot":
return octokit.dependabot.createOrUpdateRepoSecret({
owner: repo_owner,
Expand Down Expand Up @@ -278,6 +297,10 @@ export async function deleteSecretForRepo(
if (!dry_run) {
const action = "DELETE";
switch (target) {
case "codespaces":
return octokit.request(
`${action} /repos/${repo.full_name}/codespaces/secrets/${name}`
);
case "dependabot":
return octokit.request(
`${action} /repos/${repo.full_name}/dependabot/secrets/${name}`
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function run(): Promise<void> {
return;
}

const allowedTargets = ["dependabot", "actions"];
const allowedTargets = ["dependabot", "actions", "codespaces"];
if (!allowedTargets.some((x) => x === config.TARGET)) {
core.setFailed(
`Target: Value not in supported targets: ${allowedTargets}`
Expand Down