check github_ref tag and sha are checked out on parse
This commit is contained in:
@@ -212,3 +212,50 @@ describe('readFileContents', () => {
|
||||
expect(fsHelper.readFileContents(tempFile).toString()).toEqual(fileContent)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ensureCorrectShaCheckedOut', () => {
|
||||
let dir: string
|
||||
let commit1: string
|
||||
let commit2: string
|
||||
const tag1 = 'tag1'
|
||||
const tag2 = 'tag2'
|
||||
|
||||
beforeEach(() => {
|
||||
dir = fsHelper.createTempDir(tmpFileDir, 'subdir')
|
||||
|
||||
// Set up a git repository with two commits
|
||||
execSync('git init', { cwd: dir })
|
||||
execSync('git commit --allow-empty -m "test"', { cwd: dir })
|
||||
execSync('git commit --allow-empty -m "test"', { cwd: dir })
|
||||
|
||||
// Grab the two commits
|
||||
commit1 = execSync('git rev-parse HEAD~1', { cwd: dir }).toString().trim()
|
||||
commit2 = execSync('git rev-parse HEAD', { cwd: dir }).toString().trim()
|
||||
|
||||
// Create a tag for each commit
|
||||
execSync(`git tag ${tag1} ${commit1}`, { cwd: dir })
|
||||
execSync(`git tag ${tag2} ${commit2}`, { cwd: dir })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(dir, { recursive: true })
|
||||
})
|
||||
|
||||
it('does not throw an error if the correct SHA is checked out', async () => {
|
||||
await expect(
|
||||
fsHelper.ensureCorrectShaCheckedOut(`refs/tags/${tag2}`, commit2, dir)
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('throws an error if the correct SHA is not checked out', async () => {
|
||||
await expect(
|
||||
fsHelper.ensureCorrectShaCheckedOut(`refs/tags/${tag1}`, commit1, dir)
|
||||
).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('throws an error if the sha of the tag does not match expected sha', async () => {
|
||||
await expect(async () =>
|
||||
fsHelper.ensureCorrectShaCheckedOut(`refs/tags/${tag1}`, commit2, dir)
|
||||
).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,6 +23,7 @@ let setOutputMock: jest.SpyInstance
|
||||
let createTempDirMock: jest.SpyInstance
|
||||
let createArchivesMock: jest.SpyInstance
|
||||
let stageActionFilesMock: jest.SpyInstance
|
||||
let ensureCorrectShaCheckedOutMock: jest.SpyInstance
|
||||
let publishOCIArtifactMock: jest.SpyInstance
|
||||
|
||||
// Mock the config resolution
|
||||
@@ -49,6 +50,9 @@ describe('run', () => {
|
||||
stageActionFilesMock = jest
|
||||
.spyOn(fsHelper, 'stageActionFiles')
|
||||
.mockImplementation()
|
||||
ensureCorrectShaCheckedOutMock = jest
|
||||
.spyOn(fsHelper, 'ensureCorrectShaCheckedOut')
|
||||
.mockImplementation()
|
||||
|
||||
// GHCR Client mocks
|
||||
publishOCIArtifactMock = jest
|
||||
@@ -93,9 +97,24 @@ describe('run', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('fails if ensuring the correct SHA is checked out errors', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {
|
||||
throw new Error('Something went wrong')
|
||||
})
|
||||
|
||||
// Run the action
|
||||
await main.run()
|
||||
|
||||
// Check the results
|
||||
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
||||
})
|
||||
|
||||
it('fails if creating staging temp directory fails', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
throw new Error('Something went wrong')
|
||||
})
|
||||
@@ -110,6 +129,8 @@ describe('run', () => {
|
||||
it('fails if staging files fails', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
return 'tmpDir/staging'
|
||||
})
|
||||
@@ -128,6 +149,8 @@ describe('run', () => {
|
||||
it('fails if creating archives temp directory fails', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
|
||||
createTempDirMock.mockImplementation((_, path: string) => {
|
||||
if (path === 'staging') {
|
||||
return 'staging'
|
||||
@@ -147,6 +170,8 @@ describe('run', () => {
|
||||
it('fails if creating archives fails', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
return 'stagingOrArchivesDir'
|
||||
})
|
||||
@@ -167,6 +192,8 @@ describe('run', () => {
|
||||
it('fails if publishing OCI artifact fails', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
return 'stagingOrArchivesDir'
|
||||
})
|
||||
@@ -202,6 +229,8 @@ describe('run', () => {
|
||||
it('fails if creating attestation fails', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
return 'stagingOrArchivesDir'
|
||||
})
|
||||
@@ -246,6 +275,8 @@ describe('run', () => {
|
||||
options.isEnterprise = true
|
||||
resolvePublishActionOptionsMock.mockReturnValue(options)
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
return 'stagingOrArchivesDir'
|
||||
})
|
||||
@@ -302,6 +333,8 @@ describe('run', () => {
|
||||
it('uploads the artifact, returns package metadata from GHCR, and creates an attestation in non-enterprise for public repo', async () => {
|
||||
resolvePublishActionOptionsMock.mockReturnValue(baseOptions())
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
return 'stagingOrArchivesDir'
|
||||
})
|
||||
@@ -383,6 +416,8 @@ describe('run', () => {
|
||||
|
||||
resolvePublishActionOptionsMock.mockReturnValue(opts)
|
||||
|
||||
ensureCorrectShaCheckedOutMock.mockImplementation(() => {})
|
||||
|
||||
createTempDirMock.mockImplementation(() => {
|
||||
return 'stagingOrArchivesDir'
|
||||
})
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="116" height="20" role="img" aria-label="Coverage: 96.63%"><title>Coverage: 96.63%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#4c1"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">96.63%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">96.63%</text></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="116" height="20" role="img" aria-label="Coverage: 96.78%"><title>Coverage: 96.78%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#4c1"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">96.78%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">96.78%</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
+5087
-3
File diff suppressed because it is too large
Load Diff
+52
@@ -82,6 +82,55 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
|
||||
@kwsites/file-exists
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Steve King
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
@kwsites/promise-deferred
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 kwsites
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
@npmcli/agent
|
||||
ISC
|
||||
|
||||
@@ -3213,6 +3262,9 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
simple-git
|
||||
MIT
|
||||
|
||||
smart-buffer
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Generated
+28
@@ -16,6 +16,7 @@
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"archiver": "^6.0.1",
|
||||
"fs-extra": "^11.2.0",
|
||||
"simple-git": "^3.22.0",
|
||||
"tar": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1408,6 +1409,19 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kwsites/file-exists": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
|
||||
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@kwsites/promise-deferred": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
|
||||
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -7496,6 +7510,20 @@
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/simple-git": {
|
||||
"version": "3.24.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.24.0.tgz",
|
||||
"integrity": "sha512-QqAKee9Twv+3k8IFOFfPB2hnk6as6Y6ACUpwCtQvRYBAes23Wv3SZlHVobAzqcE8gfsisCvPw3HGW3HYM+VYYw==",
|
||||
"dependencies": {
|
||||
"@kwsites/file-exists": "^1.1.1",
|
||||
"@kwsites/promise-deferred": "^1.1.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/steveukx/git-js?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
|
||||
+2
-1
@@ -74,7 +74,8 @@
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"archiver": "^6.0.1",
|
||||
"fs-extra": "^11.2.0",
|
||||
"tar": "^6.2.0"
|
||||
"tar": "^6.2.0",
|
||||
"simple-git": "^3.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^6.0.2",
|
||||
|
||||
+2
-2
@@ -8,8 +8,6 @@ export interface PublishActionOptions {
|
||||
nameWithOwner: string
|
||||
// The GitHub token to use for API requests
|
||||
token: string
|
||||
// The commit SHA to reset back to after the action completes
|
||||
sha: string
|
||||
// The base URL for the GitHub API
|
||||
apiBaseUrl: string
|
||||
// The base URL for the GitHub Container Registry
|
||||
@@ -30,6 +28,8 @@ export interface PublishActionOptions {
|
||||
event: string
|
||||
// The ref that triggered the action, associated with the event
|
||||
ref: string
|
||||
// The commit SHA associated with the ref that triggered the action
|
||||
sha: string
|
||||
}
|
||||
|
||||
export async function resolvePublishActionOptions(): Promise<PublishActionOptions> {
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as path from 'path'
|
||||
import * as tar from 'tar'
|
||||
import * as archiver from 'archiver'
|
||||
import * as crypto from 'crypto'
|
||||
import * as simpleGit from 'simple-git'
|
||||
|
||||
export interface FileMetadata {
|
||||
path: string
|
||||
@@ -114,6 +115,31 @@ export function stageActionFiles(actionDir: string, targetDir: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the correct SHA is checked out for the tag by inspecting the git metadata in the workspace
|
||||
// and comparing it to the information actions provided us.
|
||||
// Provided ref should be in format refs/tags/<tagname>.
|
||||
export async function ensureCorrectShaCheckedOut(
|
||||
tagRef: string,
|
||||
expectedSha: string,
|
||||
gitDir: string
|
||||
): Promise<void> {
|
||||
const git: simpleGit.SimpleGit = simpleGit.simpleGit(gitDir)
|
||||
|
||||
const tagCommitSha = await git.raw(['rev-parse', '--verify', tagRef])
|
||||
if (tagCommitSha.trim() !== expectedSha) {
|
||||
throw new Error(
|
||||
`The commit associated with the tag ${tagRef} does not match the SHA of the commit provided by the actions context.`
|
||||
)
|
||||
}
|
||||
|
||||
const currentlyCheckedOutSha = await git.revparse(['HEAD'])
|
||||
if (currentlyCheckedOutSha.trim() !== expectedSha) {
|
||||
throw new Error(
|
||||
`The expected commit associated with the tag ${tagRef} is not checked out.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a file path to a filemetadata object by querying the fs for relevant metadata.
|
||||
async function fileMetadata(filePath: string): Promise<FileMetadata> {
|
||||
const stats = fs.statSync(filePath)
|
||||
|
||||
+10
-2
@@ -18,7 +18,7 @@ export async function run(): Promise<void> {
|
||||
core.info(`Publishing action package version with options:`)
|
||||
core.info(cfg.serializeOptions(options))
|
||||
|
||||
const semverTag: semver.SemVer = parseSemverTagFromRef(options.ref)
|
||||
const semverTag: semver.SemVer = await parseSemverTagFromRef(options)
|
||||
|
||||
const stagedActionFilesDir = fsHelper.createTempDir(
|
||||
options.runnerTempDir,
|
||||
@@ -77,7 +77,11 @@ export async function run(): Promise<void> {
|
||||
// This action can be triggered by any workflow that specifies a tag as its GITHUB_REF.
|
||||
// This includes releases, creating or pushing tags, or workflow_dispatch.
|
||||
// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#about-events-that-trigger-workflows.
|
||||
function parseSemverTagFromRef(ref: string): semver.SemVer {
|
||||
async function parseSemverTagFromRef(
|
||||
opts: cfg.PublishActionOptions
|
||||
): Promise<semver.SemVer> {
|
||||
const ref = opts.ref
|
||||
|
||||
if (!ref.startsWith('refs/tags/')) {
|
||||
throw new Error(`The ref ${ref} is not a valid tag reference.`)
|
||||
}
|
||||
@@ -89,6 +93,10 @@ function parseSemverTagFromRef(ref: string): semver.SemVer {
|
||||
`${rawTag} is not a valid semantic version tag, and so cannot be uploaded to the action package.`
|
||||
)
|
||||
}
|
||||
|
||||
// Ensure the correct SHA is checked out for the tag we're parsing, otherwise the bundled content will be incorrect.
|
||||
await fsHelper.ensureCorrectShaCheckedOut(ref, opts.sha, opts.workspaceDir)
|
||||
|
||||
return semverTag
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user