Skip to content

Commit

Permalink
fix(fetch-engine): faster and more safe getLatestAlphaTag
Browse files Browse the repository at this point in the history
  • Loading branch information
timsuchanek committed Jun 8, 2020
1 parent 6ebbce7 commit 26b8dfc
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 119 deletions.
2 changes: 1 addition & 1 deletion src/packages/fetch-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"husky": "4.2.5",
"jest": "25.5.4",
"lint-staged": "10.2.7",
"p-queue": "^6.4.0",
"prettier": "2.0.5",
"ts-jest": "25.5.1",
"typescript": "3.9.3"
Expand All @@ -33,7 +34,6 @@
"execa": "^4.0.0",
"find-cache-dir": "^3.3.1",
"hasha": "^5.2.0",
"htmlparser2": "^4.1.0",
"http-proxy-agent": "^4.0.1",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.0.2",
Expand Down
196 changes: 82 additions & 114 deletions src/packages/fetch-engine/src/getLatestAlphaTag.ts
Original file line number Diff line number Diff line change
@@ -1,132 +1,100 @@
const htmlparser = require('htmlparser2') // eslint-disable-line @typescript-eslint/no-var-requires
import fetch from 'node-fetch'
import { getProxyAgent } from './getProxyAgent'
import { getDownloadUrl } from './util'
import { platforms } from '@prisma/get-platform'
import PQueue from 'p-queue'

export async function getLatestAlphaTag(): Promise<any> {
const objects = []
let isTruncated = false
let nextContinuationToken: string | undefined = undefined

do {
const url = getUrl(nextContinuationToken)
const xml = await fetch(url, { agent: getProxyAgent(url) }).then((res) =>
res.text(),
)
const result = await getObjects(xml)
isTruncated = result.isTruncated
nextContinuationToken = result.nextContinuationToken
objects.push(...result.objects)
} while (isTruncated && nextContinuationToken)

return findLatestAlphaTag(objects)
}

function getUrl(nextContinuationToken?: string): string {
const prefix = process.env.PATCH_BRANCH ?? `master`
let url = `https://prisma-builds.s3-eu-west-1.amazonaws.com/?list-type=2&prefix=${prefix}`

if (nextContinuationToken) {
url += `&continuation-token=${encodeURIComponent(nextContinuationToken)}`
const url = `https://api.github.com/repos/prisma/prisma-engines/commits`
const result = await fetch(url, {
agent: getProxyAgent(url),
} as any).then((res) => res.json())
const commits = result.map((r) => r.sha)
const commit = await getFirstExistingCommit(commits)
const queue = new PQueue({ concurrency: 30 })
const promises = []
const excludedPlatforms = [
'freebsd',
'arm',
'linux-nixos',
'openbsd',
'netbsd',
]
const relevantPlatforms = platforms.filter(
(p) => !excludedPlatforms.includes(p),
)
for (const platform of relevantPlatforms) {
for (const engine of [
'query-engine',
'introspection-engine',
'migration-engine',
'prisma-fmt',
]) {
for (const extension of [
'.gz',
'.gz.sha256',
'.gz.sig',
'.sig',
'.sha256',
]) {
const downloadUrl = getDownloadUrl(
'master',
commit,
platform,
engine,
extension,
)
promises.push(
queue.add(async () => ({
downloadUrl,
exists: await urlExists(downloadUrl),
})),
)
}
}
}

return url
}
// wait for all queue items to finish
const exist: Array<{
downloadUrl: string
exists: boolean
}> = await Promise.all(promises)

async function getObjects(
xml,
): Promise<{
objects: Array<any>
isTruncated: boolean
nextContinuationToken: string | null
}> {
return new Promise((resolve) => {
const parser = new htmlparser.Parser(
new htmlparser.DomHandler((err, result) => {
const bucketTag = result.find(
(child) => child.name === 'listbucketresult',
)
if (!bucketTag) {
resolve({
objects: [],
isTruncated: false,
nextContinuationToken: null,
})
}
const isTruncated = getKey(bucketTag, 'istruncated')
const nextContinuationToken = getKey(bucketTag, 'nextcontinuationtoken')
resolve({
objects: bucketTag.children
.filter((c) => c.name === 'contents')
.map((child) => {
return child.children.reduce((acc, curr) => {
acc[curr.name] = curr.children[0].data
return acc
}, {})
}),
isTruncated,
nextContinuationToken,
})
}),
const missing = exist.filter((e) => !e.exists)
if (missing.length > 0) {
throw new Error(
`Could not get new alpha tag, as some assets don't exist: ${missing
.map((m) => m.downloadUrl)
.join(', ')}`,
)
parser.write(xml)
parser.end()
})
}

function findLatestAlphaTag(objects): any {
// look for the darwin build, as it always finishes last
objects = objects.filter((o) => o.key.includes('darwin'))
objects.sort((a, b) => {
// sort beta to the complete end
if (!a.key.startsWith('master') || a.key.startsWith('master/latest')) {
return 1
}
if (!b.key.startsWith('master') || b.key.startsWith('master/latest')) {
return -1
}
// sort last modified descending
return new Date(a.lastmodified) < new Date(b.lastmodified) ? 1 : -1
})
return objects[0].key.split('/')[1]
}

function getKey(parentTag, key): any {
if (!parentTag) {
return null
}
const tag = parentTag.children.find((c) => c.name === key)
if (tag) {
return serializeTag(tag)
}

return null
return commit
}

function serializeTag(tag): any {
if (tag.children) {
return tag.children
.map((c) => {
if (typeof c.data !== 'undefined') {
return serializeData(c.data)
}
if (c.children) {
return serializeTag(c)
}
return null
})
.join('')
async function getFirstExistingCommit(commits: string[]): Promise<string> {
for (const commit of commits) {
const exists = await urlExists(
getDownloadUrl('master', commits[0], 'darwin', 'query-engine'),
)
if (exists) {
return commit
}
}
return null
}

function serializeData(data): boolean {
if (data === 'false') {
return false
}
async function urlExists(url) {
try {
const res = await fetch(url, {
method: 'HEAD',
})

if (data === 'true') {
return true
const headers = Object.fromEntries(res.headers.entries())
if (parseInt(headers['content-length']) > 0) {
return res.status < 300
}
} catch (e) {
//
}

return data
return false
}
5 changes: 3 additions & 2 deletions src/packages/fetch-engine/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ export function getDownloadUrl(
version: string,
platform: string,
binaryName: string,
extension = '.gz',
): string {
const extension = platform === 'windows' ? '.exe.gz' : '.gz'
const finalExtension = platform === 'windows' ? `.exe${extension}` : extension
const baseUrl =
process.env.PRISMA_BINARIES_MIRROR || 'https://binaries.prisma.sh'
return `${baseUrl}/${channel}/${version}/${platform}/${binaryName}${extension}`
return `${baseUrl}/${channel}/${version}/${platform}/${binaryName}${finalExtension}`
}
25 changes: 23 additions & 2 deletions src/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 26b8dfc

Please sign in to comment.