Skip to content
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

add explore subcommand #89

Merged
merged 6 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Shorthand notion for endpoints defined within the config file (`am.toml`) is now
allowed (#85)
- Allow user to specify the Prometheus scrape interval (#87)
- Added new subcommand `explore` which opens up explorer in the browser (#89)

## [0.1.0]

Expand Down
5 changes: 5 additions & 0 deletions src/bin/am/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use indicatif::MultiProgress;
use std::path::PathBuf;
use tracing::info;

mod explore;
pub mod start;
pub mod system;

Expand Down Expand Up @@ -39,6 +40,9 @@ pub enum SubCommands {
/// Prometheus, Pushgateway installs.
System(system::Arguments),

/// Open up the existing Explorer
Explore(explore::Arguments),

/// Open the Fiberplane discord to receive help, send suggestions or
/// discuss various things related to Autometrics and the `am` CLI
Discord,
Expand All @@ -51,6 +55,7 @@ pub async fn handle_command(app: Application, config: AmConfig, mp: MultiProgres
match app.command {
SubCommands::Start(args) => start::handle_command(args, config, mp).await,
SubCommands::System(args) => system::handle_command(args, mp).await,
SubCommands::Explore(args) => explore::handle_command(args).await,
SubCommands::Discord => {
const URL: &str = "https://discord.gg/kHtwcH8As9";

Expand Down
33 changes: 33 additions & 0 deletions src/bin/am/commands/explore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use anyhow::Result;
use clap::Parser;
use tracing::info;
use url::Url;

#[derive(Parser, Clone)]
pub struct Arguments {
/// The Prometheus endpoint that will be passed to Explorer
#[clap(long, env)]
prometheus_endpoint: Option<Url>,

/// Which endpoint to open in the browser
#[clap(long, env, default_value = "http://localhost:6789/explorer")]
explorer_endpoint: Url,
}

pub async fn handle_command(mut args: Arguments) -> Result<()> {
let url = &mut args.explorer_endpoint;

if let Some(prom_url) = args.prometheus_endpoint {
let query = format!("prometheusUrl={}", prom_url.as_str());
url.set_query(Some(&query));
}

if open::that(url.as_str()).is_err() {
info!(
"Unable to open browser, open the following URL in your browser: {}",
url.as_str()
);
}

Ok(())
}
59 changes: 22 additions & 37 deletions src/bin/am/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::dir::AutoCleanupDir;
use crate::downloader::{download_github_release, unpack, verify_checksum};
use crate::interactive;
use crate::server::start_web_server;
use anyhow::{bail, Context, Result};
use autometrics_am::config::AmConfig;
use anyhow::{anyhow, bail, Context, Result};
use autometrics_am::config::{endpoints_from_first_input, AmConfig};
use autometrics_am::parser::endpoint_parser;
use autometrics_am::prometheus;
use autometrics_am::prometheus::ScrapeConfig;
Expand All @@ -16,7 +16,6 @@ use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::net::SocketAddr;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use std::{env, vec};
use tempfile::NamedTempFile;
Expand Down Expand Up @@ -111,40 +110,11 @@ struct Arguments {

impl Arguments {
fn new(args: CliArguments, config: AmConfig) -> Self {
static COUNTER: AtomicUsize = AtomicUsize::new(0);

// If the user specified an endpoint using args, then use those.
// Otherwise use the endpoint configured in the config file. And
// fallback to an empty list if neither are configured.
let metrics_endpoints = if !args.metrics_endpoints.is_empty() {
args.metrics_endpoints
.into_iter()
.map(|url| {
let num = COUNTER.fetch_add(1, Ordering::SeqCst);
Endpoint::new(url, format!("am_{num}"), false, None)
})
.collect()
} else if let Some(endpoints) = config.endpoints {
endpoints
.into_iter()
.map(|endpoint| {
let job_name = endpoint.job_name.unwrap_or_else(|| {
format!("am_{num}", num = COUNTER.fetch_add(1, Ordering::SeqCst))
});
Endpoint::new(
endpoint.url,
job_name,
endpoint.honor_labels.unwrap_or(false),
endpoint.prometheus_scrape_interval,
)
})
.collect()
} else {
Vec::new()
};

Arguments {
metrics_endpoints,
metrics_endpoints: endpoints_from_first_input(args.metrics_endpoints, config.endpoints)
.into_iter()
.filter_map(|e| e.try_into().ok())
.collect(),
prometheus_version: args.prometheus_version,
listen_address: args.listen_address,
pushgateway_enabled: args
Expand All @@ -162,7 +132,7 @@ impl Arguments {
}

#[derive(Debug, Clone)]
struct Endpoint {
pub struct Endpoint {
url: Url,
job_name: String,
honor_labels: bool,
Expand All @@ -185,6 +155,21 @@ impl Endpoint {
}
}

impl TryFrom<autometrics_am::config::Endpoint> for Endpoint {
type Error = anyhow::Error;

fn try_from(value: autometrics_am::config::Endpoint) -> Result<Self, Self::Error> {
Ok(Self {
url: value.url,
job_name: value
.job_name
.ok_or_else(|| anyhow!("TryFrom requires job_name"))?,
honor_labels: value.honor_labels.unwrap_or(false),
scrape_interval: value.prometheus_scrape_interval,
})
}
}

impl From<Endpoint> for ScrapeConfig {
/// Convert an InnerEndpoint to a Prometheus ScrapeConfig.
///
Expand Down
40 changes: 40 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::parser::endpoint_parser;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use url::Url;

Expand Down Expand Up @@ -45,3 +46,42 @@ fn parse_maybe_shorthand<'de, D: Deserializer<'de>>(input: D) -> Result<Url, D::
let input_str: String = Deserialize::deserialize(input)?;
endpoint_parser(&input_str).map_err(Error::custom)
}

/// If the user specified an endpoint using args, then use those.
/// Otherwise, use the endpoint configured in the config file. And
/// fallback to an empty list if neither are configured.
pub fn endpoints_from_first_input(args: Vec<Url>, config: Option<Vec<Endpoint>>) -> Vec<Endpoint> {
static COUNTER: AtomicUsize = AtomicUsize::new(0);

if !args.is_empty() {
args.into_iter()
.map(|url| {
let num = COUNTER.fetch_add(1, Ordering::SeqCst);
Endpoint {
url,
job_name: Some(format!("am_{num}")),
honor_labels: Some(false),
prometheus_scrape_interval: None,
}
})
.collect()
} else if let Some(endpoints) = config {
endpoints
.into_iter()
.map(|endpoint| {
let job_name = endpoint.job_name.unwrap_or_else(|| {
format!("am_{num}", num = COUNTER.fetch_add(1, Ordering::SeqCst))
});

Endpoint {
url: endpoint.url,
job_name: Some(job_name),
honor_labels: endpoint.honor_labels,
prometheus_scrape_interval: endpoint.prometheus_scrape_interval,
}
})
.collect()
} else {
Vec::new()
}
}