-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1640 from serlo/staging
Deployment
- Loading branch information
Showing
25 changed files
with
407 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
name: Push DB migration image | ||
on: | ||
# not a problem if we do it at every push because it will check if the image already exists | ||
push: | ||
|
||
jobs: | ||
docker-image: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: serlo/configure-repositories/actions/setup-node@main | ||
- uses: google-github-actions/auth@v2 | ||
with: | ||
credentials_json: '${{ secrets.GCP_KEY_CONTAINER_REGISTRY }}' | ||
- run: gcloud auth configure-docker | ||
- uses: google-github-actions/setup-gcloud@v2 | ||
- run: yarn migrate:push-image |
Binary file not shown.
Binary file not shown.
Binary file added
BIN
+5.41 KB
.yarn/cache/@msgpackr-extract-msgpackr-extract-darwin-arm64-npm-3.0.3-23b9647943-8.zip
Binary file not shown.
Binary file added
BIN
+11.2 KB
.yarn/cache/@msgpackr-extract-msgpackr-extract-linux-arm64-npm-3.0.3-9da3d01c14-8.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
FROM node:20-alpine AS base_image | ||
WORKDIR /app | ||
COPY package.json . | ||
RUN corepack enable | ||
RUN yarn set version 3.x | ||
|
||
FROM base_image AS build_migrations | ||
COPY scripts scripts | ||
COPY src src | ||
RUN yarn | ||
RUN yarn build:all | ||
|
||
FROM base_image AS runner | ||
RUN yarn plugin import workspace-tools | ||
RUN yarn workspaces focus --production | ||
COPY --from=build_migrations /app/migrations migrations | ||
COPY migrations/package.json migrations/package.json | ||
COPY database.json . | ||
|
||
ENTRYPOINT ["yarn", "db-migrate"] | ||
CMD ["up"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { spawnSync } from 'child_process' | ||
import * as fs from 'fs' | ||
import * as path from 'path' | ||
import * as R from 'ramda' | ||
import * as semver from 'semver' | ||
import { fileURLToPath } from 'url' | ||
import * as util from 'util' | ||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url)) | ||
const root = path.join(__dirname, '..') | ||
const packageJsonPath = path.join(root, 'package.json') | ||
|
||
const fsOptions: { encoding: BufferEncoding } = { encoding: 'utf-8' } | ||
|
||
const readFile = util.promisify(fs.readFile) | ||
|
||
void run() | ||
|
||
async function run() { | ||
const { version } = await fetchPackageJSON() | ||
buildDockerImage({ | ||
name: 'api-db-migration', | ||
version, | ||
Dockerfile: path.join(root, 'Dockerfile'), | ||
context: '.', | ||
}) | ||
} | ||
|
||
function fetchPackageJSON(): Promise<{ version: string }> { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
return readFile(packageJsonPath, fsOptions).then(JSON.parse) | ||
} | ||
|
||
export function buildDockerImage({ | ||
name, | ||
version, | ||
Dockerfile, | ||
context, | ||
}: { | ||
name: string | ||
version: string | ||
Dockerfile: string | ||
context: string | ||
}) { | ||
const semanticVersion = semver.parse(version) | ||
|
||
if (!semanticVersion) { | ||
throw new Error(`illegal version number ${version}`) | ||
} | ||
|
||
const remoteName = `eu.gcr.io/serlo-shared/${name}` | ||
|
||
if (!shouldBuild()) { | ||
// eslint-disable-next-line no-console | ||
console.log( | ||
`Skipping deployment: ${remoteName}:${version} already in registry`, | ||
) | ||
return | ||
} | ||
|
||
const versions = getTargetVersions(semanticVersion).map((t) => t.toString()) | ||
|
||
runBuild(versions) | ||
pushTags(versions) | ||
|
||
function shouldBuild() { | ||
const args = [ | ||
'container', | ||
'images', | ||
'list-tags', | ||
remoteName, | ||
'--filter', | ||
`tags=${version}`, | ||
'--format', | ||
'json', | ||
] | ||
|
||
const result = spawnSync('gcloud', args, { stdio: 'pipe' }) | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
const images = JSON.parse(String(result.stdout)) | ||
|
||
if (!Array.isArray(images)) | ||
throw new Error('Wrong response from google cloud') | ||
|
||
return images.length === 0 | ||
} | ||
|
||
function runBuild(versions: string[]) { | ||
const tags = [...toTags(name, versions), ...toTags(remoteName, versions)] | ||
const args = [ | ||
'build', | ||
'-f', | ||
Dockerfile, | ||
...tags.flatMap((tag) => ['-t', tag]), | ||
context, | ||
] | ||
const result = spawnSync('docker', args, { stdio: 'inherit' }) | ||
|
||
if (result.status !== 0) throw new Error(`Error while building ${name}`) | ||
} | ||
|
||
function pushTags(versions: string[]) { | ||
toTags(remoteName, versions).forEach((remoteTag) => { | ||
// eslint-disable-next-line no-console | ||
console.log('Pushing', remoteTag) | ||
const result = spawnSync('docker', ['push', remoteTag], { | ||
stdio: 'inherit', | ||
}) | ||
if (result.status !== 0) | ||
throw new Error(`Error while pushing ${remoteTag}`) | ||
}) | ||
} | ||
} | ||
|
||
function getTargetVersions(version: semver.SemVer) { | ||
const { major, minor, patch, prerelease } = version | ||
|
||
return prerelease.length > 0 | ||
? R.range(0, prerelease.length).map( | ||
(i) => | ||
`${major}.${minor}.${patch}-${prerelease.slice(0, i + 1).join('.')}`, | ||
) | ||
: ['latest', `${major}`, `${major}.${minor}`, `${major}.${minor}.${patch}`] | ||
} | ||
|
||
function toTags(name: string, versions: string[]) { | ||
return versions.map((version) => `${name}:${version}`) | ||
} |
102 changes: 102 additions & 0 deletions
102
packages/db-migrations/src/20240624000000-replace-links-to-course-pages.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import * as t from 'io-ts' | ||
import * as R from 'ramda' | ||
|
||
import { | ||
ApiCache, | ||
Database, | ||
migrateSerloEditorContent, | ||
transformPlugins, | ||
} from './utils' | ||
|
||
const TextPlugin = t.type({ | ||
plugin: t.literal('text'), | ||
state: t.array(t.unknown), | ||
}) | ||
|
||
interface CoursePage { | ||
coursePageId: number | ||
courseId: number | ||
} | ||
|
||
const Link = t.type({ | ||
type: t.literal('a'), | ||
href: t.string, | ||
children: t.array(t.unknown), | ||
}) | ||
|
||
export async function up(db: Database) { | ||
const apiCache = new ApiCache() | ||
|
||
const coursePages = await db.runSql<CoursePage[]>(` | ||
SELECT | ||
entity.id AS coursePageId, | ||
ent2.id AS courseId | ||
FROM entity | ||
JOIN entity_link ON entity.id = entity_link.child_id | ||
JOIN entity ent2 ON entity_link.parent_id = ent2.id | ||
JOIN uuid ON entity.id = uuid.id | ||
WHERE entity.type_id = 8 | ||
AND uuid.trashed = 0 | ||
AND entity.current_revision_id IS NOT NULL | ||
`) | ||
|
||
await migrateSerloEditorContent({ | ||
apiCache, | ||
db, | ||
migrationName: 'replace-links-to-course-pages', | ||
migrateState: transformPlugins({ | ||
text: (plugin) => { | ||
if (!TextPlugin.is(plugin)) return undefined | ||
|
||
const pluginState = plugin.state | ||
if (!pluginState || !pluginState.length) return undefined | ||
|
||
const clonedState = structuredClone(pluginState) | ||
|
||
replaceLinks(clonedState, coursePages) | ||
|
||
if (!R.equals(clonedState, pluginState)) { | ||
return [{ ...plugin, state: clonedState }] | ||
} | ||
|
||
return [plugin] | ||
}, | ||
}), | ||
}) | ||
|
||
await apiCache.deleteKeysAndQuit() | ||
} | ||
|
||
function replaceLinks(object: object, coursePages: CoursePage[]) { | ||
if (Link.is(object)) { | ||
const startsWithSlash = object.href.at(0) === '/' | ||
const containsSerlo = object.href.includes('serlo') | ||
const isAnAttachment = object.href.startsWith('/attachment/') | ||
|
||
if ((startsWithSlash || containsSerlo) && !isAnAttachment) { | ||
coursePages.forEach((coursePage) => { | ||
const { coursePageId, courseId } = coursePage | ||
const regex = new RegExp(`/${coursePageId}(?:/|$)`) | ||
|
||
if (regex.test(object.href)) { | ||
const isFirstPage = | ||
coursePages | ||
.filter((page) => page.courseId === courseId) | ||
.sort((a, b) => a.coursePageId - b.coursePageId)[0] | ||
.coursePageId === coursePageId | ||
if (isFirstPage) { | ||
object.href = `/${courseId}` | ||
} else { | ||
object.href = `/${courseId}#${coursePageId}` | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
Object.values(object).forEach((value: unknown) => { | ||
if (typeof value === 'object' && value !== null) { | ||
replaceLinks(value, coursePages) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.