Skip to content

Commit

Permalink
feat(artifacts): Add support for trigger artifacts
Browse files Browse the repository at this point in the history
  • Loading branch information
jaydee864 committed Aug 24, 2023
1 parent c9dedd1 commit a5e8429
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 20 deletions.
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ steps:
- name: Spinnaker
uses: ExpediaGroup/spinnaker-pipeline-trigger@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
topic_arn: ${{ secrets.SPINNAKER_TOPIC_ARN }}
```
Expand All @@ -38,6 +39,8 @@ The action sends the following information in the payload:
* githubEventName: The name of the webhook event that triggered the workflow.
* githubActor: The name of the person or app that initiated the workflow. For example, octocat.
* githubAction: Always set to true when GitHub Actions is running the workflow. You can use this variable to differentiate when tests are being run locally or by GitHub Actions.
* githubApiUrl: The base URL for the REST API endpoint for your GitHub instance. For example, `https://api.github.com`. This is used to construct Artifact objects for each of the modified or added files when present.
* modifiedFiles: A list of all the modified or added files in the commmit that triggered the workflow. For example, `["README.md", ".github/workflows/release.yaml"]`. If the `github_token` parameter is missing from the step config, or if the list of modified files is so large the SNS message body would exceed 256 KB, this value is set to an empty list instead.

### Additional Parameters

Expand All @@ -48,6 +51,7 @@ steps:
- name: Spinnaker
uses: ExpediaGroup/spinnaker-pipeline-trigger@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
topic_arn: ${{ secrets.SPINNAKER_TOPIC_ARN }}
parameters: |
parameter1: value1
Expand All @@ -67,6 +71,7 @@ steps:
- name: Spinnaker
uses: ExpediaGroup/spinnaker-pipeline-trigger@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
topic_arn: ${{ secrets.SPINNAKER_TOPIC_ARN }}
parameters: |
parameter1: value1
Expand All @@ -89,21 +94,21 @@ Follow Spinnaker's directions for [setting up a topic and queue](https://spinnak

* Do not set up the S3 notification
* `messageFormat` should be `CUSTOM`
* Include the template below with the `echo` portion of the config.
* Include the template below with the `echo` portion of the config to receive GitHub file artifacts on the triggered pipelines. If you are running Echo in sharded mode, this config should be included in the scheduler instances.

Sample message format based on the default parameters being sent:

```json
[
{% for item in modifiedFiles %}
{
"reference": "{{ reference }}",
"repository": "{{ repository }}",
"commit": "{{ commit }}",
"ref": "{{ ref }}",
"githubEventName": "{{ githubEventName }}",
"githubActor": "{{ githubActor }}",
"githubAction": "{{ githubAction }}"
}
"customKind": false,
"reference": "{{ githubApiUrl }}/repos/{{ repository }}/contents/{{ item }}",
"metadata": {},
"name": "{{ item }}",
"type": "github/file",
"version": "{{ commit }}"
}{% if not loop.last %},{% endif %}
{% endfor %}
]
```

Expand Down
131 changes: 128 additions & 3 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

const githubContext = {
repo: {
owner: 'Org',
repo: 'actions-test-trigger'
},
sha: 'long-sha'
}
const mockedGetCommit = jest.fn()
const mockOctokit = {
rest: {
repos: {
getCommit: mockedGetCommit
}
}
}

import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'
import { run } from '../src/main'

// Capture environment variables before running tests
const cleanEnv = process.env
jest.mock('@actions/github', () => {
return {
context: githubContext,
getOctokit: () => mockOctokit
}
})

jest.mock('@aws-sdk/client-sns')
const mockedSend = jest.fn().mockReturnValue({ MessageId: '1' })
Expand All @@ -32,6 +54,7 @@ describe('Publish', () => {
process.env.GITHUB_REPOSITORY = 'Org/actions-test-trigger'
process.env.GITHUB_SHA = 'long-sha'
process.env.GITHUB_REF = 'main'
process.env.GITHUB_API_URL = 'https://api.github.com'
SNSClient.prototype.send = mockedSend
})

Expand All @@ -41,7 +64,7 @@ describe('Publish', () => {

const input = {
Message:
'{"repository":"Org/actions-test-trigger","commit":"long-sha","ref":"main","githubEventName":"","githubActor":"","githubAction":"","parameters":{},"messageAttributes":""}',
'{"repository":"Org/actions-test-trigger","commit":"long-sha","githubApiUrl":"https://api.github.com","ref":"main","githubEventName":"","githubActor":"","githubAction":"","parameters":{},"messageAttributes":"","modifiedFiles":[]}',
TopicArn: 'arn:aws:sns:us-west-2:123456789123:spinnaker-github-actions'
}

Expand All @@ -52,6 +75,7 @@ describe('Publish', () => {
expect(SNSClient).toBeCalledWith({ region })
expect(PublishCommand).toBeCalledWith(input)
expect(mockedSend).toBeCalledTimes(1)
expect(mockedGetCommit).not.toHaveBeenCalled()
})

test('No REF passed in', async () => {
Expand All @@ -61,7 +85,7 @@ describe('Publish', () => {

const input = {
Message:
'{"repository":"Org/actions-test-trigger","commit":"long-sha","ref":"","githubEventName":"","githubActor":"","githubAction":"","parameters":{},"messageAttributes":""}',
'{"repository":"Org/actions-test-trigger","commit":"long-sha","githubApiUrl":"https://api.github.com","ref":"","githubEventName":"","githubActor":"","githubAction":"","parameters":{},"messageAttributes":"","modifiedFiles":[]}',
TopicArn: 'arn:aws:sns:us-west-2:123456789123:spinnaker-github-actions'
}

Expand All @@ -72,6 +96,7 @@ describe('Publish', () => {
expect(SNSClient).toBeCalledWith({ region })
expect(PublishCommand).toBeCalledWith(input)
expect(mockedSend).toBeCalledTimes(1)
expect(mockedGetCommit).not.toHaveBeenCalled()
})
test('With Parameters and Message Attributes', async () => {
// Arrange
Expand All @@ -81,7 +106,7 @@ describe('Publish', () => {

const input = {
Message:
'{"repository":"Org/actions-test-trigger","commit":"long-sha","ref":"main","githubEventName":"","githubActor":"","githubAction":"","parameters":{"parameter1":"value1","parameter2":"value2"},"messageAttributes":"my-attribute"}',
'{"repository":"Org/actions-test-trigger","commit":"long-sha","githubApiUrl":"https://api.github.com","ref":"main","githubEventName":"","githubActor":"","githubAction":"","parameters":{"parameter1":"value1","parameter2":"value2"},"messageAttributes":"my-attribute","modifiedFiles":[]}',
TopicArn: 'arn:aws:sns:us-west-2:123456789123:spinnaker-github-actions'
}

Expand All @@ -92,6 +117,105 @@ describe('Publish', () => {
expect(SNSClient).toBeCalledWith({ region })
expect(PublishCommand).toBeCalledWith(input)
expect(mockedSend).toBeCalledTimes(1)
expect(mockedGetCommit).not.toHaveBeenCalled()
})

describe('when github_token is present', () => {
beforeEach(() => {
process.env.INPUT_GITHUB_TOKEN = 'token'
})

test('when commit response has no files it returns an empty list for modifiedFiles', async () => {
// Arrange
const region = 'us-west-2'

const input = {
Message:
'{"repository":"Org/actions-test-trigger","commit":"long-sha","githubApiUrl":"https://api.github.com","ref":"main","githubEventName":"","githubActor":"","githubAction":"","parameters":{},"messageAttributes":"","modifiedFiles":[]}',
TopicArn: 'arn:aws:sns:us-west-2:123456789123:spinnaker-github-actions'
}
mockedGetCommit.mockResolvedValueOnce({
data: {}
})

// Act
await run()

// Assert
expect(SNSClient).toBeCalledWith({ region })
expect(PublishCommand).toBeCalledWith(input)
expect(mockedSend).toBeCalledTimes(1)
expect(mockedGetCommit).toBeCalledWith({
owner: 'Org',
repo: 'actions-test-trigger',
ref: 'long-sha'
})
})

test('it returns a list of modified and added files', async () => {
// Arrange
const region = 'us-west-2'

const input = {
Message:
'{"repository":"Org/actions-test-trigger","commit":"long-sha","githubApiUrl":"https://api.github.com","ref":"main","githubEventName":"","githubActor":"","githubAction":"","parameters":{},"messageAttributes":"","modifiedFiles":["file1","file2","file4"]}',
TopicArn: 'arn:aws:sns:us-west-2:123456789123:spinnaker-github-actions'
}
mockedGetCommit.mockResolvedValueOnce({
data: {
files: [
{ filename: 'file1', status: 'added' },
{ filename: 'file2', status: 'modified' },
{ filename: 'file3', status: 'removed' },
{ filename: 'file4', status: 'added' }
]
}
})

// Act
await run()

// Assert
expect(SNSClient).toBeCalledWith({ region })
expect(PublishCommand).toBeCalledWith(input)
expect(mockedSend).toBeCalledTimes(1)
expect(mockedGetCommit).toBeCalledWith({
owner: 'Org',
repo: 'actions-test-trigger',
ref: 'long-sha'
})
})

test('when the message is too large it returns an empty list for modifiedFiles', async () => {
// Arrange
const region = 'us-west-2'

const input = {
Message:
'{"repository":"Org/actions-test-trigger","commit":"long-sha","githubApiUrl":"https://api.github.com","ref":"main","githubEventName":"","githubActor":"","githubAction":"","parameters":{},"messageAttributes":"","modifiedFiles":[]}',
TopicArn: 'arn:aws:sns:us-west-2:123456789123:spinnaker-github-actions'
}
mockedGetCommit.mockResolvedValueOnce({
data: {
files: Array.from({ length: 500000 }, (_value, index) => {
return { filename: `file${index}`, status: 'added' }
})
}
})

// Act
await run()

// Assert
expect(SNSClient).toBeCalledWith({ region })
expect(PublishCommand).toBeCalledWith(input)
expect(mockedSend).toBeCalledTimes(1)
expect(mockedGetCommit).toBeCalledWith({
owner: 'Org',
repo: 'actions-test-trigger',
ref: 'long-sha'
})
})
})
})

Expand All @@ -112,5 +236,6 @@ describe('fail', () => {
expect(SNSClient).not.toBeCalled()
expect(PublishCommand).not.toBeCalled()
expect(mockedSend).not.toBeCalled()
expect(mockedGetCommit).not.toHaveBeenCalled()
})
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"license": "MIT",
"dependencies": {
"@actions/core": "1.10.0",
"@actions/github": "^5.1.1",
"@aws-sdk/client-sns": "3.395.0",
"@types/js-yaml": "^4.0.5",
"@types/node": "20.5.3",
Expand Down
57 changes: 53 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

import * as core from '@actions/core'
import * as github from '@actions/github'
import * as yaml from 'js-yaml'
import {
PublishCommand,
Expand All @@ -23,6 +24,13 @@ import {
SNSClient
} from '@aws-sdk/client-sns'

const SNS_MESSAGE_SIZE_LIMIT_BYTES = 256000

interface GitHubFile {
filename: string
status: string
}

async function publish(
message: object,
topicArn: string,
Expand All @@ -45,26 +53,67 @@ async function publish(
return JSON.stringify(response.MessageId)
}

function constructMessage(): object {
async function constructMessage(): Promise<object> {
const repository = process.env.GITHUB_REPOSITORY
const commit = process.env.GITHUB_SHA
const githubApiUrl = process.env.GITHUB_API_URL
const ref = process.env.GITHUB_REF || ''
const githubAction = process.env.GITHUB_ACTION || ''
const githubEventName = process.env.GITHUB_EVENT_NAME || ''
const githubActor = process.env.GITHUB_ACTOR || ''
const parameters = yaml.load(core.getInput('parameters')) || {}
const messageAttributes = core.getInput('message_attributes') || ''
const modifiedFiles = await getModifiedFiles()

return {
const message = {
repository,
commit,
githubApiUrl,
ref,
githubEventName,
githubActor,
githubAction,
parameters,
messageAttributes
messageAttributes,
modifiedFiles
}

// SNS message size limit is 256 KB
if (
Buffer.byteLength(JSON.stringify(message)) > SNS_MESSAGE_SIZE_LIMIT_BYTES
) {
core.warning(
'SNS message size limit exceeded, removing modifiedFiles from message'
)
message.modifiedFiles = []
}

return message
}

async function getModifiedFiles(): Promise<string[]> {
const token = core.getInput('github_token')
if (!token) {
core.debug(
'No github token provided, defaulting to empty list of modified files'
)
return []
}
const octokit = github.getOctokit(token)
const { data } = await octokit.rest.repos.getCommit({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
ref: github.context.sha
})
if (!data.files) {
core.debug('No files found in getCommit response')
return []
}
const files = data.files
.filter((file: GitHubFile) => file.status !== 'removed')
.map((file: GitHubFile) => file.filename)
core.debug(`Found ${files.length} changed files`)
return files
}

export async function run(): Promise<void> {
Expand All @@ -77,7 +126,7 @@ export async function run(): Promise<void> {
if (!topicArn) {
throw new Error('Topic ARN is required.')
}
const message = constructMessage()
const message = await constructMessage()
core.debug(JSON.stringify(message))
await publish(message, topicArn, region)
} catch (error) {
Expand Down
Loading

0 comments on commit a5e8429

Please sign in to comment.