This repository has been archived by the owner on May 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: xphoniex <[email protected]>
- Loading branch information
Showing
7 changed files
with
288 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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 @@ | ||
[package] | ||
name = "rad-ci" | ||
version = "0.1.0" | ||
authors = ["The Radicle Team <[email protected]>"] | ||
edition = "2018" | ||
license = "MIT OR Apache-2.0" | ||
description = "Install or uninstall ci on your seed node" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
anyhow = "1.0" | ||
lexopt = { version = "0.2" } | ||
rad-common = { path = "../common" } | ||
rad-terminal = { path = "../terminal" } | ||
url = { version = "*" } | ||
ssh2 = "0.9.3" |
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,105 @@ | ||
use std::ffi::OsString; | ||
|
||
use rad_terminal::args::{Args, Help}; | ||
use std::path::PathBuf; | ||
use url::Host; | ||
|
||
pub const HELP: Help = Help { | ||
name: "ci", | ||
description: env!("CARGO_PKG_DESCRIPTION"), | ||
version: env!("CARGO_PKG_VERSION"), | ||
usage: r#" | ||
USAGE | ||
rad ci [--install/--uninstall] [--seed <host>] [--ssh-key <path>] | ||
OPTIONS | ||
--install Runs docker images for concourse | ||
--uninstall Terminates running concourse images | ||
--seed Address of the seed node | ||
--ssh-user SSH user on target machine. Default: root | ||
--ssh-index (Optional) Index of SSH key in ssh-agent. Default: 0 | ||
--config (Optional) Config file to override defaults | ||
--verbose | ||
"#, | ||
}; | ||
|
||
#[derive(Default, Eq, PartialEq)] | ||
pub struct Options { | ||
pub install: bool, | ||
pub uninstall: bool, | ||
pub seed: Option<Host>, | ||
pub ssh_user: String, | ||
pub ssh_index: usize, | ||
pub config: Option<PathBuf>, | ||
pub verbose: bool, | ||
} | ||
|
||
impl Args for Options { | ||
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> { | ||
use lexopt::prelude::*; | ||
|
||
let mut parser = lexopt::Parser::from_args(args); | ||
let mut install = false; | ||
let mut uninstall = false; | ||
let mut seed = None; | ||
let mut ssh_user = String::from("root"); | ||
let mut ssh_index = 0; | ||
let mut config = None; | ||
let mut verbose = false; | ||
|
||
while let Some(arg) = parser.next()? { | ||
match arg { | ||
Long("seed") => { | ||
let value = parser.value()?; | ||
let value = value.to_string_lossy(); | ||
let value = value.as_ref(); | ||
let addr = Host::parse(value)?; | ||
|
||
seed = Some(addr); | ||
} | ||
Long("install") | Short('i') => { | ||
install = true; | ||
} | ||
Long("uninstall") | Short('u') => { | ||
uninstall = true; | ||
} | ||
Long("ssh-index") => { | ||
let value = parser.value()?; | ||
|
||
ssh_index = value.to_string_lossy().parse().unwrap(); | ||
} | ||
Long("ssh-user") => { | ||
let value = parser.value()?; | ||
|
||
ssh_user = String::from(value.to_string_lossy()); | ||
} | ||
Long("config") => { | ||
use std::convert::TryInto; | ||
let value = parser.value()?; | ||
let path: PathBuf = value.try_into()?; | ||
|
||
config = Some(path); | ||
} | ||
Long("verbose") | Short('v') => { | ||
verbose = true; | ||
} | ||
_ => { | ||
return Err(anyhow::anyhow!(arg.unexpected())); | ||
} | ||
} | ||
} | ||
|
||
Ok(( | ||
Options { | ||
seed, | ||
install, | ||
uninstall, | ||
ssh_user, | ||
ssh_index, | ||
config, | ||
verbose, | ||
}, | ||
vec![], | ||
)) | ||
} | ||
} |
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,132 @@ | ||
use rad_ci::{Options, HELP}; | ||
use rad_terminal::components as term; | ||
|
||
use ssh2::Session; | ||
use std::net::TcpStream; | ||
|
||
use anyhow::Context; | ||
|
||
fn main() { | ||
rad_terminal::args::run_command::<Options, _>(HELP, "CI", run); | ||
} | ||
|
||
fn run(options: Options) -> anyhow::Result<()> { | ||
let seed = options.seed.context("Seed is invalid")?; | ||
// SSH into server | ||
let tcp = TcpStream::connect(format!("{}:22", &seed))?; | ||
let mut sess = Session::new()?; | ||
sess.set_tcp_stream(tcp); | ||
sess.handshake()?; | ||
|
||
// Authenticate | ||
let spinner = term::spinner(&format!("SSH into remote {}", &seed)); | ||
if options.ssh_index == 0 { | ||
sess.userauth_agent(&options.ssh_user)?; | ||
} else { | ||
let mut agent = sess.agent()?; | ||
agent.connect()?; | ||
agent.list_identities()?; | ||
let identities = agent.identities()?; | ||
let identity = identities.get(options.ssh_index).context(format!( | ||
"Couldn't retrieve ssh-agent key #{}", | ||
options.ssh_index | ||
))?; | ||
agent.userauth(&options.ssh_user, identity)?; | ||
} | ||
if sess.authenticated() { | ||
spinner.finish(); | ||
} else { | ||
spinner.failed(); | ||
} | ||
|
||
let verbosity = options.verbose; | ||
let execute_fn = |cmd, spinner| execute_cmd_with_spinner(&sess, cmd, spinner, verbosity); | ||
|
||
// Check requirements | ||
term::blank(); | ||
term::info!("Checking requirements:"); | ||
term::blank(); | ||
|
||
if ["wget", "docker", "docker-compose"] | ||
.iter() | ||
.map(|req| { | ||
let spinner = term::spinner(&format!( | ||
"Checking if {} exists", | ||
term::format::highlight(req) | ||
)); | ||
let (_, status) = | ||
execute_cmd_with_spinner(&sess, format!("which {}", req), spinner, verbosity); | ||
status | ||
}) | ||
.sum::<i32>() | ||
> 0 | ||
{ | ||
term::info!("Requirements are not installed."); | ||
std::process::exit(1); | ||
} | ||
term::blank(); | ||
|
||
// Apply changes | ||
if options.install { | ||
// Copy the docker-compose.yml over | ||
let spinner = term::spinner("Downloading docker-compose.yml for Concourse CI"); | ||
execute_fn("wget -nc -O /etc/concourse-docker-compose.yml https://concourse-ci.org/docker-compose.yml", spinner); | ||
|
||
// Run `docker-compose up -d` | ||
let spinner = term::spinner("Installing Concourse CI"); | ||
let (_, status) = execute_fn( | ||
"docker-compose -f /etc/concourse-docker-compose.yml up -d", | ||
spinner, | ||
); | ||
if status == 0 { | ||
term::success!("Concourse CI installed!"); | ||
} | ||
} else if options.uninstall { | ||
// Run `docker-compose down` | ||
let spinner = term::spinner("Uninstalling Concourse CI"); | ||
let (_, status) = execute_fn( | ||
"docker-compose -f /etc/concourse-docker-compose.yml down", | ||
spinner, | ||
); | ||
if status == 0 { | ||
term::success!("Concourse CI uninstalled!"); | ||
} | ||
|
||
// Delete docker-compose.yml file | ||
let mut channel = sess.channel_session().unwrap(); | ||
execute_cmd(&mut channel, "rm /etc/concourse-docker-compose.yml"); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn execute_cmd<S: AsRef<str>>(channel: &mut ssh2::Channel, cmd: S) -> (String, i32) { | ||
let cmd = cmd.as_ref(); | ||
use std::io::Read; | ||
let mut res = String::new(); | ||
channel.exec(cmd).unwrap(); | ||
channel.read_to_string(&mut res).unwrap(); | ||
channel.stderr().read_to_string(&mut res).unwrap(); | ||
channel.wait_close().unwrap(); | ||
(res, channel.exit_status().unwrap()) | ||
} | ||
|
||
fn execute_cmd_with_spinner<S: AsRef<str>>( | ||
sess: &ssh2::Session, | ||
cmd: S, | ||
spinner: term::Spinner, | ||
verbose: bool, | ||
) -> (String, i32) { | ||
let cmd = cmd.as_ref(); | ||
let mut channel = sess.channel_session().unwrap(); | ||
let (res, status) = execute_cmd(&mut channel, cmd); | ||
if status == 0 { | ||
spinner.finish(); | ||
} else { | ||
spinner.failed(); | ||
} | ||
if verbose { | ||
term::blob(&res); | ||
} | ||
(res, status) | ||
} |