Merge pull request #315 from github/commit-time-checks
Commit Time Checks 🔒 🕐
This commit is contained in:
Коммит
e9ac229a76
|
@ -0,0 +1,57 @@
|
|||
import {commitSafetyChecks} from '../../src/functions/commit-safety-checks'
|
||||
import * as core from '@actions/core'
|
||||
|
||||
const debugMock = jest.spyOn(core, 'debug').mockImplementation(() => {})
|
||||
|
||||
var data
|
||||
var context
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
jest.spyOn(core, 'debug').mockImplementation(() => {})
|
||||
|
||||
context = {
|
||||
payload: {
|
||||
comment: {
|
||||
created_at: '2024-10-15T12:00:00Z'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data = {
|
||||
commit: {
|
||||
author: {
|
||||
date: '2024-10-15T11:00:00Z'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('checks a commit and finds that it is safe (date)', async () => {
|
||||
expect(await commitSafetyChecks(context, data)).toStrictEqual({
|
||||
message: 'success',
|
||||
status: true
|
||||
})
|
||||
expect(debugMock).toHaveBeenCalledWith(
|
||||
'2024-10-15T12:00:00Z is not older than 2024-10-15T11:00:00Z'
|
||||
)
|
||||
})
|
||||
|
||||
test('checks a commit and finds that it is not safe (date)', async () => {
|
||||
data.commit.author.date = '2024-10-15T12:00:01Z'
|
||||
|
||||
expect(await commitSafetyChecks(context, data)).toStrictEqual({
|
||||
message:
|
||||
'### ⚠️ Cannot proceed with deployment\n\nThe latest commit is not safe for deployment. It was authored after the trigger comment was created.',
|
||||
status: false
|
||||
})
|
||||
expect(debugMock).toHaveBeenCalledWith(
|
||||
'2024-10-15T12:00:00Z is older than 2024-10-15T12:00:01Z'
|
||||
)
|
||||
})
|
||||
|
||||
test('raises an error if the date format is invalid', async () => {
|
||||
data.commit.author.date = '2024-10-15T12:00:uhoh'
|
||||
await expect(commitSafetyChecks(context, data)).rejects.toThrow(
|
||||
'Invalid date format. Please ensure the dates are valid UTC timestamps.'
|
||||
)
|
||||
})
|
|
@ -14,6 +14,7 @@ import * as core from '@actions/core'
|
|||
import * as isDeprecated from '../src/functions/deprecated-checks'
|
||||
import * as nakedCommandCheck from '../src/functions/naked-command-check'
|
||||
import * as validDeploymentOrder from '../src/functions/valid-deployment-order'
|
||||
import * as commitSafetyChecks from '../src/functions/commit-safety-checks'
|
||||
import {COLORS} from '../src/functions/colors'
|
||||
|
||||
const setOutputMock = jest.spyOn(core, 'setOutput')
|
||||
|
@ -76,7 +77,8 @@ beforeEach(() => {
|
|||
id: 123,
|
||||
user: {
|
||||
login: 'monalisa'
|
||||
}
|
||||
},
|
||||
created_at: '2024-10-21T19:11:18Z'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +98,17 @@ beforeEach(() => {
|
|||
}),
|
||||
createDeploymentStatus: jest.fn().mockImplementation(() => {
|
||||
return {data: {}}
|
||||
}),
|
||||
getCommit: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
data: {
|
||||
commit: {
|
||||
author: {
|
||||
date: '2024-10-15T12:00:00Z'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
pulls: {
|
||||
|
@ -127,6 +140,14 @@ beforeEach(() => {
|
|||
sha: null
|
||||
}
|
||||
})
|
||||
jest
|
||||
.spyOn(commitSafetyChecks, 'commitSafetyChecks')
|
||||
.mockImplementation(() => {
|
||||
return {
|
||||
status: true,
|
||||
message: 'success'
|
||||
}
|
||||
})
|
||||
jest
|
||||
.spyOn(validDeploymentOrder, 'validDeploymentOrder')
|
||||
.mockImplementation(() => {
|
||||
|
@ -809,6 +830,17 @@ test('detects an out of date branch and exits', async () => {
|
|||
}),
|
||||
createDeploymentStatus: jest.fn().mockImplementation(() => {
|
||||
return {data: {}}
|
||||
}),
|
||||
getCommit: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
data: {
|
||||
commit: {
|
||||
author: {
|
||||
date: '2024-10-15T12:00:00Z'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -877,6 +909,28 @@ test('fails prechecks', async () => {
|
|||
expect(validDeploymentOrderMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('fails commitSafetyChecks', async () => {
|
||||
jest
|
||||
.spyOn(commitSafetyChecks, 'commitSafetyChecks')
|
||||
.mockImplementation(() => {
|
||||
return {
|
||||
status: false,
|
||||
message:
|
||||
'### ⚠️ Cannot proceed with deployment... a scary commit was found'
|
||||
}
|
||||
})
|
||||
jest.spyOn(actionStatus, 'actionStatus').mockImplementation(() => {
|
||||
return undefined
|
||||
})
|
||||
expect(await run()).toBe('failure')
|
||||
expect(saveStateMock).toHaveBeenCalledWith('bypass', 'true')
|
||||
expect(setFailedMock).toHaveBeenCalledWith(
|
||||
'### ⚠️ Cannot proceed with deployment... a scary commit was found'
|
||||
)
|
||||
|
||||
expect(validDeploymentOrderMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('runs the .help command successfully', async () => {
|
||||
github.context.payload.comment.body = '.help'
|
||||
jest.spyOn(help, 'help').mockImplementation(() => {
|
||||
|
|
|
@ -44013,6 +44013,60 @@ async function validDeploymentOrder(
|
|||
return {valid: false, results: results}
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/functions/commit-safety-checks.js
|
||||
|
||||
|
||||
// A helper method to ensure that the commit being used is safe for deployment
|
||||
// These safety checks are supplemental to the checks found in `src/functions/prechecks.js`
|
||||
// :param context: The context of the event
|
||||
// :param data: An object containing data such as the sha, the created_at time for the comment, and more
|
||||
async function commitSafetyChecks(context, data) {
|
||||
const comment_created_at = context.payload.comment.created_at
|
||||
core.debug(`comment_created_at: ${comment_created_at}`)
|
||||
|
||||
// fetch the timestamp that the commit was authored (format: "2024-10-21T19:10:24Z" - String)
|
||||
const commit_created_at = data.commit.author.date
|
||||
core.debug(`commit_created_at: ${commit_created_at}`)
|
||||
|
||||
// check to ensure that the commit was authored before the comment was created
|
||||
if (isTimestampOlder(comment_created_at, commit_created_at)) {
|
||||
return {
|
||||
message: `### ⚠️ Cannot proceed with deployment\n\nThe latest commit is not safe for deployment. It was authored after the trigger comment was created.`,
|
||||
status: false
|
||||
}
|
||||
}
|
||||
|
||||
// if we make it through all the checks, we can return a success object
|
||||
return {message: 'success', status: true}
|
||||
}
|
||||
|
||||
// A helper method that checks if timestamp A is older than timestamp B
|
||||
// :param timestampA: The first timestamp to compare (String - format: "2024-10-21T19:10:24Z")
|
||||
// :param timestampB: The second timestamp to compare (String - format: "2024-10-21T19:10:24Z")
|
||||
// :returns: true if timestampA is older than timestampB, false otherwise
|
||||
function isTimestampOlder(timestampA, timestampB) {
|
||||
// Parse the date strings into Date objects
|
||||
const timestampADate = new Date(timestampA)
|
||||
const timestampBDate = new Date(timestampB)
|
||||
|
||||
// Check if the parsed dates are valid
|
||||
if (isNaN(timestampADate) || isNaN(timestampBDate)) {
|
||||
throw new Error(
|
||||
'Invalid date format. Please ensure the dates are valid UTC timestamps.'
|
||||
)
|
||||
}
|
||||
|
||||
const result = timestampADate < timestampBDate
|
||||
|
||||
if (result) {
|
||||
core.debug(`${timestampA} is older than ${timestampB}`)
|
||||
} else {
|
||||
core.debug(`${timestampA} is not older than ${timestampB}`)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/main.js
|
||||
|
||||
|
||||
|
@ -44041,11 +44095,14 @@ async function validDeploymentOrder(
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// :returns: 'success', 'success - noop', 'success - merge deploy mode', 'failure', 'safe-exit', 'success - unlock on merge mode' or raises an error
|
||||
async function run() {
|
||||
try {
|
||||
core.debug(`context: ${JSON.stringify(github.context)}`)
|
||||
|
||||
// Get the inputs for the branch-deploy Action
|
||||
const token = core.getInput('github_token', {required: true})
|
||||
|
||||
|
@ -44452,6 +44509,33 @@ async function run() {
|
|||
return 'failure'
|
||||
}
|
||||
|
||||
// fetch commit data from the API
|
||||
const commitData = await octokit.rest.repos.getCommit({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
ref: precheckResults.sha // exact SHAs can be used here in the ref parameter (which is what we want)
|
||||
})
|
||||
|
||||
// Run commit safety checks
|
||||
const commitSafetyCheckResults = await commitSafetyChecks(github.context, {
|
||||
commit: commitData.data.commit
|
||||
})
|
||||
|
||||
// If the commitSafetyCheckResults failed, run the actionStatus function and return
|
||||
// note: if we don't pass in the 'success' bool, actionStatus will default to failure mode
|
||||
if (!commitSafetyCheckResults.status) {
|
||||
await actionStatus(
|
||||
github.context,
|
||||
octokit,
|
||||
reactRes.data.id, // original reaction id
|
||||
commitSafetyCheckResults.message // message
|
||||
)
|
||||
// Set the bypass state to true so that the post run logic will not run
|
||||
core.saveState('bypass', 'true')
|
||||
core.setFailed(commitSafetyCheckResults.message)
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// check for enforced deployment order if the input was provided and we are NOT deploying to the stable branch
|
||||
if (
|
||||
inputs.enforced_deployment_order.length > 0 &&
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,255 @@
|
|||
{
|
||||
"payload": {
|
||||
"action": "created",
|
||||
"comment": {
|
||||
"author_association": "OWNER",
|
||||
"body": ".deploy to staging",
|
||||
"created_at": "2024-10-21T19:11:18Z",
|
||||
"html_url": "https://github.com/GrantBirki/actions-sandbox/pull/119#issuecomment-2427512135",
|
||||
"id": 2427512135,
|
||||
"issue_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/119",
|
||||
"node_id": "IC_kwDOID9x8M6QsOVH",
|
||||
"performed_via_github_app": null,
|
||||
"reactions": {
|
||||
"+1": 0,
|
||||
"-1": 0,
|
||||
"confused": 0,
|
||||
"eyes": 0,
|
||||
"heart": 0,
|
||||
"hooray": 0,
|
||||
"laugh": 0,
|
||||
"rocket": 0,
|
||||
"total_count": 0,
|
||||
"url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/comments/2427512135/reactions"
|
||||
},
|
||||
"updated_at": "2024-10-21T19:11:18Z",
|
||||
"url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/comments/2427512135",
|
||||
"user": {
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23362539?v=4",
|
||||
"events_url": "https://api.github.com/users/GrantBirki/events{/privacy}",
|
||||
"followers_url": "https://api.github.com/users/GrantBirki/followers",
|
||||
"following_url": "https://api.github.com/users/GrantBirki/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/GrantBirki/gists{/gist_id}",
|
||||
"gravatar_id": "",
|
||||
"html_url": "https://github.com/GrantBirki",
|
||||
"id": 23362539,
|
||||
"login": "GrantBirki",
|
||||
"node_id": "MDQ6VXNlcjIzMzYyNTM5",
|
||||
"organizations_url": "https://api.github.com/users/GrantBirki/orgs",
|
||||
"received_events_url": "https://api.github.com/users/GrantBirki/received_events",
|
||||
"repos_url": "https://api.github.com/users/GrantBirki/repos",
|
||||
"site_admin": true,
|
||||
"starred_url": "https://api.github.com/users/GrantBirki/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/GrantBirki/subscriptions",
|
||||
"type": "User",
|
||||
"url": "https://api.github.com/users/GrantBirki",
|
||||
"user_view_type": "public"
|
||||
}
|
||||
},
|
||||
"issue": {
|
||||
"active_lock_reason": null,
|
||||
"assignee": null,
|
||||
"assignees": [],
|
||||
"author_association": "OWNER",
|
||||
"body": null,
|
||||
"closed_at": null,
|
||||
"comments": 3,
|
||||
"comments_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/119/comments",
|
||||
"created_at": "2024-10-21T19:10:27Z",
|
||||
"draft": false,
|
||||
"events_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/119/events",
|
||||
"html_url": "https://github.com/GrantBirki/actions-sandbox/pull/119",
|
||||
"id": 2603432374,
|
||||
"labels": [],
|
||||
"labels_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/119/labels{/name}",
|
||||
"locked": false,
|
||||
"milestone": null,
|
||||
"node_id": "PR_kwDOID9x8M5_XW7D",
|
||||
"number": 119,
|
||||
"performed_via_github_app": null,
|
||||
"pull_request": {
|
||||
"diff_url": "https://github.com/GrantBirki/actions-sandbox/pull/119.diff",
|
||||
"html_url": "https://github.com/GrantBirki/actions-sandbox/pull/119",
|
||||
"merged_at": null,
|
||||
"patch_url": "https://github.com/GrantBirki/actions-sandbox/pull/119.patch",
|
||||
"url": "https://api.github.com/repos/GrantBirki/actions-sandbox/pulls/119"
|
||||
},
|
||||
"reactions": {
|
||||
"+1": 0,
|
||||
"-1": 0,
|
||||
"confused": 0,
|
||||
"eyes": 0,
|
||||
"heart": 0,
|
||||
"hooray": 0,
|
||||
"laugh": 0,
|
||||
"rocket": 0,
|
||||
"total_count": 0,
|
||||
"url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/119/reactions"
|
||||
},
|
||||
"repository_url": "https://api.github.com/repos/GrantBirki/actions-sandbox",
|
||||
"state": "open",
|
||||
"state_reason": null,
|
||||
"timeline_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/119/timeline",
|
||||
"title": "test",
|
||||
"updated_at": "2024-10-21T19:11:20Z",
|
||||
"url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/119",
|
||||
"user": {
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23362539?v=4",
|
||||
"events_url": "https://api.github.com/users/GrantBirki/events{/privacy}",
|
||||
"followers_url": "https://api.github.com/users/GrantBirki/followers",
|
||||
"following_url": "https://api.github.com/users/GrantBirki/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/GrantBirki/gists{/gist_id}",
|
||||
"gravatar_id": "",
|
||||
"html_url": "https://github.com/GrantBirki",
|
||||
"id": 23362539,
|
||||
"login": "GrantBirki",
|
||||
"node_id": "MDQ6VXNlcjIzMzYyNTM5",
|
||||
"organizations_url": "https://api.github.com/users/GrantBirki/orgs",
|
||||
"received_events_url": "https://api.github.com/users/GrantBirki/received_events",
|
||||
"repos_url": "https://api.github.com/users/GrantBirki/repos",
|
||||
"site_admin": true,
|
||||
"starred_url": "https://api.github.com/users/GrantBirki/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/GrantBirki/subscriptions",
|
||||
"type": "User",
|
||||
"url": "https://api.github.com/users/GrantBirki",
|
||||
"user_view_type": "public"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"allow_forking": true,
|
||||
"archive_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/{archive_format}{/ref}",
|
||||
"archived": false,
|
||||
"assignees_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/branches{/branch}",
|
||||
"clone_url": "https://github.com/GrantBirki/actions-sandbox.git",
|
||||
"collaborators_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/contributors",
|
||||
"created_at": "2022-09-25T02:35:17Z",
|
||||
"default_branch": "main",
|
||||
"deployments_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/deployments",
|
||||
"description": "A sandbox repo for testing GitHub Actions",
|
||||
"disabled": false,
|
||||
"downloads_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/downloads",
|
||||
"events_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/events",
|
||||
"fork": false,
|
||||
"forks": 1,
|
||||
"forks_count": 1,
|
||||
"forks_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/forks",
|
||||
"full_name": "GrantBirki/actions-sandbox",
|
||||
"git_commits_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/git/tags{/sha}",
|
||||
"git_url": "git://github.com/GrantBirki/actions-sandbox.git",
|
||||
"has_discussions": false,
|
||||
"has_downloads": true,
|
||||
"has_issues": true,
|
||||
"has_pages": false,
|
||||
"has_projects": true,
|
||||
"has_wiki": false,
|
||||
"homepage": "",
|
||||
"hooks_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/hooks",
|
||||
"html_url": "https://github.com/GrantBirki/actions-sandbox",
|
||||
"id": 541028848,
|
||||
"is_template": false,
|
||||
"issue_comment_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/labels{/name}",
|
||||
"language": null,
|
||||
"languages_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/languages",
|
||||
"license": null,
|
||||
"merges_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/merges",
|
||||
"milestones_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/milestones{/number}",
|
||||
"mirror_url": null,
|
||||
"name": "actions-sandbox",
|
||||
"node_id": "R_kgDOID9x8A",
|
||||
"notifications_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/notifications{?since,all,participating}",
|
||||
"open_issues": 1,
|
||||
"open_issues_count": 1,
|
||||
"owner": {
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23362539?v=4",
|
||||
"events_url": "https://api.github.com/users/GrantBirki/events{/privacy}",
|
||||
"followers_url": "https://api.github.com/users/GrantBirki/followers",
|
||||
"following_url": "https://api.github.com/users/GrantBirki/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/GrantBirki/gists{/gist_id}",
|
||||
"gravatar_id": "",
|
||||
"html_url": "https://github.com/GrantBirki",
|
||||
"id": 23362539,
|
||||
"login": "GrantBirki",
|
||||
"node_id": "MDQ6VXNlcjIzMzYyNTM5",
|
||||
"organizations_url": "https://api.github.com/users/GrantBirki/orgs",
|
||||
"received_events_url": "https://api.github.com/users/GrantBirki/received_events",
|
||||
"repos_url": "https://api.github.com/users/GrantBirki/repos",
|
||||
"site_admin": true,
|
||||
"starred_url": "https://api.github.com/users/GrantBirki/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/GrantBirki/subscriptions",
|
||||
"type": "User",
|
||||
"url": "https://api.github.com/users/GrantBirki",
|
||||
"user_view_type": "public"
|
||||
},
|
||||
"private": false,
|
||||
"pulls_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/pulls{/number}",
|
||||
"pushed_at": "2024-10-21T19:10:24Z",
|
||||
"releases_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/releases{/id}",
|
||||
"size": 380,
|
||||
"ssh_url": "git@github.com:GrantBirki/actions-sandbox.git",
|
||||
"stargazers_count": 0,
|
||||
"stargazers_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/subscription",
|
||||
"svn_url": "https://github.com/GrantBirki/actions-sandbox",
|
||||
"tags_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/tags",
|
||||
"teams_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/teams",
|
||||
"topics": [
|
||||
"testing"
|
||||
],
|
||||
"trees_url": "https://api.github.com/repos/GrantBirki/actions-sandbox/git/trees{/sha}",
|
||||
"updated_at": "2024-10-21T19:10:19Z",
|
||||
"url": "https://api.github.com/repos/GrantBirki/actions-sandbox",
|
||||
"visibility": "public",
|
||||
"watchers": 0,
|
||||
"watchers_count": 0,
|
||||
"web_commit_signoff_required": false
|
||||
},
|
||||
"sender": {
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23362539?v=4",
|
||||
"events_url": "https://api.github.com/users/GrantBirki/events{/privacy}",
|
||||
"followers_url": "https://api.github.com/users/GrantBirki/followers",
|
||||
"following_url": "https://api.github.com/users/GrantBirki/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/GrantBirki/gists{/gist_id}",
|
||||
"gravatar_id": "",
|
||||
"html_url": "https://github.com/GrantBirki",
|
||||
"id": 23362539,
|
||||
"login": "GrantBirki",
|
||||
"node_id": "MDQ6VXNlcjIzMzYyNTM5",
|
||||
"organizations_url": "https://api.github.com/users/GrantBirki/orgs",
|
||||
"received_events_url": "https://api.github.com/users/GrantBirki/received_events",
|
||||
"repos_url": "https://api.github.com/users/GrantBirki/repos",
|
||||
"site_admin": true,
|
||||
"starred_url": "https://api.github.com/users/GrantBirki/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/GrantBirki/subscriptions",
|
||||
"type": "User",
|
||||
"url": "https://api.github.com/users/GrantBirki",
|
||||
"user_view_type": "public"
|
||||
}
|
||||
},
|
||||
"eventName": "issue_comment",
|
||||
"sha": "09e517ae87b70ef328cc825abff511df43382046",
|
||||
"ref": "refs/heads/main",
|
||||
"workflow": "deploy",
|
||||
"action": "branch-deploy",
|
||||
"actor": "GrantBirki",
|
||||
"job": "trigger",
|
||||
"runNumber": 761,
|
||||
"runId": 11446979021,
|
||||
"apiUrl": "https://api.github.com",
|
||||
"serverUrl": "https://github.com",
|
||||
"graphqlUrl": "https://api.github.com/graphql"
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import * as core from '@actions/core'
|
||||
|
||||
// A helper method to ensure that the commit being used is safe for deployment
|
||||
// These safety checks are supplemental to the checks found in `src/functions/prechecks.js`
|
||||
// :param context: The context of the event
|
||||
// :param data: An object containing data such as the sha, the created_at time for the comment, and more
|
||||
export async function commitSafetyChecks(context, data) {
|
||||
const comment_created_at = context.payload.comment.created_at
|
||||
core.debug(`comment_created_at: ${comment_created_at}`)
|
||||
|
||||
// fetch the timestamp that the commit was authored (format: "2024-10-21T19:10:24Z" - String)
|
||||
const commit_created_at = data.commit.author.date
|
||||
core.debug(`commit_created_at: ${commit_created_at}`)
|
||||
|
||||
// check to ensure that the commit was authored before the comment was created
|
||||
if (isTimestampOlder(comment_created_at, commit_created_at)) {
|
||||
return {
|
||||
message: `### ⚠️ Cannot proceed with deployment\n\nThe latest commit is not safe for deployment. It was authored after the trigger comment was created.`,
|
||||
status: false
|
||||
}
|
||||
}
|
||||
|
||||
// if we make it through all the checks, we can return a success object
|
||||
return {message: 'success', status: true}
|
||||
}
|
||||
|
||||
// A helper method that checks if timestamp A is older than timestamp B
|
||||
// :param timestampA: The first timestamp to compare (String - format: "2024-10-21T19:10:24Z")
|
||||
// :param timestampB: The second timestamp to compare (String - format: "2024-10-21T19:10:24Z")
|
||||
// :returns: true if timestampA is older than timestampB, false otherwise
|
||||
function isTimestampOlder(timestampA, timestampB) {
|
||||
// Parse the date strings into Date objects
|
||||
const timestampADate = new Date(timestampA)
|
||||
const timestampBDate = new Date(timestampB)
|
||||
|
||||
// Check if the parsed dates are valid
|
||||
if (isNaN(timestampADate) || isNaN(timestampBDate)) {
|
||||
throw new Error(
|
||||
'Invalid date format. Please ensure the dates are valid UTC timestamps.'
|
||||
)
|
||||
}
|
||||
|
||||
const result = timestampADate < timestampBDate
|
||||
|
||||
if (result) {
|
||||
core.debug(`${timestampA} is older than ${timestampB}`)
|
||||
} else {
|
||||
core.debug(`${timestampA} is not older than ${timestampB}`)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
30
src/main.js
30
src/main.js
|
@ -26,10 +26,13 @@ import {COLORS} from './functions/colors'
|
|||
import {getInputs} from './functions/inputs'
|
||||
import {constructValidBranchName} from './functions/valid-branch-name'
|
||||
import {validDeploymentOrder} from './functions/valid-deployment-order'
|
||||
import {commitSafetyChecks} from './functions/commit-safety-checks'
|
||||
|
||||
// :returns: 'success', 'success - noop', 'success - merge deploy mode', 'failure', 'safe-exit', 'success - unlock on merge mode' or raises an error
|
||||
export async function run() {
|
||||
try {
|
||||
core.debug(`context: ${JSON.stringify(context)}`)
|
||||
|
||||
// Get the inputs for the branch-deploy Action
|
||||
const token = core.getInput('github_token', {required: true})
|
||||
|
||||
|
@ -436,6 +439,33 @@ export async function run() {
|
|||
return 'failure'
|
||||
}
|
||||
|
||||
// fetch commit data from the API
|
||||
const commitData = await octokit.rest.repos.getCommit({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: precheckResults.sha // exact SHAs can be used here in the ref parameter (which is what we want)
|
||||
})
|
||||
|
||||
// Run commit safety checks
|
||||
const commitSafetyCheckResults = await commitSafetyChecks(context, {
|
||||
commit: commitData.data.commit
|
||||
})
|
||||
|
||||
// If the commitSafetyCheckResults failed, run the actionStatus function and return
|
||||
// note: if we don't pass in the 'success' bool, actionStatus will default to failure mode
|
||||
if (!commitSafetyCheckResults.status) {
|
||||
await actionStatus(
|
||||
context,
|
||||
octokit,
|
||||
reactRes.data.id, // original reaction id
|
||||
commitSafetyCheckResults.message // message
|
||||
)
|
||||
// Set the bypass state to true so that the post run logic will not run
|
||||
core.saveState('bypass', 'true')
|
||||
core.setFailed(commitSafetyCheckResults.message)
|
||||
return 'failure'
|
||||
}
|
||||
|
||||
// check for enforced deployment order if the input was provided and we are NOT deploying to the stable branch
|
||||
if (
|
||||
inputs.enforced_deployment_order.length > 0 &&
|
||||
|
|
Загрузка…
Ссылка в новой задаче