-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add content hashes to release #441
Changes from all commits
96c21e4
a7b30ba
eaa4d12
7d9dd53
4227b13
037e50b
010521f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
import fs from "fs"; | ||
import path from "path"; | ||
import retry from "async-retry"; | ||
import mime from "mime-types"; | ||
import { Octokit } from "@octokit/rest"; | ||
import { RequestError } from "@octokit/request-error"; | ||
import { getRepoSlugFromManifest } from "../../files/index.js"; | ||
|
@@ -185,7 +188,7 @@ export class Github { | |
} | ||
|
||
/** | ||
* Create a Github release | ||
* Create a Github release and return the release id | ||
* @param tag "v0.2.0" | ||
* @param options | ||
*/ | ||
|
@@ -195,9 +198,9 @@ export class Github { | |
body?: string; | ||
prerelease?: boolean; | ||
} | ||
): Promise<void> { | ||
): Promise<number> { | ||
const { body, prerelease } = options || {}; | ||
await this.octokit.rest.repos | ||
const release = await this.octokit.rest.repos | ||
.createRelease({ | ||
owner: this.owner, | ||
repo: this.repo, | ||
|
@@ -214,6 +217,50 @@ export class Github { | |
e.message = `Error creating release: ${e.message}`; | ||
throw e; | ||
}); | ||
|
||
return release.data.id; | ||
} | ||
|
||
async uploadReleaseAssets({ | ||
releaseId, | ||
assetsDir, | ||
matchPattern, | ||
fileNamePrefix | ||
}: { | ||
releaseId: number; | ||
assetsDir: string; | ||
matchPattern?: RegExp; | ||
fileNamePrefix?: string; | ||
}) { | ||
for (const file of fs.readdirSync(assetsDir)) { | ||
// Used to ignore duplicated legacy .tar.xz image | ||
if (matchPattern && !matchPattern.test(file)) continue; | ||
|
||
const filepath = path.resolve(assetsDir, file); | ||
const contentType = mime.lookup(filepath) || "application/octet-stream"; | ||
try { | ||
// The uploadReleaseAssetApi fails sometimes, retry 3 times | ||
await retry( | ||
async () => { | ||
await this.octokit.repos.uploadReleaseAsset({ | ||
owner: this.owner, | ||
repo: this.repo, | ||
release_id: releaseId, | ||
data: fs.createReadStream(filepath) as any, | ||
headers: { | ||
"content-type": contentType, | ||
"content-length": fs.statSync(filepath).size | ||
}, | ||
name: `${fileNamePrefix || ""}${path.basename(filepath)}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adds prefix for multivariant packages. For example: If variant is holesky for Obol package, |
||
}); | ||
}, | ||
{ retries: 3 } | ||
); | ||
} catch (e) { | ||
e.message = `Error uploading release asset: ${e.message}`; | ||
throw e; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,6 @@ import fs from "fs"; | |
import { Github } from "../../../providers/github/Github.js"; | ||
import { ListrContextPublish, TxData } from "../../../types.js"; | ||
import { ListrTask } from "listr"; | ||
import { | ||
compactManifestIfCore, | ||
composeDeleteBuildProperties | ||
} from "../../../files/index.js"; | ||
import { | ||
getInstallDnpLink, | ||
getPublishTxLink | ||
|
@@ -15,6 +11,7 @@ import { getNextGitTag } from "../getNextGitTag.js"; | |
import { contentHashFileName } from "../../../params.js"; | ||
import { ReleaseDetailsMap } from "../types.js"; | ||
import { buildReleaseDetailsMap } from "../buildReleaseDetailsMap.js"; | ||
import { compactManifestIfCore, composeDeleteBuildProperties } from "../../../files/index.js"; | ||
|
||
/** | ||
* Create release | ||
|
@@ -23,10 +20,12 @@ import { buildReleaseDetailsMap } from "../buildReleaseDetailsMap.js"; | |
*/ | ||
export function getCreateReleaseTask({ | ||
github, | ||
composeFileName | ||
composeFileName, | ||
isMultiVariant | ||
}: { | ||
github: Github; | ||
composeFileName?: string; | ||
isMultiVariant: boolean; | ||
}): ListrTask<ListrContextPublish> { | ||
return { | ||
title: `Create release`, | ||
|
@@ -38,74 +37,102 @@ export function getCreateReleaseTask({ | |
task.output = "Deleting existing release..."; | ||
await github.deleteReleaseAndAssets(tag); | ||
|
||
const contentHashPaths = await handleReleaseVariantFiles({ | ||
releaseDetailsMap, | ||
composeFileName | ||
}); | ||
|
||
task.output = `Creating release for tag ${tag}...`; | ||
await github.createRelease(tag, { | ||
const releaseId = await github.createRelease(tag, { | ||
body: await getReleaseBody({ releaseDetailsMap }), | ||
prerelease: true, // Until it is actually published to mainnet | ||
}); | ||
|
||
// Clean content hash file so the directory uploaded to IPFS is the same | ||
// as the local build_* dir. User can then `ipfs add -r` and get the same hash | ||
contentHashPaths.map(contentHashPath => fs.unlinkSync(contentHashPath)); | ||
task.output = "Preparing release directories for Github release..."; | ||
prepareGithubReleaseFiles({ releaseDetailsMap, composeFileName }); | ||
|
||
task.output = "Uploading assets..."; | ||
await uploadAssets({ releaseDetailsMap, github, releaseId, isMultiVariant }); | ||
|
||
} | ||
}; | ||
} | ||
|
||
async function handleReleaseVariantFiles({ | ||
function prepareGithubReleaseFiles({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Creates |
||
releaseDetailsMap, | ||
composeFileName | ||
}: { | ||
releaseDetailsMap: ReleaseDetailsMap; | ||
composeFileName?: string; | ||
}): Promise<string[]> { | ||
const contentHashPaths: string[] = []; | ||
|
||
for (const [, { variant, releaseDir, releaseMultiHash }] of Object.entries( | ||
releaseDetailsMap | ||
)) { | ||
if (!releaseMultiHash) { | ||
throw new Error( | ||
`Release hash not found for variant ${variant} of ${name}` | ||
); | ||
} | ||
}) { | ||
for (const [, { releaseMultiHash, releaseDir }] of Object.entries(releaseDetailsMap)) { | ||
|
||
const contentHashPath = writeContentHashToFile({ | ||
releaseDir, | ||
releaseMultiHash | ||
}); | ||
const contentHashPath = path.join(releaseDir, `${contentHashFileName}`); | ||
|
||
try { | ||
|
||
/** | ||
* Plain text file which should contain the IPFS hash of the release | ||
* Necessary for the installer script to fetch the latest content hash | ||
* of the eth clients. The resulting hashes are used by the DAPPMANAGER | ||
* to install an eth client when the user does not want to use a remote node | ||
*/ | ||
fs.writeFileSync(contentHashPath, releaseMultiHash); | ||
|
||
contentHashPaths.push(contentHashPath); | ||
compactManifestIfCore(releaseDir); | ||
composeDeleteBuildProperties({ dir: releaseDir, composeFileName }); | ||
|
||
compactManifestIfCore(releaseDir); | ||
composeDeleteBuildProperties({ dir: releaseDir, composeFileName }); | ||
} catch (e) { | ||
console.error(`Error found while preparing files in ${releaseDir} for Github release`, e); | ||
} | ||
} | ||
} | ||
|
||
return contentHashPaths; | ||
async function uploadAssets({ | ||
releaseDetailsMap, | ||
github, | ||
releaseId, | ||
isMultiVariant | ||
}: { | ||
releaseDetailsMap: ReleaseDetailsMap; | ||
github: Github; | ||
releaseId: number; | ||
isMultiVariant: boolean; | ||
}) { | ||
const releaseEntries = Object.entries(releaseDetailsMap); | ||
const [, { releaseDir: firstReleaseDir }] = releaseEntries[0]; | ||
|
||
await uploadAvatar({ github, releaseId, avatarDir: firstReleaseDir }); | ||
|
||
for (const [dnpName, { releaseDir }] of releaseEntries) { | ||
const shortDnpName = dnpName.split(".")[0]; | ||
|
||
await github.uploadReleaseAssets({ | ||
releaseId, | ||
assetsDir: releaseDir, | ||
// Only upload yml, txz and dappnode_package.json files | ||
matchPattern: /(.*\.ya?ml$)|(.*\.txz$)|(dappnode_package\.json)|(content-hash)/, | ||
fileNamePrefix: isMultiVariant ? `${shortDnpName}_` : "" | ||
}).catch((e) => { | ||
console.error(`Error uploading assets from ${releaseDir}`, e); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Plain text file which should contain the IPFS hash of the release | ||
* Necessary for the installer script to fetch the latest content hash | ||
* of the eth clients. The resulting hashes are used by the DAPPMANAGER | ||
* to install an eth client when the user does not want to use a remote node | ||
*/ | ||
function writeContentHashToFile({ | ||
releaseDir, | ||
releaseMultiHash | ||
async function uploadAvatar({ | ||
github, | ||
releaseId, | ||
avatarDir | ||
}: { | ||
releaseDir: string; | ||
releaseMultiHash: string; | ||
}): string { | ||
const contentHashPath = path.join(releaseDir, contentHashFileName); | ||
fs.writeFileSync(contentHashPath, releaseMultiHash); | ||
return contentHashPath; | ||
github: Github; | ||
releaseId: number; | ||
avatarDir: string; | ||
}): Promise<void> { | ||
await github.uploadReleaseAssets({ | ||
releaseId, | ||
assetsDir: avatarDir, | ||
matchPattern: /.*\.png/, | ||
}).catch((e) => { | ||
console.error(`Error uploading avatar from ${avatarDir}`, e); | ||
}); | ||
} | ||
|
||
|
||
/** | ||
* Write the release body | ||
* | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only change here: This funcion now returns
releaseID