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

init grafana sdk usage #172

Merged
merged 5 commits into from
May 7, 2024
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
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ ENV=DEV # PROD or DEV
ONLY_RELAY_SERVICE=FALSE # TRUE - This will start only nightly relay service without cloud service
NONCE=VERY_SECRET_NONCE
MAILER_ADDRESS = [email protected]
GRAFANA_BASE_PATH = http://localhost:3000/api
# TEST LOGIN DO NO USE IN PRODUCTION
GRAFANA_CLIENT_LOGIN = admin
# TEST PASSWORD DO NO USE IN PRODUCTION
GRAFANA_CLIENT_PASSWORD = admin
# TEST PASSWORD DO NO USE IN PRODUCTION
MAILER_PASSWORD = A12indqww021dD
# Generated so it can work with grafana
Expand Down
5 changes: 1 addition & 4 deletions grafana/grafana.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,4 @@ email_claim = sub
username_claim = sub
auto_sign_up = true
key_file = /etc/grafana/public-key.pem
url_login = true

[auth.basic]
enabled = false
url_login = true
14 changes: 13 additions & 1 deletion server/src/cloud_state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
env::ENVIRONMENT,
env::{ENVIRONMENT, GRAFANA_BASE_PATH, GRAFANA_CLIENT_LOGIN, GRAFANA_CLIENT_PASSWORD},
ip_geolocation::GeolocationRequester,
mailer::{entry::run_mailer, mailer::Mailer},
structs::session_cache::ApiSessionsCache,
Expand All @@ -10,6 +10,7 @@ use hickory_resolver::{
name_server::{GenericConnector, TokioRuntimeProvider},
AsyncResolver, TokioAsyncResolver,
};
use openapi::apis::configuration::Configuration;
use r_cache::cache::Cache;
use reqwest::Url;
use std::{sync::Arc, time::Duration};
Expand All @@ -26,6 +27,7 @@ pub struct CloudState {
pub mailer: Arc<Mailer>,
pub dns_resolver: Arc<TokioAsyncResolver>,
pub webauthn: Arc<Webauthn>,
pub grafana_client_conf: Arc<Configuration>,
}

impl CloudState {
Expand All @@ -36,6 +38,15 @@ impl CloudState {
let mailer = Arc::new(run_mailer().await.unwrap());
let dns_resolver = Arc::new(TokioAsyncResolver::tokio_from_system_conf().unwrap());

let mut conf = Configuration::new();
conf.base_path = GRAFANA_BASE_PATH().to_string();
conf.basic_auth = Some((
GRAFANA_CLIENT_LOGIN().to_string(),
Some(GRAFANA_CLIENT_PASSWORD().to_string()),
));

let grafana_client_conf = Arc::new(conf);

// Passkey
let rp_id = match ENVIRONMENT() {
"DEV" => "localhost",
Expand All @@ -61,6 +72,7 @@ impl CloudState {
mailer,
dns_resolver,
webauthn,
grafana_client_conf,
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions server/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ pub struct ENV {
pub NONCE: String,
pub MAILER_ADDRESS: String,
pub MAILER_PASSWORD: String,
pub GRAFANA_BASE_PATH: String,
pub GRAFANA_CLIENT_LOGIN: String,
pub GRAFANA_CLIENT_PASSWORD: String,
}
pub fn get_env() -> &'static ENV {
static INSTANCE: OnceCell<ENV> = OnceCell::new();
Expand All @@ -31,6 +34,12 @@ pub fn get_env() -> &'static ENV {
.expect("Failed to get MAILER_ADDRESS env"),
MAILER_PASSWORD: std::env::var("MAILER_PASSWORD")
.expect("Failed to get MAILER_PASSWORD env"),
GRAFANA_BASE_PATH: std::env::var("GRAFANA_BASE_PATH")
.expect("Failed to get GRAFANA_BASE_PATH env"),
GRAFANA_CLIENT_LOGIN: std::env::var("GRAFANA_CLIENT_LOGIN")
.expect("Failed to get GRAFANA_CLIENT_LOGIN env"),
GRAFANA_CLIENT_PASSWORD: std::env::var("GRAFANA_CLIENT_PASSWORD")
.expect("Failed to get GRAFANA_CLIENT_PASSWORD env"),
};
return env;
})
Expand Down Expand Up @@ -60,3 +69,12 @@ pub fn MAILER_ADDRESS() -> &'static str {
pub fn MAILER_PASSWORD() -> &'static str {
get_env().MAILER_PASSWORD.as_str()
}
pub fn GRAFANA_BASE_PATH() -> &'static str {
get_env().GRAFANA_BASE_PATH.as_str()
}
pub fn GRAFANA_CLIENT_LOGIN() -> &'static str {
get_env().GRAFANA_CLIENT_LOGIN.as_str()
}
pub fn GRAFANA_CLIENT_PASSWORD() -> &'static str {
get_env().GRAFANA_CLIENT_PASSWORD.as_str()
}
10 changes: 9 additions & 1 deletion server/src/http/cloud/accept_team_invite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ use axum::{extract::State, http::StatusCode, Extension, Json};
use database::db::Db;
use garde::Validate;
use log::error;
use openapi::apis::configuration::Configuration;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use ts_rs::TS;

use super::utils::{custom_validate_uuid, validate_request};
use super::{
grafana_utils::add_user_to_team::handle_grafana_add_user_to_team,
utils::{custom_validate_uuid, validate_request},
};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Validate)]
#[ts(export)]
Expand All @@ -25,6 +29,7 @@ pub struct HttpAcceptTeamInviteResponse {}

pub async fn accept_team_invite(
State(db): State<Arc<Db>>,
State(grafana_conf): State<Arc<Configuration>>,
Extension(user_id): Extension<UserId>,
Json(request): Json<HttpAcceptTeamInviteRequest>,
) -> Result<Json<HttpAcceptTeamInviteResponse>, (StatusCode, String)> {
Expand Down Expand Up @@ -96,6 +101,9 @@ pub async fn accept_team_invite(
}
}

// Grafana add user to the team
handle_grafana_add_user_to_team(&grafana_conf, &request.team_id, &user.email).await?;

// Accept invite
let mut tx = match db.connection_pool.begin().await {
Ok(tx) => tx,
Expand Down
104 changes: 104 additions & 0 deletions server/src/http/cloud/grafana_utils/add_user_to_team.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use crate::structs::cloud::{
api_cloud_errors::CloudApiErrors, grafana_error::handle_grafana_error,
};
use axum::http::StatusCode;
use log::{error, warn};
use openapi::{
apis::{
admin_users_api::admin_create_user,
configuration::Configuration,
teams_api::add_team_member,
users_api::{get_user_by_login_or_email, get_user_teams},
},
models::{AddTeamMemberCommand, AdminCreateUserForm},
};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::sync::Arc;

pub async fn handle_grafana_add_user_to_team(
grafana_conf: &Arc<Configuration>,
team_id: &String,
user_email: &String,
) -> Result<(), (StatusCode, String)> {
// Check if user exists, if not create a new user
let user_id = match get_user_by_login_or_email(&grafana_conf, user_email).await {
Ok(user) => user.id,
Err(_) => {
// Create user with the same email as the user, password can be anything, it won't be used
let random_password: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect();

let request = AdminCreateUserForm {
password: Some(random_password),
email: Some(user_email.clone()),
login: None,
name: None,
org_id: None,
};

match admin_create_user(&grafana_conf, request).await {
Ok(user) => user.id,
Err(err) => {
warn!("Failed to create user: {:?}", err);
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
CloudApiErrors::InternalServerError.to_string(),
));
}
}
}
};

// If for some reason user_id is not found, return error
let id = match user_id {
Some(id) => id,
None => {
error!("Failed to get user_id for email: {:?}", user_email);
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
CloudApiErrors::InternalServerError.to_string(),
));
}
};

// Check if user is already in the team
match get_user_teams(&grafana_conf, id.clone()).await {
Ok(teams) => {
let team_id: i64 = team_id.parse().map_err(|err| {
error!("Failed to parse team_id: {:?}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
CloudApiErrors::InternalServerError.to_string(),
)
})?;

// For now we will be checking team id but in the future we might need to swap to team uid
if teams.iter().any(|team| team.id == Some(team_id)) {
return Err((
StatusCode::BAD_REQUEST,
CloudApiErrors::UserAlreadyBelongsToTheTeam.to_string(),
));
}
}
Err(err) => {
warn!("Failed to get user teams: {:?}", err);
return Err(handle_grafana_error(err));
}
}

// Add user to the team
let request = AddTeamMemberCommand { user_id: user_id };

if let Err(err) = add_team_member(&grafana_conf, team_id, request).await {
warn!(
"Failed to add user [{:?}] to team [{:?}], error: {:?}",
user_email, team_id, err
);
return Err(handle_grafana_error(err));
}

Ok(())
}
88 changes: 88 additions & 0 deletions server/src/http/cloud/grafana_utils/create_new_app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use crate::{
statics::DASHBOARD_TEMPLATE_UID,
structs::cloud::{api_cloud_errors::CloudApiErrors, grafana_error::handle_grafana_error},
};
use axum::http::StatusCode;
use log::warn;
use openapi::{
apis::{
configuration::Configuration,
dashboards_api::{get_dashboard_by_uid, import_dashboard},
},
models::ImportDashboardRequest,
};
use serde_json::json;
use std::sync::Arc;

pub async fn handle_grafana_create_new_app(
grafana_conf: &Arc<Configuration>,
app_name: &String,
app_id: &String,
team_id: &String,
) -> Result<(), (StatusCode, String)> {
// Import template dashboard
let mut template_dashboard =
match get_dashboard_by_uid(&grafana_conf, &DASHBOARD_TEMPLATE_UID).await {
Ok(response) => match response.dashboard {
Some(dashboard) => dashboard,
None => {
warn!("Failed to get template dashboard: {:?}", response);
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
CloudApiErrors::DashboardImportFail.to_string(),
));
}
},
Err(err) => {
return Err(handle_grafana_error(err));
}
};

// Modify dashboard template fields
if let Some(uid_field) = template_dashboard.get_mut("uid") {
*uid_field = json!(app_id);
} else {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
CloudApiErrors::DashboardImportFail.to_string(),
));
}

if let Some(id_field) = template_dashboard.get_mut("id") {
*id_field = json!(""); // Set dashboard id to empty string to create a new dashboard
} else {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
CloudApiErrors::DashboardImportFail.to_string(),
));
}

if let Some(title_field) = template_dashboard.get_mut("title") {
*title_field = json!(app_name);
} else {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
CloudApiErrors::DashboardImportFail.to_string(),
));
}

// Import dashboard for the team
if let Err(err) = import_dashboard(
&grafana_conf,
ImportDashboardRequest {
dashboard: Some(template_dashboard),
folder_id: None,
folder_uid: Some(team_id.clone()), // When we create a new team, we create a folder with the same uid as the team id
inputs: None,
overwrite: Some(false),
path: None,
plugin_id: None,
},
)
.await
{
return Err(handle_grafana_error(err));
};

Ok(())
}
Loading
Loading