Stub out resources

This commit is contained in:
nora 2024-04-29 19:38:12 +02:00
parent 3c891034ee
commit 60de4d5ba8
7 changed files with 155 additions and 39 deletions

View file

@ -1 +1 @@
/.env
/.env.sh

View file

@ -4,11 +4,7 @@ mod resources;
use std::collections::HashMap;
use eyre::Context;
use terustform::{
datasource::DataSource,
provider::{MkDataSource, Provider},
DResult, EyreExt, Schema, Value,
};
use terustform::{datasource::DataSource, provider::Provider, DResult, EyreExt, Schema, Value};
#[tokio::main]
async fn main() -> eyre::Result<()> {
@ -44,11 +40,15 @@ impl Provider for ExampleProvider {
Ok(client)
}
fn data_sources(&self) -> Vec<MkDataSource<Self::Data>> {
fn data_sources(&self) -> terustform::provider::DataSources<Self> {
vec![
resources::kitty::ExampleDataSource::erase(),
resources::hugo::HugoDataSource::erase(),
resources::class_data_source::ClassDataSource::erase(),
]
}
fn resources(&self) -> terustform::provider::Resources<Self> {
vec![]
}
}

View file

@ -8,21 +8,6 @@ terraform {
provider "corsschool" {}
//resource "terustform_hello" "test1" {}
data "corsschool_kitty" "kitty" {
name = "aa mykitten"
}
data "corsschool_kitty" "hellyes" {
name = "aa a cute kitty"
}
output "cat1" {
value = data.corsschool_kitty.kitty.meow
}
output "cat2" {
value = data.corsschool_kitty.hellyes.meow
}
data "corsschool_hugo" "hugo" {}
output "hugo" {
value = data.corsschool_hugo.hugo

View file

@ -9,6 +9,7 @@ mod values;
// Public modules
pub mod datasource;
pub mod provider;
pub mod resource;
// Re-exports
pub use diag::*;

View file

@ -1,6 +1,15 @@
use std::{future::Future, sync::Arc};
use crate::{datasource::DataSource, DResult, Schema, Value};
use crate::{datasource::DataSource, resource::Resource, DResult, Schema, Value};
// This setup is a bit complicated.
// In this explanation, substitute "`Resource`" for "`Resource` or `DataSource`".
// Semantically, we want to store a `HashMap<String, Box<dyn Resource>>`.
// But this doesn't quite work.
// The reason for this is that we want our `dyn Resource`s to be able to store `ProviderData` directly.
// But `ProviderData` is only available after configuration, and we need to know the schema _before_ configuration.
// So we turn the `dyn Resource` into a _statically known_ `MkResource` that contains the constructor and the schema.
// Then after configuration, we invoke the constructor and get our `dyn Resource`.
pub trait ProviderData: Clone + Send + Sync + 'static {}
impl<D: Clone + Send + Sync + 'static> ProviderData for D {}
@ -40,10 +49,50 @@ impl<D: ProviderData> MkDataSource<D> {
}
}
pub struct MkResource<D: ProviderData> {
pub(crate) name: fn(&str) -> String,
pub(crate) schema: Schema,
pub(crate) mk: fn(D) -> DResult<StoredResource<D>>,
}
pub(crate) struct StoredResource<D: ProviderData> {
pub(crate) ds: Arc<dyn Resource<ProviderData = D>>,
pub(crate) schema: Schema,
}
impl<D: ProviderData> Clone for StoredResource<D> {
fn clone(&self) -> Self {
Self {
ds: self.ds.clone(),
schema: self.schema.clone(),
}
}
}
impl<D: ProviderData> MkResource<D> {
pub fn create<Rs: Resource<ProviderData = D>>() -> Self {
Self {
name: Rs::name,
schema: Rs::schema(),
mk: |data| {
Ok(StoredResource {
ds: Arc::new(Rs::new(data)?),
schema: Rs::schema(),
})
},
}
}
}
pub type DataSources<P> = Vec<MkDataSource<<P as Provider>::Data>>;
pub type Resources<P> = Vec<MkResource<<P as Provider>::Data>>;
pub trait Provider: Send + Sync + Sized + 'static {
type Data: ProviderData;
fn name(&self) -> String;
fn schema(&self) -> Schema;
fn configure(&self, config: Value) -> impl Future<Output = DResult<Self::Data>> + Send;
fn data_sources(&self) -> Vec<MkDataSource<Self::Data>>;
fn data_sources(&self) -> DataSources<Self>;
fn resources(&self) -> Resources<Self>;
}

View file

@ -0,0 +1,35 @@
use crate::{
provider::{MkResource, ProviderData},
values::Value,
Schema,
};
use super::DResult;
#[crate::async_trait]
pub trait Resource: Send + Sync + 'static {
type ProviderData: ProviderData;
// todo: probably want some kind of Value+Schema thing like tfsdk? whatever.
async fn read(&self, config: Value) -> DResult<Value>;
async fn create(&self, config: Value) -> DResult<Value>;
async fn update(&self, config: Value) -> DResult<Value>;
async fn delete(&self, config: Value) -> DResult<Value>;
fn name(provider_name: &str) -> String
where
Self: Sized;
fn schema() -> Schema
where
Self: Sized;
fn new(data: Self::ProviderData) -> DResult<Self>
where
Self: Sized;
fn erase() -> MkResource<Self::ProviderData>
where
Self: Sized,
{
MkResource::create::<Self>()
}
}

View file

@ -4,7 +4,7 @@ use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use crate::{
provider::{MkDataSource, Provider, StoredDataSource},
provider::{MkDataSource, MkResource, Provider, StoredDataSource, StoredResource},
DResult, Diagnostic, Diagnostics, Type, Value,
};
@ -20,12 +20,14 @@ enum ProviderState<P: Provider> {
Setup {
provider: P,
mk_ds: HashMap<String, MkDataSource<P::Data>>,
mk_rs: HashMap<String, MkResource<P::Data>>,
},
Failed {
diags: Diagnostics,
},
Configured {
data_sources: HashMap<String, StoredDataSource<P::Data>>,
resources: HashMap<String, StoredResource<P::Data>>,
},
}
@ -34,11 +36,10 @@ impl<P: Provider> ProviderHandler<P> {
/// This function is infallible, as it is not called during a time where reporting errors nicely is possible.
/// If there's an error, we just taint our internal state and report errors in `GetProviderSchema`.
pub fn new(shutdown: CancellationToken, provider: P) -> Self {
let mut mk_ds = HashMap::new();
let mut errors = Diagnostics::default();
let name = provider.name();
let mut mk_ds = HashMap::new();
for ds in provider.data_sources() {
let ds_name = (ds.name)(&name);
let entry = mk_ds.insert(ds_name.clone(), ds);
@ -49,10 +50,25 @@ impl<P: Provider> ProviderHandler<P> {
}
}
let mut mk_rs = HashMap::new();
for rs in provider.resources() {
let rs_name = (rs.name)(&name);
let entry = mk_rs.insert(rs_name.clone(), rs);
if entry.is_some() {
errors.push(Diagnostic::error_string(format!(
"data source {rs_name} exists more than once"
)));
}
}
let state = if errors.has_errors() {
ProviderState::Failed { diags: errors }
} else {
ProviderState::Setup { provider, mk_ds }
ProviderState::Setup {
provider,
mk_ds,
mk_rs,
}
};
Self {
shutdown,
@ -65,8 +81,12 @@ impl<P: Provider> ProviderHandler<P> {
config: &Option<tfplugin6::DynamicValue>,
) -> Vec<tfplugin6::Diagnostic> {
let mut state = self.state.lock().await;
let (provider, mk_ds) = match &*state {
ProviderState::Setup { provider, mk_ds } => (provider, mk_ds),
let (provider, mk_ds, mk_rs) = match &*state {
ProviderState::Setup {
provider,
mk_ds,
mk_rs,
} => (provider, mk_ds, mk_rs),
ProviderState::Failed { diags } => return diags.clone().to_tfplugin_diags(),
ProviderState::Configured { .. } => unreachable!("called configure twice"),
};
@ -79,10 +99,9 @@ impl<P: Provider> ProviderHandler<P> {
Ok(data) => data,
Err(errs) => return errs.to_tfplugin_diags(),
};
let mut data_sources = HashMap::new();
let mut diags = vec![];
let mut data_sources = HashMap::new();
for (ds_name, ds) in mk_ds {
let ds = (ds.mk)(data.clone());
@ -94,16 +113,35 @@ impl<P: Provider> ProviderHandler<P> {
}
}
*state = ProviderState::Configured { data_sources };
let mut resources = HashMap::new();
for (rs_name, rs) in mk_rs {
let rs = (rs.mk)(data.clone());
match rs {
Ok(rs) => {
resources.insert(rs_name.clone(), rs);
}
Err(errs) => diags.extend(errs.to_tfplugin_diags()),
}
}
*state = ProviderState::Configured {
data_sources,
resources,
};
diags
}
pub(super) async fn get_schemas(&self) -> Schemas {
let state = self.state.lock().await;
let resources = HashMap::new();
let mk_ds = match &*state {
ProviderState::Setup { mk_ds, provider: _ } => mk_ds,
let (mk_ds, mk_rs) = match &*state {
ProviderState::Setup {
mk_ds,
mk_rs,
provider: _,
} => (mk_ds, mk_rs),
ProviderState::Failed { diags } => {
return Schemas {
resources: HashMap::new(),
@ -122,6 +160,13 @@ impl<P: Provider> ProviderHandler<P> {
(name.to_owned(), ds.schema.clone().to_tfplugin())
})
.collect::<HashMap<String, tfplugin6::Schema>>();
let resources = mk_rs
.iter()
.map(|(name, ds)| {
tracing::debug!(?name, "Initializing resources");
(name.to_owned(), ds.schema.clone().to_tfplugin())
})
.collect::<HashMap<String, tfplugin6::Schema>>();
Schemas {
resources,
@ -144,9 +189,10 @@ impl<P: Provider> ProviderHandler<P> {
ProviderState::Failed { diags } => {
return (None, diags.clone().to_tfplugin_diags())
}
ProviderState::Configured { data_sources } => {
data_sources.get(type_name).unwrap().clone()
}
ProviderState::Configured {
data_sources,
resources: _,
} => data_sources.get(type_name).unwrap().clone(),
}
};