From 7c965aebec9a47c70ed361e8f0d1dcfca53b5b15 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Sat, 28 Jan 2023 01:43:10 +0100 Subject: [PATCH] use Octokit client to download buildx Signed-off-by: CrazyMax --- .github/workflows/test.yml | 2 + __tests__/buildx.test.ts | 2 +- __tests__/github.test.ts | 8 ++- action.yml | 4 ++ dev.Dockerfile | 3 +- docker-bake.hcl | 1 + package.json | 2 +- src/buildx.ts | 10 +-- src/context.ts | 4 +- src/github.ts | 37 ++++++++-- src/main.ts | 2 +- yarn.lock | 143 ++++++++++++++++++++++++++++++++++++- 12 files changed, 200 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4da49a3..5d442a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,8 @@ jobs: uses: docker/bake-action@v2 with: targets: test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload coverage uses: codecov/codecov-action@v3 diff --git a/__tests__/buildx.test.ts b/__tests__/buildx.test.ts index 2399865..af3b944 100644 --- a/__tests__/buildx.test.ts +++ b/__tests__/buildx.test.ts @@ -221,7 +221,7 @@ describe('install', () => { ])( 'acquires %p of buildx (standalone: %p)', async (version, standalone) => { - const buildxBin = await buildx.install(version, tmpDir, standalone); + const buildxBin = await buildx.install(version, process.env.GITHUB_TOKEN || '', tmpDir, standalone); expect(fs.existsSync(buildxBin)).toBe(true); }, 100000 diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts index b5c532f..0fa8037 100644 --- a/__tests__/github.test.ts +++ b/__tests__/github.test.ts @@ -3,14 +3,18 @@ import * as github from '../src/github'; describe('github', () => { it('returns latest buildx GitHub release', async () => { - const release = await github.getRelease('latest'); + const release = await github.getLatestRelease(process.env.GITHUB_TOKEN || ''); expect(release).not.toBeNull(); expect(release?.tag_name).not.toEqual(''); }); it('returns v0.2.2 buildx GitHub release', async () => { - const release = await github.getRelease('v0.2.2'); + const release = await github.getReleaseTag('v0.2.2', process.env.GITHUB_TOKEN || ''); expect(release).not.toBeNull(); expect(release?.tag_name).toEqual('v0.2.2'); }); + + it('unknown release', async () => { + await expect(github.getReleaseTag('foo', process.env.GITHUB_TOKEN || '')).rejects.toThrowError(new Error('Cannot get release foo: HttpError: Not Found')); + }); }); diff --git a/action.yml b/action.yml index 885679e..1453236 100644 --- a/action.yml +++ b/action.yml @@ -44,6 +44,10 @@ inputs: append: description: 'Append additional nodes to the builder' required: false + github_token: + description: 'The GitHub token used to create an authenticated client for GitHub API' + default: ${{ github.token }} + required: false outputs: name: diff --git a/dev.Dockerfile b/dev.Dockerfile index 43b44a1..8d92a0d 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -72,7 +72,8 @@ RUN --mount=type=bind,target=.,rw \ --mount=type=cache,target=/src/node_modules \ --mount=type=bind,from=docker,source=/usr/local/bin/docker,target=/usr/bin/docker \ --mount=type=bind,from=buildx,source=/buildx,target=/usr/libexec/docker/cli-plugins/docker-buildx \ - yarn run test --coverageDirectory=/tmp/coverage + --mount=type=secret,id=GITHUB_TOKEN \ + GITHUB_TOKEN=$(cat /run/secrets/GITHUB_TOKEN) yarn run test --coverageDirectory=/tmp/coverage FROM scratch AS test-coverage COPY --from=test /tmp/coverage / diff --git a/docker-bake.hcl b/docker-bake.hcl index 2942972..b64f6c6 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -50,4 +50,5 @@ target "test" { dockerfile = "dev.Dockerfile" target = "test-coverage" output = ["./coverage"] + secret = ["id=GITHUB_TOKEN,env=GITHUB_TOKEN"] } diff --git a/package.json b/package.json index 7cf7bd7..e4f928c 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "dependencies": { "@actions/core": "^1.10.0", "@actions/exec": "^1.1.1", - "@actions/http-client": "^2.0.1", + "@actions/github": "^5.1.1", "@actions/tool-cache": "^2.0.1", "csv-parse": "^5.3.3", "js-yaml": "^4.1.0", diff --git a/src/buildx.ts b/src/buildx.ts index ebaffef..f3e7d19 100644 --- a/src/buildx.ts +++ b/src/buildx.ts @@ -237,10 +237,12 @@ export async function build(inputBuildRef: string, dest: string, standalone: boo return setPlugin(toolPath, dest); } -export async function install(inputVersion: string, dest: string, standalone: boolean): Promise { - const release: github.GitHubRelease | null = await github.getRelease(inputVersion); - if (!release) { - throw new Error(`Cannot find buildx ${inputVersion} release`); +export async function install(inputVersion: string, githubToken: string, dest: string, standalone: boolean): Promise { + let release: github.Release; + if (inputVersion == 'latest') { + release = await github.getLatestRelease(githubToken); + } else { + release = await github.getReleaseTag(inputVersion, githubToken); } core.debug(`Release ${release.tag_name} found`); const version = release.tag_name.replace(/^v+|v+$/g, ''); diff --git a/src/context.ts b/src/context.ts index 5146bc1..6a863d5 100644 --- a/src/context.ts +++ b/src/context.ts @@ -36,6 +36,7 @@ export interface Inputs { config: string; configInline: string; append: string; + githubToken: string; } export async function getInputs(): Promise { @@ -51,7 +52,8 @@ export async function getInputs(): Promise { endpoint: core.getInput('endpoint'), config: core.getInput('config'), configInline: core.getInput('config-inline'), - append: core.getInput('append') + append: core.getInput('append'), + githubToken: core.getInput('github_token') }; } diff --git a/src/github.ts b/src/github.ts index 76edfe9..a1a2757 100644 --- a/src/github.ts +++ b/src/github.ts @@ -1,12 +1,37 @@ -import * as httpm from '@actions/http-client'; +import * as github from '@actions/github'; -export interface GitHubRelease { +export interface Release { id: number; tag_name: string; } -export const getRelease = async (version: string): Promise => { - const url = `https://github.com/docker/buildx/releases/${version}`; - const http: httpm.HttpClient = new httpm.HttpClient('setup-buildx'); - return (await http.getJson(url)).result; +const [owner, repo] = 'docker/buildx'.split('/'); + +export const getReleaseTag = async (tag: string, githubToken: string): Promise => { + return ( + await github + .getOctokit(githubToken) + .rest.repos.getReleaseByTag({ + owner, + repo, + tag + }) + .catch(error => { + throw new Error(`Cannot get release ${tag}: ${error}`); + }) + ).data as Release; +}; + +export const getLatestRelease = async (githubToken: string): Promise => { + return ( + await github + .getOctokit(githubToken) + .rest.repos.getLatestRelease({ + owner, + repo + }) + .catch(error => { + throw new Error(`Cannot get latest release: ${error}`); + }) + ).data as Release; }; diff --git a/src/main.ts b/src/main.ts index 46f54d1..b737ed9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -42,7 +42,7 @@ async function run(): Promise { core.endGroup(); } else if (!(await buildx.isAvailable(standalone)) || inputs.version) { core.startGroup(`Download and install buildx`); - await buildx.install(inputs.version || 'latest', standalone ? context.tmpDir() : dockerConfigHome, standalone); + await buildx.install(inputs.version || 'latest', inputs.githubToken, standalone ? context.tmpDir() : dockerConfigHome, standalone); core.endGroup(); } diff --git a/yarn.lock b/yarn.lock index 36ff79c..1d78434 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,6 +17,16 @@ dependencies: "@actions/io" "^1.0.1" +"@actions/github@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@actions/github/-/github-5.1.1.tgz#40b9b9e1323a5efcf4ff7dadd33d8ea51651bbcb" + integrity sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g== + dependencies: + "@actions/http-client" "^2.0.1" + "@octokit/core" "^3.6.0" + "@octokit/plugin-paginate-rest" "^2.17.0" + "@octokit/plugin-rest-endpoint-methods" "^5.13.0" + "@actions/http-client@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.0.1.tgz#873f4ca98fe32f6839462a6f046332677322f99c" @@ -801,6 +811,92 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@octokit/auth-token@^2.4.4": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" + integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/core@^3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.6.0.tgz#3376cb9f3008d9b3d110370d90e0a1fcd5fe6085" + integrity sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.3" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== + dependencies: + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.5.8": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" + integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== + dependencies: + "@octokit/request" "^5.6.0" + "@octokit/types" "^6.0.3" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^12.11.0": + version "12.11.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-12.11.0.tgz#da5638d64f2b919bca89ce6602d059f1b52d3ef0" + integrity sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ== + +"@octokit/plugin-paginate-rest@^2.17.0": + version "2.21.3" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz#7f12532797775640dbb8224da577da7dc210c87e" + integrity sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw== + dependencies: + "@octokit/types" "^6.40.0" + +"@octokit/plugin-rest-endpoint-methods@^5.13.0": + version "5.16.2" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz#7ee8bf586df97dd6868cf68f641354e908c25342" + integrity sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw== + dependencies: + "@octokit/types" "^6.39.0" + deprecation "^2.3.1" + +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== + dependencies: + "@octokit/types" "^6.0.3" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^5.6.0", "@octokit/request@^5.6.3": + version "5.6.3" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.3.tgz#19a022515a5bba965ac06c9d1334514eb50c48b0" + integrity sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.7" + universal-user-agent "^6.0.0" + +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.39.0", "@octokit/types@^6.40.0": + version "6.41.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.41.0.tgz#e58ef78d78596d2fb7df9c6259802464b5f84a04" + integrity sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg== + dependencies: + "@octokit/openapi-types" "^12.11.0" + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -1234,6 +1330,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +before-after-hook@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1508,6 +1609,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -2099,6 +2205,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" @@ -2787,6 +2898,13 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +node-fetch@^2.6.7: + version "2.6.8" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e" + integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg== + dependencies: + whatwg-url "^5.0.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -2819,7 +2937,7 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -once@^1.3.0: +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -3312,6 +3430,11 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + ts-jest@^27.1.2: version "27.1.3" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.3.tgz#1f723e7e74027c4da92c0ffbd73287e8af2b2957" @@ -3403,6 +3526,11 @@ typescript@^4.4.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -3470,6 +3598,11 @@ walker@^1.0.7: dependencies: makeerror "1.0.x" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -3492,6 +3625,14 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^8.0.0, whatwg-url@^8.5.0: version "8.6.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.6.0.tgz#27c0205a4902084b872aecb97cf0f2a7a3011f4c"