mirror of
https://github.com/Noratrieb/terustform.git
synced 2026-01-14 08:30:13 +01:00
do stuff (supposedly)
This commit is contained in:
parent
f9da7ebe43
commit
c4e62f9d2d
12 changed files with 437 additions and 79 deletions
|
|
@ -3,8 +3,10 @@ name = "terraform-provider-terustform"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
76
src/example.rs
Normal file
76
src/example.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use crate::{
|
||||
framework::{
|
||||
datasource::{self, DataSource},
|
||||
provider::Provider,
|
||||
DResult,
|
||||
},
|
||||
values::Value,
|
||||
};
|
||||
|
||||
pub struct ExampleProvider {}
|
||||
|
||||
impl Provider for ExampleProvider {
|
||||
fn name(&self) -> String {
|
||||
"terustform".to_owned()
|
||||
}
|
||||
|
||||
fn data_sources(&self) -> Vec<Box<dyn DataSource>> {
|
||||
vec![ExampleDataSource {}.erase()]
|
||||
}
|
||||
}
|
||||
|
||||
struct ExampleDataSource {}
|
||||
|
||||
impl DataSource for ExampleDataSource {
|
||||
fn name(&self, provider_name: &str) -> String {
|
||||
format!("{provider_name}_kitty")
|
||||
}
|
||||
|
||||
fn schema(&self) -> datasource::Schema {
|
||||
datasource::Schema {
|
||||
description: "an example".to_owned(),
|
||||
attributes: HashMap::from([
|
||||
(
|
||||
"name".to_owned(),
|
||||
datasource::Attribute::String {
|
||||
description: "a cool name".to_owned(),
|
||||
mode: datasource::Mode::Required,
|
||||
sensitive: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
"meow".to_owned(),
|
||||
datasource::Attribute::String {
|
||||
description: "the meow of the cat".to_owned(),
|
||||
mode: datasource::Mode::Computed,
|
||||
sensitive: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
"id".to_owned(),
|
||||
datasource::Attribute::String {
|
||||
description: "the ID of the meowy cat".to_owned(),
|
||||
mode: datasource::Mode::Computed,
|
||||
sensitive: false,
|
||||
},
|
||||
),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&self, config: Value) -> DResult<Value> {
|
||||
Ok(Value::Object(BTreeMap::from([
|
||||
(
|
||||
"name".to_owned(),
|
||||
match config {
|
||||
Value::Object(mut obj) => obj.remove("name").unwrap(),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
),
|
||||
("meow".to_owned(), Value::String("mrrrrr".to_owned())),
|
||||
("id".to_owned(), Value::String("0".to_owned())),
|
||||
])))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
pub trait DataSource {
|
||||
fn schema(&self);
|
||||
fn read(&self) -> DResult<()>;
|
||||
}
|
||||
|
||||
pub struct Diagnostics {
|
||||
|
||||
}
|
||||
|
||||
pub type DResult<T> = Result<T, Diagnostics>;
|
||||
|
||||
fn _data_source_obj_safe(_: &dyn DataSource) {}
|
||||
59
src/framework/datasource.rs
Normal file
59
src/framework/datasource.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::values::Value;
|
||||
|
||||
use super::DResult;
|
||||
|
||||
pub trait DataSource: Send + Sync {
|
||||
fn name(&self, provider_name: &str) -> String;
|
||||
fn schema(&self) -> Schema;
|
||||
// todo: probably want some kind of Value+Schema thing like tfsdk? whatever.
|
||||
fn read(&self, config: Value) -> DResult<Value>;
|
||||
|
||||
fn erase(self) -> Box<dyn DataSource>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Schema {
|
||||
pub description: String,
|
||||
pub attributes: HashMap<String, Attribute>,
|
||||
}
|
||||
|
||||
pub enum Attribute {
|
||||
String {
|
||||
description: String,
|
||||
mode: Mode,
|
||||
sensitive: bool,
|
||||
},
|
||||
Int64 {
|
||||
description: String,
|
||||
mode: Mode,
|
||||
sensitive: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Mode {
|
||||
Required,
|
||||
Optional,
|
||||
OptionalComputed,
|
||||
Computed,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn required(&self) -> bool {
|
||||
matches!(self, Self::Required)
|
||||
}
|
||||
|
||||
pub fn optional(&self) -> bool {
|
||||
matches!(self, Self::Optional | Self::OptionalComputed)
|
||||
}
|
||||
|
||||
pub fn computed(&self) -> bool {
|
||||
matches!(self, Self::OptionalComputed | Self::Computed)
|
||||
}
|
||||
}
|
||||
12
src/framework/mod.rs
Normal file
12
src/framework/mod.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
pub mod datasource;
|
||||
pub mod provider;
|
||||
|
||||
use self::datasource::DataSource;
|
||||
|
||||
pub struct Diagnostics {
|
||||
pub(crate) errors: Vec<String>,
|
||||
}
|
||||
|
||||
pub type DResult<T> = Result<T, Diagnostics>;
|
||||
6
src/framework/provider.rs
Normal file
6
src/framework/provider.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
use super::DataSource;
|
||||
|
||||
pub trait Provider: Send + Sync {
|
||||
fn name(&self) -> String;
|
||||
fn data_sources(&self) -> Vec<Box<dyn DataSource>>;
|
||||
}
|
||||
24
src/main.rs
24
src/main.rs
|
|
@ -2,19 +2,25 @@ mod cert;
|
|||
mod framework;
|
||||
mod server;
|
||||
mod values;
|
||||
mod example;
|
||||
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use base64::Engine;
|
||||
use eyre::{bail, Context, Result};
|
||||
use framework::provider::Provider;
|
||||
use tokio::net::UnixListener;
|
||||
use tonic::transport::{Certificate, ServerTlsConfig};
|
||||
use tracing::{info, Level};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
serve(&example::ExampleProvider {}).await
|
||||
}
|
||||
|
||||
async fn serve(provider: &dyn Provider) -> eyre::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::DEBUG)
|
||||
.with_max_level(Level::ERROR)
|
||||
.with_writer(std::io::stderr)
|
||||
.without_time()
|
||||
.init();
|
||||
|
|
@ -43,27 +49,25 @@ async fn main() -> eyre::Result<()> {
|
|||
let uds = UnixListener::bind(socket).wrap_err("failed to bind unix listener")?;
|
||||
let uds_stream = tokio_stream::wrappers::UnixListenerStream::new(uds);
|
||||
|
||||
let token = tokio_util::sync::CancellationToken::new();
|
||||
let shutdown = tokio_util::sync::CancellationToken::new();
|
||||
|
||||
let server = tonic::transport::Server::builder()
|
||||
.tls_config(tls)
|
||||
.wrap_err("invalid TLS config")?
|
||||
.add_service(server::tfplugin6::provider_server::ProviderServer::new(
|
||||
server::MyProvider {
|
||||
shutdown: token.clone(),
|
||||
},
|
||||
.add_service(server::ProviderServer::new(
|
||||
server::ProviderHandler::new(shutdown.clone(), provider),
|
||||
))
|
||||
.add_service(
|
||||
server::plugin::grpc_controller_server::GrpcControllerServer::new(
|
||||
server::MyController {
|
||||
shutdown: token.clone(),
|
||||
server::GrpcControllerServer::new(
|
||||
server::Controller {
|
||||
shutdown: shutdown.clone(),
|
||||
},
|
||||
),
|
||||
)
|
||||
.serve_with_incoming(uds_stream);
|
||||
|
||||
tokio::select! {
|
||||
_ = token.cancelled() => {}
|
||||
_ = shutdown.cancelled() => {}
|
||||
result = server => {
|
||||
result.wrap_err("failed to start server")?;
|
||||
}
|
||||
|
|
|
|||
91
src/server/convert.rs
Normal file
91
src/server/convert.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
use crate::{
|
||||
framework::{
|
||||
datasource::{self, Mode},
|
||||
Diagnostics,
|
||||
},
|
||||
values::Type,
|
||||
};
|
||||
|
||||
use super::grpc::tfplugin6;
|
||||
|
||||
impl datasource::Schema {
|
||||
pub(crate) fn to_tfplugin(self) -> tfplugin6::Schema {
|
||||
tfplugin6::Schema {
|
||||
version: 1,
|
||||
block: Some(tfplugin6::schema::Block {
|
||||
version: 0,
|
||||
attributes: self
|
||||
.attributes
|
||||
.into_iter()
|
||||
.map(|(name, attr)| attr.to_tfplugin(name))
|
||||
.collect(),
|
||||
block_types: vec![],
|
||||
description: self.description,
|
||||
description_kind: tfplugin6::StringKind::Markdown as _,
|
||||
deprecated: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl datasource::Attribute {
|
||||
pub(crate) fn to_tfplugin(self, name: String) -> tfplugin6::schema::Attribute {
|
||||
let mut attr = tfplugin6::schema::Attribute {
|
||||
name,
|
||||
r#type: vec![],
|
||||
nested_type: None,
|
||||
description: "<placeholder, this is a bug in terustform>".to_owned(),
|
||||
required: false,
|
||||
optional: false,
|
||||
computed: true,
|
||||
sensitive: false,
|
||||
description_kind: tfplugin6::StringKind::Markdown as _,
|
||||
deprecated: false,
|
||||
};
|
||||
|
||||
let set_modes = |attr: &mut tfplugin6::schema::Attribute, mode: Mode| {
|
||||
attr.required = mode.required();
|
||||
attr.optional = mode.optional();
|
||||
attr.computed = mode.computed();
|
||||
};
|
||||
|
||||
match self {
|
||||
datasource::Attribute::String {
|
||||
description,
|
||||
mode,
|
||||
sensitive,
|
||||
} => {
|
||||
attr.r#type = Type::String.to_json().into_bytes();
|
||||
attr.description = description;
|
||||
set_modes(&mut attr, mode);
|
||||
attr.sensitive = sensitive;
|
||||
}
|
||||
datasource::Attribute::Int64 {
|
||||
description,
|
||||
mode,
|
||||
sensitive,
|
||||
} => {
|
||||
attr.r#type = Type::Number.to_json().into_bytes();
|
||||
attr.description = description;
|
||||
set_modes(&mut attr, mode);
|
||||
attr.sensitive = sensitive;
|
||||
}
|
||||
}
|
||||
|
||||
attr
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostics {
|
||||
pub(crate) fn to_tfplugin_diags(self) -> Vec<tfplugin6::Diagnostic> {
|
||||
self.errors
|
||||
.into_iter()
|
||||
.map(|err| tfplugin6::Diagnostic {
|
||||
severity: tfplugin6::diagnostic::Severity::Error as _,
|
||||
summary: err,
|
||||
detail: "".to_owned(),
|
||||
attribute: None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -21,11 +21,6 @@ use tracing::info;
|
|||
|
||||
use crate::values::Type;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MyProvider {
|
||||
pub shutdown: CancellationToken,
|
||||
}
|
||||
|
||||
fn empty_schema() -> tfplugin6::Schema {
|
||||
tfplugin6::Schema {
|
||||
version: 1,
|
||||
|
|
@ -41,12 +36,13 @@ fn empty_schema() -> tfplugin6::Schema {
|
|||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Provider for MyProvider {
|
||||
impl Provider for super::ProviderHandler {
|
||||
/// GetMetadata returns upfront information about server capabilities and
|
||||
/// supported resource types without requiring the server to instantiate all
|
||||
/// schema information, which may be memory intensive. This RPC is optional,
|
||||
/// where clients may receive an unimplemented RPC error. Clients should
|
||||
/// ignore the error and call the GetProviderSchema RPC as a fallback.
|
||||
/// Returns data source, managed resource, and function metadata, such as names.
|
||||
async fn get_metadata(
|
||||
&self,
|
||||
request: Request<tfplugin6::get_metadata::Request>,
|
||||
|
|
@ -58,11 +54,15 @@ impl Provider for MyProvider {
|
|||
}
|
||||
/// GetSchema returns schema information for the provider, data resources,
|
||||
/// and managed resources.
|
||||
/// Returns provider schema, provider metaschema, all resource schemas and all data source schemas.
|
||||
async fn get_provider_schema(
|
||||
&self,
|
||||
request: Request<tfplugin6::get_provider_schema::Request>,
|
||||
) -> Result<Response<tfplugin6::get_provider_schema::Response>, Status> {
|
||||
info!("Received get_provider_schema");
|
||||
|
||||
let schemas = self.get_schemas();
|
||||
|
||||
let reply = tfplugin6::get_provider_schema::Response {
|
||||
provider: Some(empty_schema()),
|
||||
provider_meta: Some(empty_schema()),
|
||||
|
|
@ -71,38 +71,16 @@ impl Provider for MyProvider {
|
|||
get_provider_schema_optional: true,
|
||||
move_resource_state: false,
|
||||
}),
|
||||
data_source_schemas: HashMap::from([(
|
||||
"terustform_kitty".to_owned(),
|
||||
tfplugin6::Schema {
|
||||
version: 1,
|
||||
block: Some(tfplugin6::schema::Block {
|
||||
version: 0,
|
||||
attributes: vec![tfplugin6::schema::Attribute {
|
||||
name: "kitten".to_owned(),
|
||||
r#type: Type::String.to_json().into_bytes(),
|
||||
nested_type: None,
|
||||
description: "what sound does the kitten make?".to_owned(),
|
||||
required: false,
|
||||
optional: false,
|
||||
computed: true,
|
||||
sensitive: false,
|
||||
description_kind: 0,
|
||||
deprecated: false,
|
||||
}],
|
||||
block_types: vec![],
|
||||
description: "something or nothing?".to_owned(),
|
||||
description_kind: 0,
|
||||
deprecated: false,
|
||||
}),
|
||||
},
|
||||
)]),
|
||||
resource_schemas: HashMap::from([("terustform_hello".to_owned(), empty_schema())]),
|
||||
data_source_schemas: schemas.data_sources,
|
||||
resource_schemas: schemas.resources,
|
||||
functions: HashMap::default(),
|
||||
diagnostics: vec![],
|
||||
diagnostics: schemas.diagnostics,
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
|
||||
/// Validates the practitioner supplied provider configuration by verifying types conform to the schema and supports value validation diagnostics.
|
||||
async fn validate_provider_config(
|
||||
&self,
|
||||
request: Request<tfplugin6::validate_provider_config::Request>,
|
||||
|
|
@ -115,6 +93,8 @@ impl Provider for MyProvider {
|
|||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
|
||||
/// Validates the practitioner supplied resource configuration by verifying types conform to the schema and supports value validation diagnostics.
|
||||
async fn validate_resource_config(
|
||||
&self,
|
||||
request: Request<tfplugin6::validate_resource_config::Request>,
|
||||
|
|
@ -127,6 +107,8 @@ impl Provider for MyProvider {
|
|||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
|
||||
/// Validates the practitioner supplied data source configuration by verifying types conform to the schema and supports value validation diagnostics.
|
||||
async fn validate_data_resource_config(
|
||||
&self,
|
||||
request: Request<tfplugin6::validate_data_resource_config::Request>,
|
||||
|
|
@ -139,14 +121,24 @@ impl Provider for MyProvider {
|
|||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
|
||||
/// Called when a resource has existing state. Primarily useful for when the schema version does not match the current version.
|
||||
/// The provider is expected to modify the state to upgrade it to the latest schema.
|
||||
async fn upgrade_resource_state(
|
||||
&self,
|
||||
request: Request<tfplugin6::upgrade_resource_state::Request>,
|
||||
) -> Result<Response<tfplugin6::upgrade_resource_state::Response>, Status> {
|
||||
tracing::error!("upgrade_resource_state");
|
||||
todo!("upgrade_resource_state")
|
||||
tracing::info!("upgrade_resource_state");
|
||||
// We don't do anything interesting, it's fine.
|
||||
let reply = tfplugin6::upgrade_resource_state::Response {
|
||||
upgraded_state: None,
|
||||
diagnostics: vec![],
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
/// ////// One-time initialization, called before other functions below
|
||||
/// Passes the practitioner supplied provider configuration to the provider.
|
||||
async fn configure_provider(
|
||||
&self,
|
||||
request: Request<tfplugin6::configure_provider::Request>,
|
||||
|
|
@ -158,13 +150,24 @@ impl Provider for MyProvider {
|
|||
Ok(Response::new(reply))
|
||||
}
|
||||
/// ////// Managed Resource Lifecycle
|
||||
/// Called when refreshing a resource's state.
|
||||
async fn read_resource(
|
||||
&self,
|
||||
request: Request<tfplugin6::read_resource::Request>,
|
||||
) -> Result<Response<tfplugin6::read_resource::Response>, Status> {
|
||||
tracing::error!("read_resource");
|
||||
todo!("read_resource")
|
||||
tracing::info!("read_resource");
|
||||
|
||||
let reply = tfplugin6::read_resource::Response {
|
||||
deferred: None,
|
||||
diagnostics: vec![],
|
||||
new_state: request.into_inner().current_state,
|
||||
private: vec![],
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
|
||||
/// Calculates a plan for a resource. A proposed new state is generated, which the provider can modify.
|
||||
async fn plan_resource_change(
|
||||
&self,
|
||||
request: Request<tfplugin6::plan_resource_change::Request>,
|
||||
|
|
@ -182,6 +185,9 @@ impl Provider for MyProvider {
|
|||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
|
||||
/// Called when a practitioner has approved a planned change.
|
||||
/// The provider is to apply the changes contained in the plan, and return a resulting state matching the given plan.
|
||||
async fn apply_resource_change(
|
||||
&self,
|
||||
request: Request<tfplugin6::apply_resource_change::Request>,
|
||||
|
|
@ -197,6 +203,8 @@ impl Provider for MyProvider {
|
|||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
|
||||
/// Called when importing a resource into state so that the resource becomes managed.
|
||||
async fn import_resource_state(
|
||||
&self,
|
||||
request: Request<tfplugin6::import_resource_state::Request>,
|
||||
|
|
@ -213,26 +221,42 @@ impl Provider for MyProvider {
|
|||
|
||||
todo!("move_resource_state")
|
||||
}
|
||||
|
||||
/// Called when refreshing a data source's state.
|
||||
async fn read_data_source(
|
||||
&self,
|
||||
request: Request<tfplugin6::read_data_source::Request>,
|
||||
) -> Result<Response<tfplugin6::read_data_source::Response>, Status> {
|
||||
tracing::info!("read_data_source");
|
||||
|
||||
let reply = tfplugin6::read_data_source::Response {
|
||||
state: Some(tfplugin6::DynamicValue {
|
||||
msgpack: crate::values::Value::Object(BTreeMap::from([(
|
||||
"kitten".to_owned(),
|
||||
Box::new(crate::values::Value::String("meow".to_owned())),
|
||||
)]))
|
||||
.msg_pack(),
|
||||
json: vec![],
|
||||
}),
|
||||
deferred: None,
|
||||
diagnostics: vec![],
|
||||
let ds = self
|
||||
.state
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.data_sources
|
||||
.get(&request.get_ref().type_name)
|
||||
.unwrap();
|
||||
|
||||
let state = ds.read(crate::values::Value::Object(BTreeMap::from([(
|
||||
"name".to_owned(),
|
||||
crate::values::Value::String("mykitten".to_owned()),
|
||||
)])));
|
||||
let (state, diagnostics) = match state {
|
||||
Ok(s) => (
|
||||
Some(tfplugin6::DynamicValue {
|
||||
msgpack: s.msg_pack(),
|
||||
json: vec![],
|
||||
}),
|
||||
vec![],
|
||||
),
|
||||
Err(errs) => (None, errs.to_tfplugin_diags()),
|
||||
};
|
||||
|
||||
dbg!(request);
|
||||
let reply = tfplugin6::read_data_source::Response {
|
||||
state,
|
||||
deferred: None,
|
||||
diagnostics,
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
|
|
@ -265,7 +289,7 @@ impl Provider for MyProvider {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct MyController {
|
||||
pub struct Controller {
|
||||
pub shutdown: CancellationToken,
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +300,7 @@ async fn shutdown(token: &CancellationToken) -> ! {
|
|||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl plugin::grpc_controller_server::GrpcController for MyController {
|
||||
impl plugin::grpc_controller_server::GrpcController for Controller {
|
||||
async fn shutdown(&self, request: Request<plugin::Empty>) -> Result<Response<plugin::Empty>> {
|
||||
shutdown(&self.shutdown).await
|
||||
}
|
||||
89
src/server/mod.rs
Normal file
89
src/server/mod.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
mod convert;
|
||||
mod grpc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::framework::datasource::{self, DataSource};
|
||||
use crate::framework::provider::Provider;
|
||||
use crate::framework::DResult;
|
||||
|
||||
pub use grpc::plugin::grpc_controller_server::GrpcControllerServer;
|
||||
pub use grpc::tfplugin6::provider_server::ProviderServer;
|
||||
pub use grpc::Controller;
|
||||
|
||||
use self::grpc::tfplugin6;
|
||||
|
||||
pub struct ProviderHandler {
|
||||
shutdown: CancellationToken,
|
||||
/// Delayed diagnostics reporting in `GetProviderSchema` for better UX.
|
||||
state: Result<ProviderState, Vec<tfplugin6::Diagnostic>>,
|
||||
}
|
||||
|
||||
struct ProviderState {
|
||||
data_sources: HashMap<String, Box<dyn DataSource>>,
|
||||
}
|
||||
|
||||
impl ProviderHandler {
|
||||
/// Creates a new `ProviderHandler`.
|
||||
/// 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: &dyn Provider) -> Self {
|
||||
let name = provider.name();
|
||||
let mut data_sources = HashMap::new();
|
||||
let mut errors = vec![];
|
||||
|
||||
for ds in provider.data_sources() {
|
||||
let ds_name = ds.name(&name);
|
||||
let entry = data_sources.insert(ds_name.clone(), ds);
|
||||
if entry.is_some() {
|
||||
errors.push(tfplugin6::Diagnostic {
|
||||
severity: tfplugin6::diagnostic::Severity::Error as _,
|
||||
summary: format!("data source {ds_name} exists more than once"),
|
||||
detail: "".to_owned(),
|
||||
attribute: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let state = if errors.len() > 0 {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(ProviderState { data_sources })
|
||||
};
|
||||
|
||||
Self { shutdown, state }
|
||||
}
|
||||
|
||||
fn get_schemas(&self) -> Schemas {
|
||||
let resources = HashMap::new();
|
||||
let state = match &self.state {
|
||||
Ok(state) => state,
|
||||
Err(errors) => {
|
||||
return Schemas {
|
||||
resources: HashMap::new(),
|
||||
data_sources: HashMap::new(),
|
||||
diagnostics: errors.clone(),
|
||||
}
|
||||
}
|
||||
};
|
||||
let data_sources = state
|
||||
.data_sources
|
||||
.iter()
|
||||
.map(|(name, ds)| (name.to_owned(), ds.schema().to_tfplugin()))
|
||||
.collect::<HashMap<String, tfplugin6::Schema>>();
|
||||
|
||||
Schemas {
|
||||
resources,
|
||||
data_sources,
|
||||
diagnostics: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Schemas {
|
||||
resources: HashMap<String, tfplugin6::Schema>,
|
||||
data_sources: HashMap<String, tfplugin6::Schema>,
|
||||
diagnostics: Vec<tfplugin6::Diagnostic>,
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ impl Type {
|
|||
// this is very dumb and wrong
|
||||
pub enum Value {
|
||||
String(String),
|
||||
Object(BTreeMap<String, Box<Value>>)
|
||||
Object(BTreeMap<String, Value>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
|
|
|
|||
13
test/main.tf
13
test/main.tf
|
|
@ -10,8 +10,17 @@ provider "terustform" {}
|
|||
|
||||
//resource "terustform_hello" "test1" {}
|
||||
|
||||
data "terustform_kitty" "kitty" {}
|
||||
data "terustform_kitty" "kitty" {
|
||||
name = "mykitten"
|
||||
}
|
||||
|
||||
data "terustform_kitty" "hellyes" {
|
||||
name = "a cute kitty"
|
||||
}
|
||||
|
||||
output "meow" {
|
||||
value = data.terustform_kitty.kitty.kitten
|
||||
value = data.terustform_kitty.kitty.id
|
||||
}
|
||||
output "hellyes" {
|
||||
value = data.terustform_kitty.kitty.meow
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue