Skip to content

Commit

Permalink
support specifying auth{keyId,issuerId,key material} as input
Browse files Browse the repository at this point in the history
  • Loading branch information
nodeselector committed Aug 12, 2024
1 parent 6cc74e6 commit b21164e
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 22 deletions.
32 changes: 26 additions & 6 deletions __tests__/app-store-connect-api-key.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
appStoreConnectApiKey,
appStoreConfigFromSecretValue,
buildAppStoreConnectApiKeyObject,
generateTestKey
} from '../src/app-store-connect-api-key'
Expand All @@ -12,7 +13,7 @@ describe('certificate', () => {
jest.clearAllMocks()
})

it('should create and populate a keychain', async () => {
it('should get config from secret value', async () => {
const now = new Date().getTime().toString()
const testDir = path.join(os.tmpdir(), `app-store-connect-api-key-${now}`)
fs.mkdirSync(testDir)
Expand All @@ -28,18 +29,37 @@ describe('certificate', () => {
JSON.stringify(appStoreConnectConfig)
).toString('base64')

await appStoreConnectApiKey(base64ApiKey, testDir)
const gotAppStoreAuthObj = appStoreConfigFromSecretValue(base64ApiKey)
expect(gotAppStoreAuthObj).toEqual(appStoreConnectConfig)
})

it('should create and populate a keychain', async () => {
const now = new Date().getTime().toString()
const testDir = path.join(os.tmpdir(), `app-store-connect-api-key-${now}`)
fs.mkdirSync(testDir)
const keyPath = path.join(testDir, 'app-store-connect-api-test.p8')
await generateTestKey(keyPath)

const keyValue = fs.readFileSync(keyPath, 'utf-8')
const keyBase64 = Buffer.from(keyValue).toString('base64')

await appStoreConnectApiKey(
{
keyId: 'keyId',
issuerId: 'issuedId',
privateKey: keyBase64
},
testDir
)

const gotAppStoreAuthObjPath = path.join(testDir, 'keyinfo.json')
const gotAppStoreAuthObj = JSON.parse(
fs.readFileSync(gotAppStoreAuthObjPath, 'utf-8')
)
expect(gotAppStoreAuthObj).toEqual(appStoreConnectConfig)
expect(gotAppStoreAuthObj.privateKey).toEqual(keyBase64)

const privateKeyPath = path.join(testDir, 'AuthKey_keyId.p8')
const gotPrivateKey = fs.readFileSync(privateKeyPath, 'utf-8')
expect(gotPrivateKey).toEqual(
Buffer.from(appStoreConnectConfig.privateKey, 'base64').toString('utf-8')
)
expect(gotPrivateKey).toEqual(keyValue)
})
})
38 changes: 37 additions & 1 deletion __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ let provisioningProfileMock: jest.SpiedFunction<
let appStoreConnectApiKeyMock: jest.SpiedFunction<
typeof appStoreConnectApiKey.appStoreConnectApiKey
>
let appStoreConfigFromSecretValueMock: jest.SpiedFunction<
typeof appStoreConnectApiKey.appStoreConfigFromSecretValue
>

describe('action', () => {
beforeEach(() => {
Expand All @@ -48,6 +51,9 @@ describe('action', () => {
appStoreConnectApiKeyMock = jest
.spyOn(appStoreConnectApiKey, 'appStoreConnectApiKey')
.mockImplementation()
appStoreConfigFromSecretValueMock = jest
.spyOn(appStoreConnectApiKey, 'appStoreConfigFromSecretValue')
.mockImplementation()
})

it('fails for invalid asset type', async () => {
Expand Down Expand Up @@ -165,6 +171,36 @@ describe('action', () => {
expect(setSecretMock).toHaveBeenCalledWith('test-secret-value')
expect(certificateMock).not.toHaveBeenCalled()
expect(provisioningProfileMock).not.toHaveBeenCalled()
expect(appStoreConnectApiKeyMock).toHaveBeenCalledWith('test-secret-value')
expect(appStoreConfigFromSecretValueMock).toHaveBeenCalledWith(
'test-secret-value'
)
})

it('calls appStoreConnectApiKey for app-store-connect-api-key asset type with auth inputs', async () => {
getInputMock.mockImplementation(name => {
switch (name) {
case 'asset-type':
return 'app-store-connect-api-key'
case 'app-store-connect-api-key-key-id':
return 'test-key-id'
case 'app-store-connect-api-key-issuer-id':
return 'test-issuer-id'
case 'app-store-connect-api-key-base64-private-key':
return 'test-base64-private-key'
default:
return ''
}
})

await main.run()
expect(runMock).toHaveReturned()
expect(setSecretMock).toHaveBeenCalledWith('')
expect(certificateMock).not.toHaveBeenCalled()
expect(provisioningProfileMock).not.toHaveBeenCalled()
expect(appStoreConnectApiKeyMock).toHaveBeenCalledWith({
keyId: 'test-key-id',
issuerId: 'test-issuer-id',
privateKey: 'test-base64-private-key'
})
})
})
20 changes: 19 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,25 @@ inputs:
secret-value:
description: >
* The value of the secret to import.
required: true
default: ''
app-store-connect-api-key-key-id:
description: >
* The identifier of the App Store Connect API key. * Only valid for the
`app-store-connect-api-key` asset-type. Mutualy exclusive with
`secret-value`.
default: ''
app-store-connect-api-key-issuer-id:
description: >
* The identifier of the App Store Connect API key issuer. * Only valid for
the `app-store-connect-api-key` asset-type. Mutualy exclusive with
`secret-value`.
default: ''
app-store-connect-api-key-base64-private-key:
description: >
* The base64 encoded private key of the App Store Connect API key. * Only
valid for the `app-store-connect-api-key` asset-type. Mutualy exclusive
with `secret-value`.
default: ''
keychain-name:
description: >
* The name of the keychain to use for importing the asset. * Only valid
Expand Down
2 changes: 1 addition & 1 deletion badges/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 22 additions & 5 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions src/app-store-connect-api-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ type AppStoreAuthConfig = {
privateKey: string
}

export function appStoreConfigFromSecretValue(
secretValue: string
): AppStoreAuthConfig {
const decodedSecret = Buffer.from(secretValue, 'base64').toString('utf-8')
return JSON.parse(decodedSecret)
}

export async function appStoreConnectApiKey(
secretValue: string,
appStoreAuthConfig: AppStoreAuthConfig,
destinationDir = ''
): Promise<void> {
if (!destinationDir) {
Expand All @@ -22,10 +29,8 @@ export async function appStoreConnectApiKey(
)
fs.mkdirSync(destinationDir, { recursive: true })
}
const decodedSecret = Buffer.from(secretValue, 'base64').toString('utf-8')
const appStoreAuthConfig: AppStoreAuthConfig = JSON.parse(decodedSecret)
const apiKeyPath = path.join(destinationDir, 'keyinfo.json')
fs.writeFileSync(apiKeyPath, decodedSecret)
fs.writeFileSync(apiKeyPath, JSON.stringify(appStoreAuthConfig))
const decodedPrivateKey = Buffer.from(
appStoreAuthConfig.privateKey,
'base64'
Expand Down
1 change: 0 additions & 1 deletion src/certificate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Keychain } from './keychain'
import { pki } from 'node-forge'
import { spawn } from './spawn'
import fs from 'node:fs'

Expand Down
32 changes: 30 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import * as core from '@actions/core'
import { prepareKeychainWithDeveloperCertificate } from './certificate'
import { provisioningProfile } from './provisioning-profile'
import { appStoreConnectApiKey } from './app-store-connect-api-key'
import {
appStoreConnectApiKey,
appStoreConfigFromSecretValue
} from './app-store-connect-api-key'
import { Keychain } from './keychain'

/**
Expand Down Expand Up @@ -35,7 +38,32 @@ export async function run(): Promise<void> {
await provisioningProfile(secretValue)
break
case 'app-store-connect-api-key': {
await appStoreConnectApiKey(secretValue)
if (secretValue) {
await appStoreConnectApiKey(
appStoreConfigFromSecretValue(secretValue)
)
break
}

const keyId: string = core.getInput('app-store-connect-api-key-key-id')
const issuerId: string = core.getInput(
'app-store-connect-api-key-issuer-id'
)
const privateKey: string = core.getInput(
'app-store-connect-api-key-base64-private-key'
)

if (!keyId || !issuerId || !privateKey) {
throw new Error(
'Missing required input for app-store-connect-api-key asset type'
)
}

await appStoreConnectApiKey({
keyId,
issuerId,
privateKey
})
break
}
default:
Expand Down

0 comments on commit b21164e

Please sign in to comment.