-
Notifications
You must be signed in to change notification settings - Fork 643
Add deploy cli [WIP] #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
7a2afa4
Add deploy cli
BrunoQuaresma 61e94eb
add development instructions
bpmct f680570
Fix async runner and add extra token info
BrunoQuaresma d312c61
Merge branch 'add-cli' of github.com:cdr/deploy-code-server into add-cli
BrunoQuaresma 9bfcebd
Add prettier
BrunoQuaresma 82417e5
Move prettier config
BrunoQuaresma 266a683
Add Prettier and pre-commit linting
BrunoQuaresma c21d48a
Remove package.json
BrunoQuaresma 3c67a8d
Move Prettier config to cli
BrunoQuaresma 933ddcb
Pull version from package.json
BrunoQuaresma 7c8acea
Get description from package.json
BrunoQuaresma fe99f9f
Update package info
BrunoQuaresma 5dd0b05
Update package name
BrunoQuaresma 3031ea2
Add cli to coder org
BrunoQuaresma File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,2 @@ | ||
node_modules | ||
bin |
This file contains hidden or 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 @@ | ||
_ |
This file contains hidden or 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,4 @@ | ||
#!/bin/sh | ||
. "$(dirname "$0")/_/husky.sh" | ||
|
||
npx lint-staged |
This file contains hidden or 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,3 @@ | ||
node_modules | ||
bin | ||
yarn.lock |
This file contains hidden or 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,4 @@ | ||
{ | ||
"tabWidth": 2, | ||
"useTabs": false | ||
} |
This file contains hidden or 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,14 @@ | ||
# dcs-cli | ||
|
||
Provision a code-server instance from your terminal. | ||
|
||
## Development | ||
|
||
```console | ||
git clone git@github.com:cdr/deploy-code-server.git | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Is it standard to use the SSH URL instead of HTTPS? I feel like HTTPS is more common, at least from what I see in OSS. |
||
cd deploy-code-server/cli | ||
npm install && npm run build:watch | ||
|
||
# in another session: | ||
node bin/index.js | ||
``` |
This file contains hidden or 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,33 @@ | ||
{ | ||
"name": "@coder/deploy-code-server", | ||
"version": "0.1.0", | ||
"repository": "cdr/deploy-code-server", | ||
"homepage": "https://github.com/cdr/deploy-code-server", | ||
"description": "CLI to deploy code-server", | ||
"main": "bin/index.js", | ||
"bin": "bin/index.js", | ||
"scripts": { | ||
"build": "tsc", | ||
"build:watch": "tsc -w", | ||
"prepare": "yarn build" | ||
}, | ||
"keywords": ["code-server", "coder"], | ||
"author": "coder", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"license": "ISC", | ||
"devDependencies": { | ||
"@types/inquirer": "^7.3.3", | ||
"@types/node": "^14.14.20", | ||
"typescript": "^4.1.3" | ||
}, | ||
"dependencies": { | ||
"async-wait-until": "^2.0.7", | ||
"chalk": "^4.1.2", | ||
"commander": "^8.1.0", | ||
"got": "^11.8.2", | ||
"inquirer": "^8.1.2", | ||
"ora": "^5.4.1" | ||
} | ||
} |
This file contains hidden or 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,138 @@ | ||
import inquirer from "inquirer"; | ||
import got from "got"; | ||
import ora from "ora"; | ||
import chalk from "chalk"; | ||
import { | ||
createDroplet, | ||
Droplet, | ||
DropletV4Network, | ||
getDroplet, | ||
} from "../lib/digitalOcean"; | ||
import waitUntil from "async-wait-until"; | ||
|
||
const getUserDataScript = async () => | ||
got( | ||
"https://raw.githubusercontent.com/cdr/deploy-code-server/main/deploy-vm/launch-code-server.sh" | ||
).text(); | ||
|
||
const isPermissionError = (error: unknown) => { | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return error instanceof got.HTTPError && error.response.statusCode === 401; | ||
}; | ||
|
||
const getPublicIp = (droplet: Droplet) => { | ||
const network = droplet.networks.v4.find( | ||
(network) => network.type === "public" | ||
); | ||
return network?.ip_address; | ||
}; | ||
|
||
const isCodeServerLive = async (droplet: Droplet) => { | ||
try { | ||
const response = await got(`http://${getPublicIp(droplet)}`, { retry: 0 }); | ||
return response.statusCode === 200; | ||
} catch { | ||
return false; | ||
} | ||
}; | ||
|
||
const handleErrorLog = (error: unknown) => { | ||
if (isPermissionError(error)) { | ||
console.log( | ||
chalk.red( | ||
chalk.bold("Invalid token."), | ||
"Please, verify your token and try again." | ||
) | ||
); | ||
} else { | ||
console.log(chalk.red.bold("Something wrong happened")); | ||
console.log( | ||
chalk.red( | ||
"You may have to delete the droplet manually on your Digital Ocean dashboard." | ||
) | ||
); | ||
} | ||
}; | ||
|
||
const oneMinute = 1000 * 60; | ||
const fiveMinutes = oneMinute * 5; | ||
|
||
const waitUntilBeActive = (droplet: Droplet, token: string) => { | ||
return waitUntil( | ||
async () => { | ||
const dropletInfo = await getDroplet({ token, id: droplet.id }); | ||
return dropletInfo.status === "active"; | ||
}, | ||
{ timeout: fiveMinutes, intervalBetweenAttempts: oneMinute / 2 } | ||
); | ||
}; | ||
|
||
const waitUntilHasPublicIp = (droplet: Droplet, token: string) => { | ||
return waitUntil( | ||
async () => { | ||
const dropletInfo = await getDroplet({ token, id: droplet.id }); | ||
const ip = getPublicIp(dropletInfo); | ||
return ip !== undefined; | ||
}, | ||
{ timeout: fiveMinutes, intervalBetweenAttempts: oneMinute / 2 } | ||
); | ||
}; | ||
|
||
const waitUntilCodeServerIsLive = (droplet: Droplet, token: string) => { | ||
return waitUntil( | ||
async () => { | ||
const dropletInfo = await getDroplet({ token, id: droplet.id }); | ||
return isCodeServerLive(dropletInfo); | ||
}, | ||
{ timeout: fiveMinutes * 2, intervalBetweenAttempts: oneMinute / 2 } | ||
); | ||
}; | ||
|
||
export const deployDigitalOcean = async () => { | ||
BrunoQuaresma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let spinner: ora.Ora; | ||
|
||
console.log( | ||
chalk.blue( | ||
"You can create a token on", | ||
chalk.bold("https://cloud.digitalocean.com/account/api/tokens") | ||
) | ||
); | ||
const { token } = await inquirer.prompt([ | ||
{ name: "token", message: "Your Digital Ocean token:", type: "password" }, | ||
]); | ||
|
||
try { | ||
let spinner = ora("Creating droplet and installing code-server").start(); | ||
let droplet = await createDroplet({ | ||
userData: await getUserDataScript(), | ||
token, | ||
}); | ||
spinner.stop(); | ||
console.log(chalk.green("✅ Droplet created")); | ||
|
||
spinner = ora("Waiting droplet to be active").start(); | ||
await waitUntilBeActive(droplet, token); | ||
spinner.stop(); | ||
console.log(chalk.green("✅ Droplet active")); | ||
|
||
spinner = ora("Waiting droplet to have a public IP").start(); | ||
await waitUntilHasPublicIp(droplet, token); | ||
spinner.stop(); | ||
console.log(chalk.green("✅ Public IP is available")); | ||
|
||
spinner = ora( | ||
"Waiting code-server to be live. It can take up to 5 minutes." | ||
).start(); | ||
await waitUntilCodeServerIsLive(droplet, token); | ||
droplet = await getDroplet({ token, id: droplet.id }); | ||
spinner.stop(); | ||
console.log( | ||
chalk.green( | ||
`🚀 Your code-server is live. You can access it on`, | ||
chalk.bold(`http://${getPublicIp(droplet)}`) | ||
) | ||
); | ||
} catch (error) { | ||
spinner.stop(); | ||
handleErrorLog(error); | ||
} | ||
}; |
This file contains hidden or 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,14 @@ | ||
#!/usr/bin/env node | ||
|
||
import { program } from "commander"; | ||
import { deployDigitalOcean } from "./deploys/deployDigitalOcean"; | ||
import packageJson from "../package.json"; | ||
|
||
const main = async () => { | ||
program.version(packageJson.version).description(packageJson.description); | ||
program.parse(); | ||
await deployDigitalOcean(); | ||
process.exit(0); | ||
}; | ||
|
||
main(); |
This file contains hidden or 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,55 @@ | ||
import got from "got"; | ||
|
||
const DIGITALOCEAN_API_URL = "https://api.digitalocean.com/v2"; | ||
|
||
export type DropletV4Network = { | ||
ip_address: string; | ||
type: "private" | "public"; | ||
}; | ||
export type Droplet = { | ||
id: string; | ||
name: string; | ||
networks: { v4: DropletV4Network[] }; | ||
status: "new" | "active"; | ||
}; | ||
|
||
type CreateDropletOptions = { | ||
userData: string; | ||
token: string; | ||
}; | ||
|
||
export const createDroplet = async ({ | ||
token, | ||
userData, | ||
}: CreateDropletOptions) => { | ||
return got | ||
.post(`${DIGITALOCEAN_API_URL}/droplets`, { | ||
json: { | ||
name: "code-server", | ||
region: "nyc3", | ||
size: "s-1vcpu-1gb", | ||
image: "ubuntu-20-10-x64", | ||
user_data: userData, | ||
}, | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}) | ||
.json<{ droplet: Droplet }>() | ||
.then((data) => data.droplet); | ||
}; | ||
|
||
type GetDropletOptions = { | ||
id: string; | ||
token: string; | ||
}; | ||
|
||
export const getDroplet = async ({ token, id }: GetDropletOptions) => { | ||
return got(`${DIGITALOCEAN_API_URL}/droplets/${id}`, { | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}) | ||
.json<{ droplet: Droplet }>() | ||
.then((data) => data.droplet); | ||
}; |
This file contains hidden or 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 @@ | ||
{ | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"target": "es2017", | ||
"lib": ["es2015"], | ||
"moduleResolution": "node", | ||
"sourceMap": true, | ||
"outDir": "bin", | ||
"baseUrl": ".", | ||
"paths": { | ||
"*": ["node_modules/*", "src/types/*"] | ||
}, | ||
"esModuleInterop": true, | ||
"resolveJsonModule": true | ||
}, | ||
"include": ["src/**/*"] | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: not super obvious what
dcs
stands for. maybe we should spell it out below "Deploy code-server (dcs)"