Skip to content

Commit

Permalink
Add opencast.external_api_node config value
Browse files Browse the repository at this point in the history
This allows admins to override which OC node is used for external API
operations. Previously, `sync_node` was used, which was not always
correct.

Fixes #1216
  • Loading branch information
LukasKalbertodt committed Aug 7, 2024
1 parent 4deb15b commit 4124484
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 20 deletions.
11 changes: 11 additions & 0 deletions backend/src/cmd/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub(crate) async fn run(shared: &args::Shared, args: &Args) -> Result<()> {
};
let meili = check_meili(&config).await;
let opencast_sync = check_opencast_sync(&config).await;
let oc_external_api = check_external_api(&config).await;
info!("Done verifing various things");


Expand Down Expand Up @@ -61,6 +62,7 @@ pub(crate) async fn run(shared: &args::Shared, args: &Args) -> Result<()> {
_ => {},
}
print_outcome(&mut any_errors, "Connection to Opencast harvesting API", &opencast_sync);
print_outcome(&mut any_errors, "Connection to Opencast external API", &oc_external_api);

println!();
if any_errors {
Expand Down Expand Up @@ -155,6 +157,15 @@ async fn check_opencast_sync(config: &Config) -> Result<()> {
Ok(())
}

async fn check_external_api(config: &Config) -> Result<()> {
let client = OcClient::new(config)?;
let versions = client.external_api_versions().await?;
info!("External API version: {}", versions.default);
debug!("External API supported versions: {:?}", versions.versions);
// TODO: actually check the version against our requirements maybe?
Ok(())
}

async fn check_db_migrations(db_pool: &deadpool_postgres::Pool) -> Result<MigrationPlan> {
let mut db = db_pool.get().await?;
let tx = db.transaction().await?;
Expand Down
8 changes: 8 additions & 0 deletions backend/src/config/opencast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub(crate) struct OpencastConfig {
/// the ingest API.
pub(crate) upload_node: Option<HttpHost>,

/// Explicitly set Opencast node for "external API" use (used to modify
/// Opencast data from Tobira).
pub(crate) external_api_node: Option<HttpHost>,

/// Explicitly set base-URL to Opencast Studio.
///
/// Example: "https://admin.oc.my-uni.edu/studio".
Expand Down Expand Up @@ -68,6 +72,10 @@ impl OpencastConfig {
self.upload_node.as_ref().unwrap_or_else(|| self.unwrap_host())
}

pub(crate) fn external_api_node(&self) -> &HttpHost {
self.external_api_node.as_ref().unwrap_or_else(|| self.unwrap_host())
}

pub(crate) fn studio_url(&self) -> ToolBaseUri {
self.studio_url.clone().unwrap_or_else(|| {
let host = self.unwrap_host();
Expand Down
62 changes: 43 additions & 19 deletions backend/src/sync/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ use chrono::{DateTime, Utc, TimeZone};
use hyper::{
Response, Request, StatusCode,
body::Incoming,
http::{self, request, uri::{Authority, Scheme, Uri}},
http::{self, request, uri::Uri},
};
use hyper_rustls::HttpsConnector;
use hyper_util::client::legacy::{connect::HttpConnector, Client};
use secrecy::{ExposeSecret, Secret};
use serde::Deserialize;
use tap::TapFallible;

use crate::{
config::Config,
config::{Config, HttpHost},
prelude::*,
sync::harvest::HarvestResponse,
util::download_body,
Expand All @@ -29,8 +30,8 @@ type RequestBody = http_body_util::Full<Bytes>;
/// Used to send request to the harvesting API.
pub(crate) struct OcClient {
http_client: Client<HttpsConnector<HttpConnector>, RequestBody>,
scheme: Scheme,
authority: Authority,
sync_node: HttpHost,
external_api_node: HttpHost,
auth_header: Secret<String>,
username: String,
}
Expand All @@ -54,16 +55,16 @@ impl OcClient {

Ok(Self {
http_client,
scheme: config.opencast.sync_node().scheme.clone(),
authority: config.opencast.sync_node().authority.clone(),
sync_node: config.opencast.sync_node().clone(),
external_api_node: config.opencast.external_api_node().clone(),
auth_header: Secret::new(auth_header),
username: config.sync.user.clone(),
})
}

pub(crate) async fn get_version(&self) -> Result<VersionResponse> {
pub(crate) async fn get_tobira_api_version(&self) -> Result<VersionResponse> {
trace!("Sending request to '{}'", Self::VERSION_PATH);
let (uri, req) = self.build_req(Self::VERSION_PATH);
let (uri, req) = self.build_authed_req(&self.sync_node, Self::VERSION_PATH);

let response = self.http_client.request(req)
.await
Expand Down Expand Up @@ -95,7 +96,7 @@ impl OcClient {
since.timestamp_millis(),
preferred_amount,
);
let (uri, req) = self.build_req(&pq);
let (uri, req) = self.build_authed_req(&self.sync_node, &pq);

trace!("Sending harvest request (since = {:?}): GET {}", since, uri);

Expand Down Expand Up @@ -127,7 +128,8 @@ impl OcClient {

/// Sends the given serialized JSON to the `/stats` endpoint in Opencast.
pub async fn send_stats(&self, stats: String) -> Result<Response<Incoming>> {
let req = self.req_builder(Self::STATS_PATH)
// TODO: maybe introduce configurable node for this
let req = self.authed_req_builder(&self.external_api_node, Self::STATS_PATH)
.method(http::Method::POST)
.header(http::header::CONTENT_TYPE, "application/json")
.body(stats.into())
Expand All @@ -136,35 +138,51 @@ impl OcClient {
self.http_client.request(req).await.map_err(Into::into)
}

pub async fn external_api_versions(&self) -> Result<ExternalApiVersions> {
let req = self.authed_req_builder(&self.external_api_node, "/api/version")
.body(RequestBody::empty())
.expect("failed to build request");
let uri = req.uri().clone();
let response = self.http_client.request(req)
.await
.with_context(|| format!("HTTP request failed (uri: '{uri}')"))?;

let (out, _) = self.deserialize_response(response, &uri).await?;
Ok(out)
}

pub async fn delete_event(&self, oc_id: &String) -> Result<Response<Incoming>> {
let pq = format!("/api/events/{}", oc_id);
let req = self.req_builder(&pq)
let req = self.authed_req_builder(&self.external_api_node, &pq)
.method(http::Method::DELETE)
.body(RequestBody::empty())
.expect("failed to build request");

self.http_client.request(req).await.map_err(Into::into)
}

fn build_req(&self, path_and_query: &str) -> (Uri, Request<RequestBody>) {
let req = self.req_builder(path_and_query)
fn build_authed_req(&self, node: &HttpHost, path_and_query: &str) -> (Uri, Request<RequestBody>) {
let req = self.authed_req_builder(node, path_and_query)
.body(RequestBody::empty())
.expect("bug: failed to build request");

(req.uri().clone(), req)
}

fn req_builder(&self, path_and_query: &str) -> request::Builder {
fn authed_req_builder(&self, node: &HttpHost, path_and_query: &str) -> request::Builder {
self.req_builder(node, path_and_query)
.header("Authorization", self.auth_header.expose_secret())
}

fn req_builder(&self, node: &HttpHost, path_and_query: &str) -> request::Builder {
let uri = Uri::builder()
.scheme(self.scheme.clone())
.authority(self.authority.clone())
.scheme(node.scheme.clone())
.authority(node.authority.clone())
.path_and_query(path_and_query)
.build()
.expect("bug: failed build URI");

Request::builder()
.uri(&uri)
.header("Authorization", self.auth_header.expose_secret())
Request::builder().uri(&uri)
}

async fn deserialize_response<T: for<'de> serde::Deserialize<'de>>(
Expand Down Expand Up @@ -199,3 +217,9 @@ impl OcClient {
Ok((out, body.len()))
}
}

#[derive(Deserialize)]
pub struct ExternalApiVersions {
pub default: String,
pub versions: Vec<String>,
}
2 changes: 1 addition & 1 deletion backend/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub(crate) async fn run(daemon: bool, db: DbConnection, config: &Config) -> Resu
}

pub(crate) async fn check_compatibility(client: &OcClient) -> Result<()> {
let response = client.get_version().await.context("failed to fetch API version")?;
let response = client.get_tobira_api_version().await.context("failed to fetch API version")?;
let version = response.version();
if !version.is_compatible() {
bail!("Tobira-module API version incompatible! Required: \
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/setup/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,10 @@
# the ingest API.
#upload_node =

# Explicitly set Opencast node for "external API" use (used to modify
# Opencast data from Tobira).
#external_api_node =

# Explicitly set base-URL to Opencast Studio.
#
# Example: "https://admin.oc.my-uni.edu/studio".
Expand Down

0 comments on commit 4124484

Please sign in to comment.