diff --git a/CHANGELOG.md b/CHANGELOG.md index 47efc3d..8f4dd16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ ## Fixes and improvements - +# 3.2.0: +## Added features +- Add `hosting` command to setup BitBucket hosting with service user + # 3.1.0 ## Added features - Ability to `replay` service runs from `queue` diff --git a/dist/windows.zip b/dist/windows.zip index 8170ef7..9f4e476 100644 Binary files a/dist/windows.zip and b/dist/windows.zip differ diff --git a/executors.js b/executors.js index 8c65e15..c93b4a8 100644 --- a/executors.js +++ b/executors.js @@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.do_delete_org = exports.do_delete_group = exports.do_delete_service = exports.do_event = exports.do_spending = exports.do_remove_auto_approve = exports.do_auto_approve = exports.do_attach_role = exports.do_join = exports.do_post = exports.do_help = exports.do_queue_time = exports.printTableHeader = exports.alignLeft = exports.alignRight = exports.do_cron = exports.do_envvar = exports.do_key = exports.do_replay = exports.do_build = exports.do_redeploy = exports.do_deploy = exports.generateNewKey = exports.useExistingKey = exports.do_register = exports.addKnownHost = exports.do_duplicate = exports.fetch_template = exports.createService = exports.createServiceGroup = exports.createOrganization = exports.do_clone = exports.do_fetch = void 0; +exports.do_bitbucket = exports.do_create_deployment_agent = exports.do_delete_org = exports.do_delete_group = exports.do_delete_service = exports.do_event = exports.do_spending = exports.do_remove_auto_approve = exports.do_auto_approve = exports.do_attach_role = exports.do_join = exports.do_post = exports.do_help = exports.do_queue_time = exports.printTableHeader = exports.alignLeft = exports.alignRight = exports.do_cron = exports.do_envvar = exports.do_key = exports.do_replay = exports.do_build = exports.do_redeploy = exports.do_deploy = exports.generateNewKey = exports.useExistingKey = exports.do_register = exports.addKnownHost = exports.do_duplicate = exports.fetch_template = exports.createService = exports.createServiceGroup = exports.createOrganization = exports.do_clone = exports.do_fetch = exports.SPECIAL_FOLDERS = void 0; const fs_1 = __importDefault(require("fs")); const os_1 = __importDefault(require("os")); const utils_1 = require("./utils"); @@ -24,6 +24,7 @@ const path_1 = __importDefault(require("path")); const args_1 = require("./args"); const process_1 = require("process"); const secret_lib_1 = require("@merrymake/secret-lib"); +exports.SPECIAL_FOLDERS = ["event-catalogue", "public"]; function clone(struct, name) { return __awaiter(this, void 0, void 0, function* () { try { @@ -37,21 +38,21 @@ function clone(struct, name) { yield (0, utils_1.execPromise)(`git init --initial-branch=main`, dir); yield (0, utils_1.execPromise)(`git remote add origin "${config_1.GIT_HOST}/${name}/public"`, dir); // await execPromise(`git fetch`, dir); - fetch(`./${name}`, name, struct); + fetch(`./${name}/`, struct, (path, team, service) => createServiceFolder(path, name, team, service)); } catch (e) { throw e; } }); } -function fetch(prefix, org, struct) { +function fetch(prefix, struct, func) { return __awaiter(this, void 0, void 0, function* () { try { let keys = Object.keys(struct); for (let i = 0; i < keys.length; i++) { let group = keys[i]; - fs_1.default.mkdirSync(`${prefix}/${group}`, { recursive: true }); - yield createFolderStructure(struct[group], `${prefix}/${group}`, org, group); + fs_1.default.mkdirSync(`${prefix}${group}`, { recursive: true }); + yield createFolderStructure(struct[group], `${prefix}${group}`, group, func); } } catch (e) { @@ -70,7 +71,7 @@ function do_fetch() { } (0, utils_1.output2)(`Fetching...`); let structure = JSON.parse(reply); - yield fetch(org.pathToRoot, org.org.name, structure); + yield fetch(org.pathToRoot, structure, (path, team, service) => createServiceFolder(path, org.org.name, team, service)); } catch (e) { throw e; @@ -78,43 +79,47 @@ function do_fetch() { }); } exports.do_fetch = do_fetch; -function createFolderStructure(struct, prefix, org, team) { +function createServiceFolder(path, org, team, service) { return __awaiter(this, void 0, void 0, function* () { + let repo = `"${config_1.GIT_HOST}/${org}/${team}/${service}"`; + let dir = `${path}/${service}`; try { - let keys = Object.keys(struct); - for (let i = 0; i < keys.length; i++) { - let k = keys[i]; - if (struct[k] instanceof Object) - yield createFolderStructure(struct[k], prefix + "/" + k, org, team); - else { - // output(`git clone "${HOST}/${org}/${team}/${k}" "${prefix}/${k}"`); - let repo = `"${config_1.GIT_HOST}/${org}/${team}/${k}"`; - let dir = `${prefix}/${k}`; - try { - if (!fs_1.default.existsSync(dir)) { - fs_1.default.mkdirSync(dir, { recursive: true }); - } - if (!fs_1.default.existsSync(dir + "/.git")) { - yield (0, utils_1.execPromise)(`git init --initial-branch=main`, dir); - yield (0, utils_1.execPromise)(`git remote add origin ${repo}`, dir); - fs_1.default.writeFileSync(dir + "/fetch.bat", `@echo off + if (!fs_1.default.existsSync(dir)) { + fs_1.default.mkdirSync(dir, { recursive: true }); + } + if (!fs_1.default.existsSync(dir + "/.git")) { + yield (0, utils_1.execPromise)(`git init --initial-branch=main`, dir); + yield (0, utils_1.execPromise)(`git remote add origin ${repo}`, dir); + fs_1.default.writeFileSync(dir + "/fetch.bat", `@echo off git fetch git reset --hard origin/main del fetch.sh (goto) 2>nul & del fetch.bat`); - fs_1.default.writeFileSync(dir + "/fetch.sh", `#!/bin/sh + fs_1.default.writeFileSync(dir + "/fetch.sh", `#!/bin/sh git fetch git reset --hard origin/main rm fetch.bat fetch.sh`, {}); - fs_1.default.chmodSync(dir + "/fetch.sh", "755"); - } - else { - yield (0, utils_1.execPromise)(`git remote set-url origin ${repo}`, dir); - } - } - catch (e) { - console.log(e); - } + fs_1.default.chmodSync(dir + "/fetch.sh", "755"); + } + else { + yield (0, utils_1.execPromise)(`git remote set-url origin ${repo}`, dir); + } + } + catch (e) { + console.log(e); + } + }); +} +function createFolderStructure(struct, prefix, team, func) { + return __awaiter(this, void 0, void 0, function* () { + try { + let keys = Object.keys(struct); + for (let i = 0; i < keys.length; i++) { + let k = keys[i]; + if (struct[k] instanceof Object) + yield createFolderStructure(struct[k], prefix + "/" + k, team, func); + else { + func(prefix, team, k); } } } @@ -165,7 +170,7 @@ function createServiceGroup(pth, name) { console.log("Creating service group..."); let { org } = (0, utils_1.fetchOrg)(); fs_1.default.mkdirSync(name); - console.log(yield (0, utils_1.sshReq)(`team`, name, `--org`, org.name)); + yield (0, utils_1.sshReq)(`team`, name, `--org`, org.name); process.chdir(before); } catch (e) { @@ -180,23 +185,33 @@ function createService(pth, group, name) { let before = process.cwd(); process.chdir(pth.toString()); console.log("Creating service..."); - let { org } = (0, utils_1.fetchOrg)(); + let { org, pathToRoot } = (0, utils_1.fetchOrg)(); yield (0, utils_1.sshReq)(`service`, name, `--team`, group, `--org`, org.name); - let repoBase = `${config_1.GIT_HOST}/${org.name}/${group}`; - try { - yield (0, utils_1.execPromise)(`git clone -q "${repoBase}/${name}" ${name}`); + if (fs_1.default.existsSync(pathToRoot + BITBUCKET_FILE)) { + fs_1.default.mkdirSync(name, { recursive: true }); + fs_1.default.appendFileSync(pathToRoot + BITBUCKET_FILE, "\n" + bitbucketStep(group + "/" + name)); + (0, utils_1.addExitMessage)(`Use '${prompt_1.GREEN}cd ${pth + .with(name) + .toString() + .replace(/\\/g, "\\\\")}${prompt_1.NORMAL_COLOR}' to go to the new service. \nAutomatic deployment added to BitBucket pipeline.`); } - catch (e) { - if (("" + e).startsWith("warning: You appear to have cloned an empty repository.")) { + else { + let repoBase = `${config_1.GIT_HOST}/${org.name}/${group}`; + try { + yield (0, utils_1.execPromise)(`git clone -q "${repoBase}/${name}" ${name}`); + } + catch (e) { + if (("" + e).startsWith("warning: You appear to have cloned an empty repository.")) { + } + else + throw e; } - else - throw e; + yield (0, utils_1.execPromise)(`git symbolic-ref HEAD refs/heads/main`, name); + (0, utils_1.addExitMessage)(`Use '${prompt_1.GREEN}cd ${pth + .with(name) + .toString() + .replace(/\\/g, "\\\\")}${prompt_1.NORMAL_COLOR}' to go to the new service. \nThen use '${prompt_1.GREEN}${process.env["COMMAND"]} deploy${prompt_1.NORMAL_COLOR}' to deploy it.`); } - yield (0, utils_1.execPromise)(`git symbolic-ref HEAD refs/heads/main`, name); - (0, utils_1.addExitMessage)(`Use '${prompt_1.GREEN}cd ${pth - .with(name) - .toString() - .replace(/\\/g, "\\\\")}${prompt_1.NORMAL_COLOR}' to go to the new service. \nThen use '${prompt_1.GREEN}${process.env["COMMAND"]} deploy${prompt_1.NORMAL_COLOR}' to deploy it.`); process.chdir(before); } catch (e) { @@ -210,7 +225,12 @@ function do_pull(pth, repo) { try { let before = process.cwd(); process.chdir(pth.toString()); - yield (0, utils_1.execPromise)(`git pull -q "${repo}"`); + if (fs_1.default.existsSync(".git")) + yield (0, utils_1.execPromise)(`git pull -q "${repo}"`); + else { + yield (0, utils_1.execPromise)(`git clone -q "${repo}" .`); + fs_1.default.rmSync(".git", { recursive: true, force: true }); + } process.chdir(before); } catch (e) { @@ -423,7 +443,7 @@ exports.do_build = do_build; function do_replay(org, id, river) { return __awaiter(this, void 0, void 0, function* () { try { - yield (0, utils_1.sshReq)(`replay`, id, `--river`, river, `--org`, `${org}`); + yield (0, utils_1.sshReq)(`replay`, id, `--river`, river, `--org`, org); (0, utils_1.output2)("Replayed event."); } catch (e) { @@ -804,3 +824,81 @@ function do_delete_org(org) { }); } exports.do_delete_org = do_delete_org; +function do_create_deployment_agent(org, name, file) { + return __awaiter(this, void 0, void 0, function* () { + try { + (0, utils_1.output2)("Creating service user..."); + let cmd = [`service-user`, org]; + if (name !== "") + cmd.push(`--name`, name); + let key = yield (0, utils_1.sshReq)(...cmd); + fs_1.default.writeFileSync(file, key); + } + catch (e) { + throw e; + } + }); +} +exports.do_create_deployment_agent = do_create_deployment_agent; +const BITBUCKET_FILE = "bitbucket-pipelines.yml"; +function bitbucketStep(pth) { + return ` - step: + name: ${pth} + script: + - ./.merrymake/deploy.sh ${pth}`; +} +function do_bitbucket(org, host, key) { + return __awaiter(this, void 0, void 0, function* () { + try { + let struct = (0, utils_1.fetchOrg)(); + fs_1.default.writeFileSync(struct.pathToRoot + path_1.default.join(".merrymake", "deploy.sh"), `set -o errexit +chmod 600 ${key} +eval \`ssh-agent\` +ssh-add ${key} +echo "api.merrymake.io ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOW2dgo+0nuahOzHD7XVnSdrCwhkK9wMnAZyr6XOKotO" >> ~/.ssh/known_hosts +cd $1 +git init +git remote add merrymake ssh://mist@api.merrymake.io/${org}/$1 +git fetch merrymake +git reset merrymake/main || echo "No previous deployment" +git config --global user.email "support@merrymake.eu" +git config --global user.name "Merrymake" +git add -A && (git diff-index --quiet HEAD || git commit -m 'Deploy from BitBucket') +export RES=$(git push merrymake HEAD:main --force 2>&1); echo "\${RES}" +case $RES in "Everything up-to-date"*) exit 0 ;; *"if/when the smoke test succeeds"*) exit 0 ;; *) echo "Deployment failed"; exit -1 ;; esac`); + let reply = yield (0, utils_1.sshReq)(`clone`, struct.org.name); + if (!reply.startsWith("{")) { + (0, utils_1.output2)(reply); + return; + } + let structure = JSON.parse(reply); + let pipelineFile = [ + `pipelines: + branches: + master: + - parallel:`, + ]; + let folders = [...exports.SPECIAL_FOLDERS]; + fetch("", structure, (path, team, service) => folders.push(path + "/" + service)); + for (let i = 0; i < folders.length; i++) { + let folder = folders[i]; + let toService = struct.pathToRoot + folder; + try { + yield (0, utils_1.execPromise)(`git fetch`, toService); + yield (0, utils_1.execPromise)(`git reset origin/main`, toService); + } + catch (e) { } + fs_1.default.rmSync(`${toService}/.git`, { recursive: true, force: true }); + pipelineFile.push(bitbucketStep(folder)); + } + fs_1.default.writeFileSync(struct.pathToRoot + BITBUCKET_FILE, pipelineFile.join("\n")); + yield (0, utils_1.execPromise)(`git init`, struct.pathToRoot); + yield (0, utils_1.execPromise)(`git remote add origin ${host}`, struct.pathToRoot); + yield (0, utils_1.execPromise)(`git update-index --add --chmod=+x .merrymake/deploy.sh`, struct.pathToRoot); + } + catch (e) { + throw e; + } + }); +} +exports.do_bitbucket = do_bitbucket; diff --git a/executors.ts b/executors.ts index 9b7834e..c06bfd3 100644 --- a/executors.ts +++ b/executors.ts @@ -26,6 +26,8 @@ import { getArgs } from "./args"; import { stdout } from "process"; import { MerrymakeCrypto } from "@merrymake/secret-lib"; +export const SPECIAL_FOLDERS = ["event-catalogue", "public"]; + async function clone(struct: any, name: string) { try { output2(`Cloning ${name}...`); @@ -44,23 +46,29 @@ async function clone(struct: any, name: string) { dir ); // await execPromise(`git fetch`, dir); - fetch(`./${name}`, name, struct); + fetch(`./${name}/`, struct, (path, team, service) => + createServiceFolder(path, name, team, service) + ); } catch (e) { throw e; } } -async function fetch(prefix: string, org: string, struct: any) { +async function fetch( + prefix: string, + struct: any, + func: (path: string, team: string, service: string) => void +) { try { let keys = Object.keys(struct); for (let i = 0; i < keys.length; i++) { let group = keys[i]; - fs.mkdirSync(`${prefix}/${group}`, { recursive: true }); + fs.mkdirSync(`${prefix}${group}`, { recursive: true }); await createFolderStructure( struct[group], - `${prefix}/${group}`, - org, - group + `${prefix}${group}`, + group, + func ); } } catch (e) { @@ -78,58 +86,68 @@ export async function do_fetch() { } output2(`Fetching...`); let structure = JSON.parse(reply); - await fetch(org.pathToRoot, org.org.name, structure); + await fetch(org.pathToRoot, structure, (path, team, service) => + createServiceFolder(path, org.org.name, team, service) + ); } catch (e) { throw e; } } -async function createFolderStructure( - struct: any, - prefix: string, +async function createServiceFolder( + path: string, org: string, - team: string + team: string, + service: string ) { + let repo = `"${GIT_HOST}/${org}/${team}/${service}"`; + let dir = `${path}/${service}`; try { - let keys = Object.keys(struct); - for (let i = 0; i < keys.length; i++) { - let k = keys[i]; - if (struct[k] instanceof Object) - await createFolderStructure(struct[k], prefix + "/" + k, org, team); - else { - // output(`git clone "${HOST}/${org}/${team}/${k}" "${prefix}/${k}"`); - let repo = `"${GIT_HOST}/${org}/${team}/${k}"`; - let dir = `${prefix}/${k}`; - try { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - if (!fs.existsSync(dir + "/.git")) { - await execPromise(`git init --initial-branch=main`, dir); - await execPromise(`git remote add origin ${repo}`, dir); - fs.writeFileSync( - dir + "/fetch.bat", - `@echo off + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + if (!fs.existsSync(dir + "/.git")) { + await execPromise(`git init --initial-branch=main`, dir); + await execPromise(`git remote add origin ${repo}`, dir); + fs.writeFileSync( + dir + "/fetch.bat", + `@echo off git fetch git reset --hard origin/main del fetch.sh (goto) 2>nul & del fetch.bat` - ); - fs.writeFileSync( - dir + "/fetch.sh", - `#!/bin/sh + ); + fs.writeFileSync( + dir + "/fetch.sh", + `#!/bin/sh git fetch git reset --hard origin/main rm fetch.bat fetch.sh`, - {} - ); - fs.chmodSync(dir + "/fetch.sh", "755"); - } else { - await execPromise(`git remote set-url origin ${repo}`, dir); - } - } catch (e) { - console.log(e); - } + {} + ); + fs.chmodSync(dir + "/fetch.sh", "755"); + } else { + await execPromise(`git remote set-url origin ${repo}`, dir); + } + } catch (e) { + console.log(e); + } +} + +async function createFolderStructure( + struct: any, + prefix: string, + team: string, + func: (path: string, team: string, service: string) => void +) { + try { + let keys = Object.keys(struct); + for (let i = 0; i < keys.length; i++) { + let k = keys[i]; + if (struct[k] instanceof Object) + await createFolderStructure(struct[k], prefix + "/" + k, team, func); + else { + func(prefix, team, k); } } } catch (e) { @@ -172,7 +190,7 @@ export async function createServiceGroup(pth: Path, name: string) { console.log("Creating service group..."); let { org } = fetchOrg(); fs.mkdirSync(name); - console.log(await sshReq(`team`, name, `--org`, org.name)); + await sshReq(`team`, name, `--org`, org.name); process.chdir(before); } catch (e) { throw e; @@ -184,31 +202,48 @@ export async function createService(pth: Path, group: string, name: string) { let before = process.cwd(); process.chdir(pth.toString()); console.log("Creating service..."); - let { org } = fetchOrg(); + let { org, pathToRoot } = fetchOrg(); await sshReq(`service`, name, `--team`, group, `--org`, org.name); - let repoBase = `${GIT_HOST}/${org.name}/${group}`; - try { - await execPromise(`git clone -q "${repoBase}/${name}" ${name}`); - } catch (e) { - if ( - ("" + e).startsWith( - "warning: You appear to have cloned an empty repository." - ) - ) { - } else throw e; + if (fs.existsSync(pathToRoot + BITBUCKET_FILE)) { + fs.mkdirSync(name, { recursive: true }); + fs.appendFileSync( + pathToRoot + BITBUCKET_FILE, + "\n" + bitbucketStep(group + "/" + name) + ); + addExitMessage( + `Use '${GREEN}cd ${pth + .with(name) + .toString() + .replace( + /\\/g, + "\\\\" + )}${NORMAL_COLOR}' to go to the new service. \nAutomatic deployment added to BitBucket pipeline.` + ); + } else { + let repoBase = `${GIT_HOST}/${org.name}/${group}`; + try { + await execPromise(`git clone -q "${repoBase}/${name}" ${name}`); + } catch (e) { + if ( + ("" + e).startsWith( + "warning: You appear to have cloned an empty repository." + ) + ) { + } else throw e; + } + await execPromise(`git symbolic-ref HEAD refs/heads/main`, name); + addExitMessage( + `Use '${GREEN}cd ${pth + .with(name) + .toString() + .replace( + /\\/g, + "\\\\" + )}${NORMAL_COLOR}' to go to the new service. \nThen use '${GREEN}${ + process.env["COMMAND"] + } deploy${NORMAL_COLOR}' to deploy it.` + ); } - await execPromise(`git symbolic-ref HEAD refs/heads/main`, name); - addExitMessage( - `Use '${GREEN}cd ${pth - .with(name) - .toString() - .replace( - /\\/g, - "\\\\" - )}${NORMAL_COLOR}' to go to the new service. \nThen use '${GREEN}${ - process.env["COMMAND"] - } deploy${NORMAL_COLOR}' to deploy it.` - ); process.chdir(before); } catch (e) { throw e; @@ -219,7 +254,11 @@ async function do_pull(pth: Path, repo: string) { try { let before = process.cwd(); process.chdir(pth.toString()); - await execPromise(`git pull -q "${repo}"`); + if (fs.existsSync(".git")) await execPromise(`git pull -q "${repo}"`); + else { + await execPromise(`git clone -q "${repo}" .`); + fs.rmSync(".git", { recursive: true, force: true }); + } process.chdir(before); } catch (e) { throw e; @@ -445,7 +484,7 @@ export async function do_build() { export async function do_replay(org: string, id: string, river: string) { try { - await sshReq(`replay`, id, `--river`, river, `--org`, `${org}`); + await sshReq(`replay`, id, `--river`, river, `--org`, org); output2("Replayed event."); } catch (e) { throw e; @@ -901,3 +940,89 @@ export async function do_delete_org(org: string) { throw e; } } + +export async function do_create_deployment_agent( + org: string, + name: string, + file: string +) { + try { + output2("Creating service user..."); + let cmd = [`service-user`, org]; + if (name !== "") cmd.push(`--name`, name); + let key = await sshReq(...cmd); + fs.writeFileSync(file, key); + } catch (e) { + throw e; + } +} + +const BITBUCKET_FILE = "bitbucket-pipelines.yml"; +function bitbucketStep(pth: string) { + return ` - step: + name: ${pth} + script: + - ./.merrymake/deploy.sh ${pth}`; +} + +export async function do_bitbucket(org: string, host: string, key: string) { + try { + let struct = fetchOrg(); + fs.writeFileSync( + struct.pathToRoot + path.join(".merrymake", "deploy.sh"), + `set -o errexit +chmod 600 ${key} +eval \`ssh-agent\` +ssh-add ${key} +echo "api.merrymake.io ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOW2dgo+0nuahOzHD7XVnSdrCwhkK9wMnAZyr6XOKotO" >> ~/.ssh/known_hosts +cd $1 +git init +git remote add merrymake ssh://mist@api.merrymake.io/${org}/$1 +git fetch merrymake +git reset merrymake/main || echo "No previous deployment" +git config --global user.email "support@merrymake.eu" +git config --global user.name "Merrymake" +git add -A && (git diff-index --quiet HEAD || git commit -m 'Deploy from BitBucket') +export RES=$(git push merrymake HEAD:main --force 2>&1); echo "\${RES}" +case $RES in "Everything up-to-date"*) exit 0 ;; *"if/when the smoke test succeeds"*) exit 0 ;; *) echo "Deployment failed"; exit -1 ;; esac` + ); + let reply = await sshReq(`clone`, struct.org.name); + if (!reply.startsWith("{")) { + output2(reply); + return; + } + let structure = JSON.parse(reply); + let pipelineFile = [ + `pipelines: + branches: + master: + - parallel:`, + ]; + let folders: string[] = [...SPECIAL_FOLDERS]; + fetch("", structure, (path, team, service) => + folders.push(path + "/" + service) + ); + for (let i = 0; i < folders.length; i++) { + let folder = folders[i]; + let toService = struct.pathToRoot + folder; + try { + await execPromise(`git fetch`, toService); + await execPromise(`git reset origin/main`, toService); + } catch (e) {} + fs.rmSync(`${toService}/.git`, { recursive: true, force: true }); + pipelineFile.push(bitbucketStep(folder)); + } + fs.writeFileSync( + struct.pathToRoot + BITBUCKET_FILE, + pipelineFile.join("\n") + ); + await execPromise(`git init`, struct.pathToRoot); + await execPromise(`git remote add origin ${host}`, struct.pathToRoot); + await execPromise( + `git update-index --add --chmod=+x .merrymake/deploy.sh`, + struct.pathToRoot + ); + } catch (e) { + throw e; + } +} diff --git a/package.json b/package.json index a3e57d7..2142ec4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@merrymake/cli", - "version": "3.1.0", + "version": "3.2.0", "description": "", "main": "index.js", "scripts": { diff --git a/questions.js b/questions.js index 0167e85..f888363 100644 --- a/questions.js +++ b/questions.js @@ -85,6 +85,10 @@ function delete_org_name(org) { (0, utils_1.addToExecuteQueue)(() => (0, executors_1.do_delete_org)(org)); return (0, utils_1.finish)(); } +function hosting_bitbucket_key_host(org, host, key) { + (0, utils_1.addToExecuteQueue)(() => (0, executors_1.do_bitbucket)(org, host, key)); + return (0, utils_1.finish)(); +} function service_template(pathToService, template) { return __awaiter(this, void 0, void 0, function* () { try { @@ -249,26 +253,26 @@ function register_manual() { function register() { return __awaiter(this, void 0, void 0, function* () { try { - let keyfiles = (0, utils_1.getFiles)(new utils_2.Path(`${os_1.default.homedir()}/.ssh`), "").filter((x) => x.endsWith(".pub")); + let keyfiles = (0, utils_1.getFiles)(new utils_2.Path(`${os_1.default.homedir()}/.ssh`)).filter((x) => x.endsWith(".pub")); let keys = keyfiles.map((x) => { let f = x.substring(0, x.length - ".pub".length); return { long: f, - text: `Use key ${f}`, + text: `use key ${f}`, action: () => register_key(() => (0, executors_1.useExistingKey)(f)), }; }); keys.push({ long: "add", short: "a", - text: "Manually add key", + text: "manually add key", action: () => register_manual(), }); if (!keyfiles.includes("merrymake")) { keys.push({ long: "new", short: "n", - text: "Setup new key specifically for Merrymake", + text: "setup new key specifically for Merrymake", action: () => register_key(executors_1.generateNewKey), }); } @@ -793,6 +797,85 @@ function post(org) { } }); } +function hosting_bitbucket_key(org, file) { + return __awaiter(this, void 0, void 0, function* () { + try { + let host = yield (0, prompt_1.shortText)("Bitbucket repo", "The URL to the bitbucket mono-repository.", `https://...`).then((x) => x); + return hosting_bitbucket_key_host(org, host, file); + } + catch (e) { + throw e; + } + }); +} +function hosting_bitbucket_create(pathToRoot, org) { + return __awaiter(this, void 0, void 0, function* () { + try { + let name = yield (0, prompt_1.shortText)("Name", "Display name for the service user", `Service User`).then((x) => x); + let file = ".merrymake/" + name.toLowerCase().replace(" ", "-") + ".key"; + (0, utils_1.addToExecuteQueue)(() => (0, executors_1.do_create_deployment_agent)(org, name, file)); + return hosting_bitbucket_key(org, file); + } + catch (e) { + throw e; + } + }); +} +function hosting_bitbucket(pathToRoot, org) { + return __awaiter(this, void 0, void 0, function* () { + try { + let keyfiles = (0, utils_1.getFiles)(new utils_2.Path(`${pathToRoot}/.merrymake`)).filter((x) => x.endsWith(".key")); + let options = keyfiles.map((x) => { + let f = x.substring(0, x.length - ".key".length); + return { + long: f, + text: `use service user ${f}`, + action: () => hosting_bitbucket_key(org, f), + }; + }); + options.push({ + long: `create`, + short: `c`, + text: `create service user`, + action: () => hosting_bitbucket_create(pathToRoot, org), + }); + return yield (0, prompt_1.choice)("Which service user would you like to use?", options, { + invertedQuiet: { cmd: false, select: true }, + }).then((x) => x); + } + catch (e) { + throw e; + } + }); +} +function hosting(pathToRoot, org) { + return (0, prompt_1.choice)("Which host would you like to use?", [ + { + long: "bitbucket", + short: "b", + text: "bitbucket", + action: () => hosting_bitbucket(pathToRoot, org), + }, + // { + // long: "github", + // short: "h", + // text: "github", + // action: () => hosting_github(), + // }, + // { + // long: "gitlab", + // short: "h", + // text: "gitlab", + // action: () => hosting_gitlab(), + // }, + // { + // long: "azure devops", + // short: "h", + // text: "azure devops", + // action: () => hosting_azure_devops(), + // }, + ]); +} function event_key_events(key, events) { (0, utils_1.addToExecuteQueue)(() => (0, executors_1.do_event)(key, events)); return (0, utils_1.finish)(); @@ -1009,7 +1092,6 @@ function sim() { (0, utils_1.addToExecuteQueue)(() => new simulator_1.Run(3000).execute()); return (0, utils_1.finish)(); } -const SPECIAL_FOLDERS = ["event-catalogue", "public"]; function start() { return __awaiter(this, void 0, void 0, function* () { try { @@ -1022,7 +1104,7 @@ function start() { let struct = { org: rawStruct.org, serviceGroup: rawStruct.serviceGroup !== null && - !SPECIAL_FOLDERS.includes(rawStruct.serviceGroup) + !executors_1.SPECIAL_FOLDERS.includes(rawStruct.serviceGroup) ? rawStruct.serviceGroup : null, inEventCatalogue: rawStruct.serviceGroup === "event-catalogue", @@ -1178,6 +1260,11 @@ function start() { text: "register an additional sshkey or email to account", action: () => register(), }); + options.push({ + long: "hosting", + text: "configure git hosting with bitbucket", // TODO add github, gitlab, and azure devops + action: () => hosting(struct.pathToRoot, orgName), + }); options.push({ long: "help", text: "help diagnose potential issues", diff --git a/questions.ts b/questions.ts index 1aa5054..ecfd16c 100644 --- a/questions.ts +++ b/questions.ts @@ -21,6 +21,7 @@ import { finish, getCache, getFiles, + getFilesFilter, output2, sshReq, } from "./utils"; @@ -60,6 +61,9 @@ import { do_delete_group, do_delete_org, do_replay, + do_create_deployment_agent, + do_bitbucket, + SPECIAL_FOLDERS, } from "./executors"; import { VERSION_CMD, type ProjectType } from "@merrymake/detect-project-type"; import { execSync } from "child_process"; @@ -148,6 +152,11 @@ function delete_org_name(org: string) { return finish(); } +function hosting_bitbucket_key_host(org: string, host: string, key: string) { + addToExecuteQueue(() => do_bitbucket(org, host, key)); + return finish(); +} + async function service_template(pathToService: Path, template: string) { try { let langs = await Promise.all( @@ -347,28 +356,28 @@ async function register_manual() { async function register() { try { - let keyfiles = getFiles(new Path(`${os.homedir()}/.ssh`), "").filter((x) => + let keyfiles = getFiles(new Path(`${os.homedir()}/.ssh`)).filter((x) => x.endsWith(".pub") ); let keys = keyfiles.map