Skip to content

Commit f7ce4ce

Browse files
committed
ci: deploy-to-container tool
1 parent 970ae44 commit f7ce4ce

9 files changed

Lines changed: 2142 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[*]
2+
indent_size = 2
3+
indent_style = space
4+
charset = utf-8
5+
trim_trailing_whitespace = false
6+
end_of_line = lf
7+
insert_final_newline = true

dev/deploy-to-container/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/node_modules

dev/deploy-to-container/.npmrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
audit = false
2+
fund = false
3+
save-exact = true

dev/deploy-to-container/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Datatracker Diff Tool
2+
3+
This tool facilitates testing 2 different datatracker instances (with their own database) and look for changes using the diff tool. Everything runs in docker containers.
4+
5+
The source instance will use the code from where it is run. The target instance can be a remote tag / branch / commmit or another local folder.
6+
7+
## Requirements
8+
9+
- Node `16.x` or later
10+
- Docker
11+
12+
## Usage
13+
14+
1. From the `dev/diff` directory, run the command:
15+
```sh
16+
npm install
17+
```
18+
2. Then run the command:
19+
```sh
20+
node cli
21+
```
22+
3. Follow the on-screen instructions.

dev/deploy-to-container/cli.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env node
2+
3+
import Docker from 'dockerode'
4+
import path from 'path'
5+
import fs from 'fs-extra'
6+
import tar from 'tar'
7+
import yargs from 'yargs/yargs'
8+
import { hideBin } from 'yargs/helpers'
9+
import slugify from 'slugify'
10+
import { nanoid } from 'nanoid'
11+
12+
async function main () {
13+
const basePath = process.cwd()
14+
const releasePath = path.join(basePath, 'release')
15+
const argv = yargs(hideBin(process.argv)).argv
16+
17+
// Parse branch argument
18+
let branch = argv.branch
19+
if (!branch) {
20+
throw new Error('Missing --branch argument!')
21+
}
22+
if (branch.indexOf('/') >= 0) {
23+
branch = branch.split('/')[1]
24+
}
25+
branch = slugify(branch, { lower: true, strict: true })
26+
if (branch.length < 1) {
27+
throw new Error('Branch name is empty!')
28+
}
29+
console.info(`Will use branch name "${branch}"`)
30+
31+
// Parse domain argument
32+
const domain = argv.domain
33+
if (!domain) {
34+
throw new Error('Missing --domain argument!')
35+
}
36+
const hostname = `dt-${branch}.${domain}`
37+
console.info(`Will use hostname "${hostname}"`)
38+
39+
// Connect to Docker Engine API
40+
console.info('Connecting to Docker Engine API...')
41+
const dock = new Docker()
42+
await dock.ping()
43+
console.info('Connected to Docker Engine API.')
44+
45+
// Extract release artifact
46+
console.info('Extracting release artifact...')
47+
if (!(await fs.pathExists(path.join(basePath, 'release.tar.gz')))) {
48+
throw new Error('Missing release.tar.gz file!')
49+
}
50+
await fs.emptyDir(releasePath)
51+
await tar.x({
52+
cwd: releasePath,
53+
file: 'release.tar.gz'
54+
})
55+
console.info('Extracted release artifact successfully.')
56+
57+
// Update the settings_local.py file
58+
console.info('Setting configuration files...')
59+
const settingsPath = path.join(releasePath, 'ietf/settings_local.py')
60+
const cfgRaw = await fs.readFile(path.join(basePath, 'dev/deploy-to-container/settings_local.py'), 'utf8')
61+
await fs.outputFile(settingsPath, cfgRaw.replace('__DBHOST__', `dt-db-${branch}`).replace('__SECRETKEY__', nanoid(36)))
62+
await fs.copy(path.join(basePath, 'docker/scripts/app-create-dirs.sh'), path.join(releasePath, 'app-create-dirs.sh'))
63+
await fs.copy(path.join(basePath, 'dev/deploy-to-container/start.sh'), path.join(releasePath, 'start.sh'))
64+
console.info('Updated configuration files.')
65+
66+
// Pull latest DB image
67+
console.info('Pulling latest DB docker image...')
68+
const dbImagePullStream = await dock.pull('ghcr.io/ietf-tools/datatracker-db:latest')
69+
await new Promise((resolve, reject) => {
70+
dock.modem.followProgress(dbImagePullStream, (err, res) => err ? reject(err) : resolve(res))
71+
})
72+
console.info('Pulled latest DB docker image successfully.')
73+
74+
// Pull latest Datatracker Base image
75+
console.info('Pulling latest Datatracker base docker image...')
76+
const appImagePullStream = await dock.pull('ghcr.io/ietf-tools/datatracker-app-base:latest')
77+
await new Promise((resolve, reject) => {
78+
dock.modem.followProgress(appImagePullStream, (err, res) => err ? reject(err) : resolve(res))
79+
})
80+
console.info('Pulled latest Datatracker base docker image.')
81+
82+
// Terminate existing containers
83+
console.info('Ensuring existing containers with same name are terminated...')
84+
const containers = await dock.listContainers({ all: true })
85+
for (const container of containers) {
86+
if (container.Names.includes(`/dt-db-${branch}`) || container.Names.includes(`/dt-app-${branch}`)) {
87+
console.info(`Terminating old container ${container.Id}...`)
88+
const oldContainer = dock.getContainer(container.Id)
89+
if (container.State === 'running') {
90+
await oldContainer.stop({ t: 5 })
91+
}
92+
await oldContainer.remove({ force: true })
93+
}
94+
}
95+
console.info('Existing containers with same name have been terminated.')
96+
97+
// Create DB container
98+
console.info(`Creating DB docker container... [dt-db-${branch}]`)
99+
const dbContainer = await dock.createContainer({
100+
Image: 'ghcr.io/ietf-tools/datatracker-db:latest',
101+
name: `dt-db-${branch}`,
102+
Hostname: `dt-db-${branch}`,
103+
HostConfig: {
104+
NetworkMode: 'bridge',
105+
RestartPolicy: {
106+
Name: 'unless-stopped'
107+
}
108+
}
109+
})
110+
await dbContainer.start()
111+
console.info('Created and started DB docker container successfully.')
112+
113+
// Create Datatracker container
114+
console.info(`Creating Datatracker docker container... [dt-app-${branch}]`)
115+
const appContainer = await dock.createContainer({
116+
Image: 'ghcr.io/ietf-tools/datatracker-app-base:latest',
117+
name: `dt-app-${branch}`,
118+
Hostname: `dt-app-${branch}`,
119+
Env: [
120+
`LETSENCRYPT_HOST=${hostname}`,
121+
`VIRTUAL_HOST=${hostname}`,
122+
`VIRTUAL_PORT=8000`
123+
],
124+
HostConfig: {
125+
NetworkMode: 'bridge',
126+
RestartPolicy: {
127+
Name: 'unless-stopped'
128+
}
129+
},
130+
Entrypoint: ['bash', '-c', 'chmod +x ./start.sh && ./start.sh']
131+
})
132+
console.info(`Created Datatracker docker container successfully.`)
133+
134+
// Inject updated release into container
135+
console.info('Building updated release tarball to inject into container...')
136+
const tgzPath = path.join(basePath, 'import.tgz')
137+
await tar.c({
138+
gzip: true,
139+
file: tgzPath,
140+
cwd: releasePath,
141+
filter (path) {
142+
if (path.includes('.git') || path.includes('node_modules')) { return false }
143+
return true
144+
}
145+
}, ['.'])
146+
console.info('Injecting archive into Datatracker docker container...')
147+
await appContainer.putArchive(tgzPath, {
148+
path: '/workspace'
149+
})
150+
await fs.remove(tgzPath)
151+
console.info(`Imported working files into Datatracker docker container successfully.`)
152+
153+
console.info('Starting Datatracker container...')
154+
await appContainer.start()
155+
console.info('Datatracker container started successfully.')
156+
157+
process.exit(0)
158+
}
159+
160+
main()

0 commit comments

Comments
 (0)