Skip to content

Commit 4665725

Browse files
committed
Initial action implementation, with tests
1 parent d964a71 commit 4665725

14 files changed

+9673
-261
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@ jobs:
2121
- uses: actions/checkout@v3
2222
- uses: ./
2323
with:
24-
milliseconds: 1000
24+
role-to-assume: 3
25+
gem-server: 'https://oidc-api-token.rubygems.org'
26+
- name: Test token
27+
run: |
28+
curl -v --fail 'https://oidc-api-token.rubygems.org/api/v1/oidc/api_key/roles/3' -H 'Authorization: ${{ env.RUBYGEMS_API_TOKEN }}'

__tests__/main.test.ts

Lines changed: 126 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,132 @@
1-
import {wait} from '../src/wait'
2-
import * as process from 'process'
3-
import * as cp from 'child_process'
4-
import * as path from 'path'
5-
import {expect, test} from '@jest/globals'
6-
7-
test('throws invalid number', async () => {
8-
const input = parseInt('foo', 10)
9-
await expect(wait(input)).rejects.toThrow('milliseconds not a number')
1+
import nock from 'nock'
2+
import {
3+
beforeEach,
4+
afterEach,
5+
expect,
6+
jest,
7+
test,
8+
describe
9+
} from '@jest/globals'
10+
import {readFile} from 'fs/promises'
11+
import mockFS from 'mock-fs'
12+
import * as os from 'os'
13+
import YAML from 'yaml'
14+
import * as core from '@actions/core'
15+
16+
import {configureApiToken} from '../src/configureApiToken'
17+
import {assumeRole} from '../src/oidc/assumeRole'
18+
19+
jest.mock('os', () => {
20+
const originalModule = jest.requireActual('os') as any
21+
22+
return {
23+
__esModule: true, // Use it when dealing with esModules
24+
...originalModule,
25+
homedir: jest.fn()
26+
}
1027
})
1128

12-
test('wait 500 ms', async () => {
13-
const start = new Date()
14-
await wait(500)
15-
const end = new Date()
16-
var delta = Math.abs(end.getTime() - start.getTime())
17-
expect(delta).toBeGreaterThan(450)
29+
describe('configureApiToken', () => {
30+
test('throws no token', async () => {
31+
await expect(configureApiToken(undefined as any, '')).rejects.toThrow(
32+
'Empty apiToken is not supported'
33+
)
34+
})
35+
36+
test('throws empty token', async () => {
37+
await expect(configureApiToken(undefined as any, '')).rejects.toThrow(
38+
'Empty apiToken is not supported'
39+
)
40+
})
41+
42+
test('adding default token', async () => {
43+
mockHomedir('/home/runner')
44+
mockFS({})
45+
46+
await configureApiToken('token', 'https://rubygems.org')
47+
48+
await expect(
49+
readFile('/home/runner/.gem/credentials', 'utf8').then(YAML.parse)
50+
).resolves.toEqual({':rubygems_api_key': 'token'})
51+
expect(exportedVariables).toEqual({
52+
BUNDLE_GEM__PUSH_KEY: 'token',
53+
GEM_HOST_API_KEY: 'token',
54+
RUBYGEMS_API_KEY: 'token'
55+
})
56+
})
57+
58+
test('adding custom token', async () => {
59+
mockHomedir('/home/runner')
60+
mockFS({})
61+
62+
await configureApiToken('token', 'https://oidc-api-token.rubygems.org')
63+
64+
await expect(
65+
readFile('/home/runner/.gem/credentials', 'utf8').then(YAML.parse)
66+
).resolves.toEqual({'https://oidc-api-token.rubygems.org': 'token'})
67+
expect(exportedVariables).toEqual({
68+
BUNDLE_GEM__PUSH_KEY: 'token',
69+
GEM_HOST_API_KEY: 'token',
70+
RUBYGEMS_API_KEY: 'token'
71+
})
72+
})
1873
})
1974

20-
// shows how the runner will run a javascript action with env / stdout protocol
21-
test('test runs', () => {
22-
process.env['INPUT_MILLISECONDS'] = '500'
23-
const np = process.execPath
24-
const ip = path.join(__dirname, '..', 'lib', 'main.js')
25-
const options: cp.ExecFileSyncOptions = {
26-
env: process.env
75+
describe('assumeRole', () => {
76+
test('works', async () => {
77+
jest.spyOn(core, 'getIDToken').mockReturnValue(Promise.resolve('ID_TOKEN'))
78+
79+
nock('https://rubygems.org')
80+
.post('/api/v1/oidc/api_key_roles/1/assume_role', {
81+
jwt: 'ID_TOKEN'
82+
})
83+
.reply(201, {
84+
name: 'role name',
85+
rubygems_api_key: 'API_KEY',
86+
expires_at: '2021-01-01T00:00:00Z',
87+
scopes: ['push_rubygem']
88+
})
89+
90+
await expect(
91+
assumeRole('1', 'rubygems.org', 'https://rubygems.org')
92+
).resolves.toEqual({
93+
expiresAt: '2021-01-01T00:00:00Z',
94+
gem: undefined,
95+
name: 'role name',
96+
rubygemsApiKey: 'API_KEY',
97+
scopes: ['push_rubygem']
98+
})
99+
})
100+
})
101+
102+
function mockHomedir(homedir: string) {
103+
mockOf(os.homedir).mockReturnValue(homedir)
104+
}
105+
106+
function mockOf<T extends (...args: any) => any>(dep: T): jest.Mock<T> {
107+
return <jest.Mock<T>>(<unknown>dep)
108+
}
109+
110+
let exportedVariables: Record<string, string>
111+
let secrets: string[]
112+
113+
beforeEach(() => {
114+
if (!nock.isActive()) {
115+
nock.activate()
27116
}
28-
console.log(cp.execFileSync(np, [ip], options).toString())
117+
nock.disableNetConnect()
118+
119+
exportedVariables = {}
120+
secrets = []
121+
jest.spyOn(core, 'exportVariable').mockImplementation((name, value) => {
122+
exportedVariables[name] = value
123+
})
124+
jest.spyOn(core, 'setSecret').mockImplementation(value => {
125+
secrets.push(value)
126+
})
127+
})
128+
129+
afterEach(() => {
130+
mockFS.restore()
131+
nock.restore()
29132
})

action.yml

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
1-
name: 'Your name here'
2-
description: 'Provide a description here'
3-
author: 'Your name or organization here'
1+
name: 'Configure RubyGems Credentials'
2+
description: 'Configure rubygems.org credential environment variables for use in other GitHub Actions.'
3+
branding:
4+
icon: 'package'
5+
color: 'red'
6+
author: 'RubyGems'
47
inputs:
5-
milliseconds: # change this
6-
required: true
7-
description: 'input description here'
8-
default: 'default value if applicable'
8+
gem-server:
9+
required: false
10+
description: 'The gem server to configure credentials for. Defaults to https://rubygems.org.'
11+
default: 'https://rubygems.org'
12+
audience:
13+
default: 'rubygems.org'
14+
description: 'The audience to use for the OIDC provider. Defaults to rubygems.org.'
15+
required: false
16+
role-to-assume:
17+
description: >-
18+
Use the provided credentials to assume an OIDC api token role and configure the Actions
19+
environment with the assumed role credentials rather than with the provided
20+
credentials
21+
required: false
22+
api-token:
23+
description: 'The rubygems api token to use for authentication.'
24+
required: false
925
runs:
1026
using: 'node16'
1127
main: 'dist/index.js'

0 commit comments

Comments
 (0)