Skip to content

Commit 00d69da

Browse files
committed
feat(deployment): --genesis=FILE and unique digitalocean SSH keys
1 parent 22a3f42 commit 00d69da

13 files changed

+134
-73
lines changed

packages/deployment/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ RUN echo 'deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main' >> /e
2727
# COPY --from=go-build /go/bin/journalbeat /usr/local/bin/
2828

2929
WORKDIR /usr/src/agoric-sdk/packages/deployment
30-
RUN ln -sf $PWD/ag-setup-cosmos /usr/local/bin/
30+
RUN ln -sf $PWD/src/entrypoint.cjs /usr/local/bin/ag-setup-cosmos
3131

3232
WORKDIR /data/chains
3333

packages/deployment/Dockerfile.sdk

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ RUN make GIT_REVISION="$GIT_REVISION" MOD_READONLY= compile-go
1313

1414
###############################
1515
# The js build container
16-
FROM node:12-buster AS build-js
16+
FROM node:14.15-buster AS build-js
1717
ARG MODDABLE_COMMIT_HASH
1818
ARG MODDABLE_URL
1919

@@ -39,7 +39,7 @@ RUN yarn install --frozen-lockfile --production
3939

4040
###############################
4141
# The install container.
42-
FROM node:12-buster AS install
42+
FROM node:14.15-buster AS install
4343

4444
# Install some conveniences.
4545
RUN apt-get update && apt-get install -y vim jq less && apt-get clean -y

packages/deployment/ag-setup-cosmos

-36
This file was deleted.

packages/deployment/ansible/prepare-machine.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
gather_facts: yes
77
strategy: free
88
vars:
9-
- NODEJS_VERSION: 12
9+
- NODEJS_VERSION: 14
1010
roles:
1111
- prereq

packages/deployment/ansible/roles/copy/tasks/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
- name: Synchronize Agoric SDK
1111
synchronize:
12-
src: "/usr/src/agoric-sdk/"
12+
src: "{{ AGORIC_SDK }}/"
1313
dest: "/usr/src/agoric-sdk/"
1414
dirs: yes
1515
delete: yes

packages/deployment/ansible/update_known_hosts.yml

+6
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,9 @@
2929
state: present
3030
line: "{{ item }}"
3131
with_items: "{{ ssh_keys.splitlines() }}"
32+
33+
- name: Set master authorized SSH key
34+
authorized_key:
35+
user: root
36+
state: present
37+
key: "{{ lookup('file', SETUP_HOME + '/id_ecdsa.pub') }}"

packages/deployment/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
"js": "mjs"
77
},
88
"private": true,
9-
"main": "main.js",
9+
"main": "src/main.js",
10+
"bin": {
11+
"ag-setup-cosmos": "src/entrypoint.cjs"
12+
},
1013
"scripts": {
1114
"test": "exit 0",
1215
"test:xs": "exit 0",

packages/deployment/src/entrypoint.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#! /usr/bin/env node
2+
/* global setInterval */
3+
4+
import '@agoric/install-ses';
5+
6+
import fs from 'fs';
7+
import path from 'path';
8+
import temp from 'temp';
9+
import process from 'process';
10+
import { exec, spawn } from 'child_process';
11+
import inquirer from 'inquirer';
12+
import fetch from 'node-fetch';
13+
14+
import { running } from './run';
15+
import { setup } from './setup';
16+
import * as files from './files';
17+
import deploy from './main.js';
18+
19+
process.on('SIGINT', () => process.exit(-1));
20+
deploy(process.argv[1], process.argv.splice(2), {
21+
env: process.env,
22+
rd: files.reading(fs, path),
23+
wr: files.writing(fs, path, temp),
24+
setup: setup({ resolve: path.resolve, env: process.env, setInterval }),
25+
running: running(process, { exec, process, spawn }),
26+
inquirer,
27+
fetch,
28+
}).then(
29+
res => process.exit(res || 0),
30+
rej => {
31+
console.error(`error running ag-setup-cosmos:`, rej);
32+
process.exit(1);
33+
},
34+
);
File renamed without changes.

packages/deployment/init.js packages/deployment/src/init.js

+35-14
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ const nodeCount = (count, force) => {
2525
const tfStringify = obj => {
2626
let ret = '';
2727
if (Array.isArray(obj)) {
28-
let sep = '[';
28+
ret += '[';
29+
let sep = '';
2930
for (const el of obj) {
3031
ret += sep + tfStringify(el);
3132
sep = ',';
3233
}
3334
ret += ']';
3435
} else if (Object(obj) === obj) {
35-
let sep = '{';
36+
ret += '{';
37+
let sep = '';
3638
for (const key of Object.keys(obj).sort()) {
3739
ret += `${sep}${JSON.stringify(key)}=${tfStringify(obj[key])}`;
3840
sep = ',';
@@ -149,7 +151,7 @@ module "${PLACEMENT}" {
149151
source = "${setup.SETUP_DIR}/terraform/${provider.value}"
150152
CLUSTER_NAME = "${PREFIX}\${var.NETWORK_NAME}-${PLACEMENT}"
151153
OFFSET = "\${var.OFFSETS["${PLACEMENT}"]}"
152-
SSH_KEY_FILE = "\${var.SSH_KEY_FILE}"
154+
SSH_KEY_FILE = "${PLACEMENT}-\${var.SSH_KEY_FILE}"
153155
ROLE = "\${var.ROLES["${PLACEMENT}"]}"
154156
SERVERS = "\${length(var.DATACENTERS["${PLACEMENT}"])}"
155157
VOLUMES = "\${var.VOLUMES["${PLACEMENT}"]}"
@@ -187,7 +189,8 @@ module "${PLACEMENT}" {
187189
OFFSET = "\${var.OFFSETS["${PLACEMENT}"]}"
188190
REGIONS = "\${var.DATACENTERS["${PLACEMENT}"]}"
189191
ROLE = "\${var.ROLES["${PLACEMENT}"]}"
190-
SSH_KEY_FILE = "\${var.SSH_KEY_FILE}"
192+
# TODO: DigitalOcean provider module doesn't allow reuse of SSH public keys.
193+
SSH_KEY_FILE = "${PLACEMENT}-\${var.SSH_KEY_FILE}"
191194
DO_API_TOKEN = "\${var.API_KEYS["${PLACEMENT}"]}"
192195
SERVERS = "\${length(var.DATACENTERS["${PLACEMENT}"])}"
193196
}
@@ -292,16 +295,23 @@ const doInit = ({ env, rd, wr, running, setup, inquirer, fetch }) => async (
292295
const deploymentJson = `deployment.json`;
293296
const config = (await rd.exists(deploymentJson))
294297
? JSON.parse(await rd.readFile(deploymentJson, 'utf-8'))
295-
: {
296-
PLACEMENTS: [],
297-
PLACEMENT_PROVIDER: {},
298-
SSH_PRIVATE_KEY_FILE: `id_${SSH_TYPE}`,
299-
DETAILS: {},
300-
OFFSETS: {},
301-
ROLES: {},
302-
DATACENTERS: {},
303-
PROVIDER_NEXT_INDEX: {},
304-
};
298+
: {};
299+
300+
const defaultConfigs = {
301+
PLACEMENTS: [],
302+
PLACEMENT_PROVIDER: {},
303+
SSH_PRIVATE_KEY_FILE: `id_${SSH_TYPE}`,
304+
DETAILS: {},
305+
OFFSETS: {},
306+
ROLES: {},
307+
DATACENTERS: {},
308+
PROVIDER_NEXT_INDEX: {},
309+
};
310+
Object.entries(defaultConfigs).forEach(([key, dflt]) => {
311+
if (!(key in config)) {
312+
config[key] = dflt;
313+
}
314+
});
305315
config.NETWORK_NAME = overrideNetworkName;
306316

307317
// eslint-disable-next-line no-constant-condition
@@ -514,6 +524,16 @@ variable ${JSON.stringify(vname)} {
514524
for (const PLACEMENT of Object.keys(config.PLACEMENT_PROVIDER).sort()) {
515525
const PROVIDER = config.PLACEMENT_PROVIDER[PLACEMENT];
516526
const provider = PROVIDERS[PROVIDER];
527+
528+
// Create a placement-specific key file.
529+
const keyFile = `${PLACEMENT}-${config.SSH_PRIVATE_KEY_FILE}`;
530+
// eslint-disable-next-line no-await-in-loop
531+
if (!(await rd.exists(keyFile))) {
532+
// Set empty password.
533+
// eslint-disable-next-line no-await-in-loop
534+
await needDoRun(['ssh-keygen', '-N', '', '-t', SSH_TYPE, '-f', keyFile]);
535+
}
536+
517537
// eslint-disable-next-line no-await-in-loop
518538
await provider.createPlacementFiles(provider, PLACEMENT, clusterPrefix);
519539
}
@@ -552,6 +572,7 @@ output "offsets" {
552572
#! /bin/sh
553573
exec ansible-playbook -f10 \\
554574
-eSETUP_HOME=${shellEscape(cwd())} \\
575+
-eAGORIC_SDK=${shellEscape(setup.AGORIC_SDK)} \\
555576
-eNETWORK_NAME=\`cat ${shellEscape(rd.resolve('network.txt'))}\` \\
556577
\${1+"$@"}
557578
`,

packages/deployment/main.js packages/deployment/src/main.js

+48-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { createHash } from 'crypto';
55
import chalk from 'chalk';
66
import parseArgs from 'minimist';
77
import { assert, details as X } from '@agoric/assert';
8+
import { dirname, basename } from 'path';
89
import { doInit } from './init';
910
import { shellMetaRegexp, shellEscape } from './run';
1011
import { streamFromString } from './files';
@@ -200,6 +201,7 @@ show-config display the client connection parameters
200201
default: {
201202
'boot-tokens': DEFAULT_BOOT_TOKENS,
202203
},
204+
string: ['bump', 'import-from', 'genesis'],
203205
stopEarly: true,
204206
},
205207
);
@@ -246,7 +248,7 @@ show-config display the client connection parameters
246248
case undefined: {
247249
await wr.createFile('boot-tokens.txt', bootTokens);
248250
const bootOpts = [];
249-
for (const propagate of ['bump', 'import-from']) {
251+
for (const propagate of ['bump', 'import-from', 'genesis']) {
250252
const val = subOpts[propagate];
251253
if (val !== undefined) {
252254
bootOpts.push(`--${propagate}=${val}`);
@@ -309,7 +311,7 @@ show-config display the client connection parameters
309311
await inited();
310312
// eslint-disable-next-line no-unused-vars
311313
const { _: subArgs, ...subOpts } = parseArgs(args.slice(1), {
312-
string: ['bump', 'import-from'],
314+
string: ['bump', 'import-from', 'genesis'],
313315
stopEarly: true,
314316
});
315317

@@ -339,9 +341,18 @@ show-config display the client connection parameters
339341
await guardFile(`chain-version.txt`, makeFile => makeFile('1'));
340342

341343
// Assign the chain name.
342-
const networkName = await trimReadFile('network.txt');
343-
const chainVersion = await trimReadFile('chain-version.txt');
344-
const chainName = `${networkName}-${chainVersion}`;
344+
let chainName;
345+
let genJSON;
346+
if (subOpts.genesis) {
347+
// Fetch the specified genesis, don't generate it.
348+
genJSON = await trimReadFile(subOpts.genesis);
349+
const genesis = JSON.parse(genJSON);
350+
chainName = genesis.chain_id;
351+
} else {
352+
const networkName = await trimReadFile('network.txt');
353+
const chainVersion = await trimReadFile('chain-version.txt');
354+
chainName = `${networkName}-${chainVersion}`;
355+
}
345356
const currentChainName = await trimReadFile(
346357
`${COSMOS_DIR}/chain-name.txt`,
347358
).catch(_ => undefined);
@@ -363,14 +374,25 @@ show-config display the client connection parameters
363374
await guardFile(`${COSMOS_DIR}/prepare.stamp`, () =>
364375
needReMain(['play', 'prepare-cosmos']),
365376
);
366-
await guardFile(`${COSMOS_DIR}/genesis.stamp`, () =>
367-
needReMain(['play', 'cosmos-genesis']),
368-
);
377+
378+
// If the canonical genesis exists, use it.
379+
await guardFile(`${COSMOS_DIR}/genesis.stamp`, async () => {
380+
await wr.mkdir(`${COSMOS_DIR}/data`, { recursive: true });
381+
if (genJSON) {
382+
await wr.createFile(`${COSMOS_DIR}/data/genesis.json`, genJSON);
383+
} else {
384+
await guardFile(`${COSMOS_DIR}/data/genesis.json`, async () => {
385+
await needReMain(['play', 'cosmos-genesis']);
386+
// Don't overwrite the data/genesis.json.
387+
return true;
388+
});
389+
}
390+
});
369391

370392
await guardFile(`${COSMOS_DIR}/set-defaults.stamp`, async () => {
371393
await needReMain(['play', 'cosmos-clone-config']);
372394

373-
const agoricCli = rd.resolve(__dirname, `../agoric-cli/bin/agoric`);
395+
const agoricCli = rd.resolve(__dirname, `../../agoric-cli/bin/agoric`);
374396

375397
// Apply the Agoric set-defaults to all the .dst dirs.
376398
const files = await rd.readdir(`${COSMOS_DIR}/data`);
@@ -397,13 +419,19 @@ show-config display the client connection parameters
397419
...importFlags,
398420
`${COSMOS_DIR}/data/${dst}`,
399421
]);
400-
if (i === 0) {
401-
// Make a canonical copy of the genesis.json.
402-
const data = await rd.readFile(
403-
`${COSMOS_DIR}/data/${dst}/genesis.json`,
404-
);
405-
await wr.createFile(`${COSMOS_DIR}/data/genesis.json`, data);
422+
if (i !== 0) {
423+
return;
406424
}
425+
await guardFile(
426+
`${COSMOS_DIR}/data/genesis.json`,
427+
async makeGenesis => {
428+
// Make a canonical copy of the genesis.json if there isn't one.
429+
const data = await rd.readFile(
430+
`${COSMOS_DIR}/data/${dst}/genesis.json`,
431+
);
432+
await makeGenesis(data);
433+
},
434+
);
407435
}),
408436
);
409437
});
@@ -862,6 +890,10 @@ ${name}:
862890
if (!addRole[role]) {
863891
addRole[role] = makeGroup(role, 4);
864892
}
893+
const keyFile = rd.resolve(
894+
dirname(SSH_PRIVATE_KEY_FILE),
895+
`${provider}-${basename(SSH_PRIVATE_KEY_FILE)}`,
896+
);
865897
for (let instance = 0; instance < ips.length; instance += 1) {
866898
const ip = ips[instance];
867899
const node = `${role}${offset + instance}`;
@@ -877,7 +909,7 @@ ${name}:
877909
${node}:${roleParams}
878910
ansible_host: ${ip}
879911
ansible_ssh_user: root
880-
ansible_ssh_private_key_file: '${SSH_PRIVATE_KEY_FILE}'
912+
ansible_ssh_private_key_file: '${keyFile}'
881913
ansible_python_interpreter: /usr/bin/python`;
882914
addProvider(host);
883915

File renamed without changes.

packages/deployment/setup.js packages/deployment/src/setup.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export const SSH_TYPE = 'ecdsa';
88

99
export const setup = ({ resolve, env, setInterval }) => {
1010
const it = harden({
11-
SETUP_DIR: __dirname,
11+
AGORIC_SDK: resolve(__dirname, '../../..'),
12+
SETUP_DIR: resolve(__dirname, '..'),
1213
SETUP_HOME: env.AG_SETUP_COSMOS_HOME
1314
? resolve(env.AG_SETUP_COSMOS_HOME)
1415
: resolve('.'),

0 commit comments

Comments
 (0)