Fixup auth when using determinate-nixd with long-running builds
This commit is contained in:
parent
11b78639f1
commit
5e7acea3d1
|
@ -1,5 +1,6 @@
|
||||||
use crate::env::Environment;
|
use crate::env::Environment;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
use crate::DETERMINATE_NETRC_PATH;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use attic::cache::CacheName;
|
use attic::cache::CacheName;
|
||||||
use attic::nix_store::{NixStore, StorePath};
|
use attic::nix_store::{NixStore, StorePath};
|
||||||
|
@ -13,7 +14,8 @@ use attic_client::{
|
||||||
use reqwest::header::HeaderValue;
|
use reqwest::header::HeaderValue;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::Path;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
@ -38,54 +40,13 @@ pub async fn init_cache(
|
||||||
auth_method: &super::FlakeHubAuthSource,
|
auth_method: &super::FlakeHubAuthSource,
|
||||||
) -> Result<State> {
|
) -> Result<State> {
|
||||||
// Parse netrc to get the credentials for api.flakehub.com.
|
// Parse netrc to get the credentials for api.flakehub.com.
|
||||||
let netrc = {
|
let netrc_path = auth_method.as_path_buf();
|
||||||
let netrc_path = auth_method.as_path_buf();
|
let NetrcInfo {
|
||||||
let mut netrc_file = File::open(&netrc_path).await.map_err(|e| {
|
netrc,
|
||||||
Error::Internal(format!("Failed to open {}: {}", netrc_path.display(), e))
|
flakehub_cache_server_hostname,
|
||||||
})?;
|
flakehub_login,
|
||||||
let mut netrc_contents = String::new();
|
flakehub_password,
|
||||||
netrc_file
|
} = extract_info_from_netrc(&netrc_path, flakehub_api_server, flakehub_cache_server).await?;
|
||||||
.read_to_string(&mut netrc_contents)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
Error::Internal(format!(
|
|
||||||
"Failed to read {} contents: {}",
|
|
||||||
netrc_path.display(),
|
|
||||||
e
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
netrc_rs::Netrc::parse(netrc_contents, false).map_err(Error::Netrc)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let flakehub_netrc_entry = {
|
|
||||||
netrc
|
|
||||||
.machines
|
|
||||||
.iter()
|
|
||||||
.find(|machine| {
|
|
||||||
machine.name.as_ref() == flakehub_api_server.host().map(|x| x.to_string()).as_ref()
|
|
||||||
})
|
|
||||||
.ok_or_else(|| Error::MissingCreds(flakehub_api_server.to_string()))?
|
|
||||||
.to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
let flakehub_cache_server_hostname = flakehub_cache_server
|
|
||||||
.host()
|
|
||||||
.ok_or_else(|| Error::BadUrl(flakehub_cache_server.to_owned()))?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let flakehub_login = flakehub_netrc_entry.login.as_ref().ok_or_else(|| {
|
|
||||||
Error::Config(format!(
|
|
||||||
"netrc file does not contain a login for '{}'",
|
|
||||||
flakehub_api_server
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let flakehub_password = flakehub_netrc_entry.password.ok_or_else(|| {
|
|
||||||
Error::Config(format!(
|
|
||||||
"netrc file does not contain a password for '{}'",
|
|
||||||
flakehub_api_server
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let super::FlakeHubAuthSource::Netrc(netrc_path) = auth_method {
|
if let super::FlakeHubAuthSource::Netrc(netrc_path) = auth_method {
|
||||||
// Append an entry for the FlakeHub cache server to netrc.
|
// Append an entry for the FlakeHub cache server to netrc.
|
||||||
|
@ -137,24 +98,50 @@ pub async fn init_cache(
|
||||||
|
|
||||||
// Periodically refresh JWT in GitHub Actions environment
|
// Periodically refresh JWT in GitHub Actions environment
|
||||||
if environment.is_github_actions() {
|
if environment.is_github_actions() {
|
||||||
if let super::FlakeHubAuthSource::Netrc(path) = auth_method {
|
match auth_method {
|
||||||
// NOTE(cole-h): This is a workaround -- at the time of writing, GitHub Actions JWTs are only
|
super::FlakeHubAuthSource::Netrc(path) => {
|
||||||
// valid for 5 minutes after being issued. FlakeHub uses these JWTs for authentication, which
|
// NOTE(cole-h): This is a workaround -- at the time of writing, GitHub Actions JWTs are only
|
||||||
// means that after those 5 minutes have passed and the token is expired, FlakeHub (and by
|
// valid for 5 minutes after being issued. FlakeHub uses these JWTs for authentication, which
|
||||||
// extension FlakeHub Cache) will no longer allow requests using this token. However, GitHub
|
// means that after those 5 minutes have passed and the token is expired, FlakeHub (and by
|
||||||
// gives us a way to repeatedly request new tokens, so we utilize that and refresh the token
|
// extension FlakeHub Cache) will no longer allow requests using this token. However, GitHub
|
||||||
// every 2 minutes (less than half of the lifetime of the token).
|
// gives us a way to repeatedly request new tokens, so we utilize that and refresh the token
|
||||||
let netrc_path_clone = path.to_path_buf();
|
// every 2 minutes (less than half of the lifetime of the token).
|
||||||
let initial_github_jwt_clone = flakehub_password.clone();
|
let netrc_path_clone = path.to_path_buf();
|
||||||
let flakehub_cache_server_clone = flakehub_cache_server.to_string();
|
let initial_github_jwt_clone = flakehub_password.clone();
|
||||||
let api_clone = api.clone();
|
let flakehub_cache_server_clone = flakehub_cache_server.to_string();
|
||||||
|
let api_clone = api.clone();
|
||||||
|
|
||||||
tokio::task::spawn(refresh_github_actions_jwt_worker(
|
tokio::task::spawn(refresh_github_actions_jwt_worker(
|
||||||
netrc_path_clone,
|
netrc_path_clone,
|
||||||
initial_github_jwt_clone,
|
initial_github_jwt_clone,
|
||||||
flakehub_cache_server_clone,
|
flakehub_cache_server_clone,
|
||||||
api_clone,
|
api_clone,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
crate::FlakeHubAuthSource::DeterminateNixd => {
|
||||||
|
// NOTE(cole-h): This is a workaround -- at the time of writing, determinate-nixd
|
||||||
|
// handles the GitHub Actions JWT refreshing for us, which means we don't know when
|
||||||
|
// this will happen. At the moment, it does it roughly every 2 minutes (less than
|
||||||
|
// half of the total lifetime of the issued token), so refreshing every 30 seconds
|
||||||
|
// is "fine".
|
||||||
|
let api_clone = api.clone();
|
||||||
|
let netrc_file = PathBuf::from(DETERMINATE_NETRC_PATH);
|
||||||
|
let flakehub_api_server_clone = flakehub_api_server.clone();
|
||||||
|
let flakehub_cache_server_clone = flakehub_cache_server.clone();
|
||||||
|
|
||||||
|
let initial_meta = tokio::fs::metadata(&netrc_file).await.map_err(|e| {
|
||||||
|
Error::Io(e, format!("getting metadata of {}", netrc_file.display()))
|
||||||
|
})?;
|
||||||
|
let initial_inode = initial_meta.ino();
|
||||||
|
|
||||||
|
tokio::task::spawn(refresh_determinate_token_worker(
|
||||||
|
netrc_file,
|
||||||
|
initial_inode,
|
||||||
|
flakehub_api_server_clone,
|
||||||
|
flakehub_cache_server_clone,
|
||||||
|
api_clone,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,6 +221,72 @@ pub async fn init_cache(
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct NetrcInfo {
|
||||||
|
netrc: netrc_rs::Netrc,
|
||||||
|
flakehub_cache_server_hostname: String,
|
||||||
|
flakehub_login: String,
|
||||||
|
flakehub_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn extract_info_from_netrc(
|
||||||
|
netrc_path: &Path,
|
||||||
|
flakehub_api_server: &Url,
|
||||||
|
flakehub_cache_server: &Url,
|
||||||
|
) -> Result<NetrcInfo> {
|
||||||
|
let netrc = {
|
||||||
|
let mut netrc_file = File::open(netrc_path).await.map_err(|e| {
|
||||||
|
Error::Internal(format!("Failed to open {}: {}", netrc_path.display(), e))
|
||||||
|
})?;
|
||||||
|
let mut netrc_contents = String::new();
|
||||||
|
netrc_file
|
||||||
|
.read_to_string(&mut netrc_contents)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::Internal(format!(
|
||||||
|
"Failed to read {} contents: {}",
|
||||||
|
netrc_path.display(),
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
netrc_rs::Netrc::parse(netrc_contents, false).map_err(Error::Netrc)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let flakehub_netrc_entry = netrc
|
||||||
|
.machines
|
||||||
|
.iter()
|
||||||
|
.find(|machine| {
|
||||||
|
machine.name.as_ref() == flakehub_api_server.host().map(|x| x.to_string()).as_ref()
|
||||||
|
})
|
||||||
|
.ok_or_else(|| Error::MissingCreds(flakehub_api_server.to_string()))?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let flakehub_cache_server_hostname = flakehub_cache_server
|
||||||
|
.host()
|
||||||
|
.ok_or_else(|| Error::BadUrl(flakehub_cache_server.to_owned()))?
|
||||||
|
.to_string();
|
||||||
|
let flakehub_login = flakehub_netrc_entry.login.ok_or_else(|| {
|
||||||
|
Error::Config(format!(
|
||||||
|
"netrc file does not contain a login for '{}'",
|
||||||
|
flakehub_api_server
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let flakehub_password = flakehub_netrc_entry.password.ok_or_else(|| {
|
||||||
|
Error::Config(format!(
|
||||||
|
"netrc file does not contain a password for '{}'",
|
||||||
|
flakehub_api_server
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(NetrcInfo {
|
||||||
|
netrc,
|
||||||
|
flakehub_cache_server_hostname,
|
||||||
|
flakehub_login,
|
||||||
|
flakehub_password,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn enqueue_paths(state: &State, store_paths: Vec<StorePath>) -> Result<()> {
|
pub async fn enqueue_paths(state: &State, store_paths: Vec<StorePath>) -> Result<()> {
|
||||||
state.push_session.queue_many(store_paths)?;
|
state.push_session.queue_many(store_paths)?;
|
||||||
|
|
||||||
|
@ -367,3 +420,72 @@ async fn rewrite_github_actions_token(
|
||||||
|
|
||||||
Ok(new_github_jwt_string)
|
Ok(new_github_jwt_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn refresh_determinate_token_worker(
|
||||||
|
netrc_file: PathBuf,
|
||||||
|
mut inode: u64,
|
||||||
|
flakehub_api_server: Url,
|
||||||
|
flakehub_cache_server: Url,
|
||||||
|
api_clone: Arc<RwLock<ApiClient>>,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
|
||||||
|
|
||||||
|
let meta = tokio::fs::metadata(&netrc_file)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::Io(e, format!("getting metadata of {}", netrc_file.display())));
|
||||||
|
|
||||||
|
let Ok(meta) = meta else {
|
||||||
|
tracing::error!(e = ?meta);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let current_inode = meta.ino();
|
||||||
|
|
||||||
|
if current_inode == inode {
|
||||||
|
tracing::debug!("current inode is the same, file didn't change");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("current inode is different, file changed");
|
||||||
|
inode = current_inode;
|
||||||
|
|
||||||
|
let flakehub_password = match extract_info_from_netrc(
|
||||||
|
&netrc_file,
|
||||||
|
&flakehub_api_server,
|
||||||
|
&flakehub_cache_server,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(NetrcInfo {
|
||||||
|
flakehub_password, ..
|
||||||
|
}) => flakehub_password,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!(?e, "Failed to extract auth info from netrc");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let server_config = ServerConfig {
|
||||||
|
endpoint: flakehub_cache_server.to_string(),
|
||||||
|
token: Some(attic_client::config::ServerTokenConfig::Raw {
|
||||||
|
token: flakehub_password,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_api = ApiClient::from_server_config(server_config.clone());
|
||||||
|
|
||||||
|
let Ok(new_api) = new_api else {
|
||||||
|
tracing::error!(e = ?new_api, "Failed to construct new ApiClient");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut api_client = api_clone.write().await;
|
||||||
|
*api_client = new_api;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("Stored new token in API client, sleeping for 30s");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ use gha_cache::Credentials;
|
||||||
|
|
||||||
const DETERMINATE_STATE_DIR: &str = "/nix/var/determinate";
|
const DETERMINATE_STATE_DIR: &str = "/nix/var/determinate";
|
||||||
const DETERMINATE_NIXD_SOCKET_NAME: &str = "determinate-nixd.socket";
|
const DETERMINATE_NIXD_SOCKET_NAME: &str = "determinate-nixd.socket";
|
||||||
|
const DETERMINATE_NETRC_PATH: &str = "/nix/var/determinate/netrc";
|
||||||
|
|
||||||
// TODO(colemickens): refactor, move with other UDS stuff (or all PBH stuff) to new file
|
// TODO(colemickens): refactor, move with other UDS stuff (or all PBH stuff) to new file
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
|
Loading…
Reference in a new issue