mirror of
https://github.com/Noratrieb/terustform.git
synced 2026-01-14 16:35:11 +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"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
[dependencies]
|
[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 framework;
|
||||||
mod server;
|
mod server;
|
||||||
mod values;
|
mod values;
|
||||||
|
mod example;
|
||||||
|
|
||||||
use std::{env, path::PathBuf};
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use eyre::{bail, Context, Result};
|
use eyre::{bail, Context, Result};
|
||||||
|
use framework::provider::Provider;
|
||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
use tonic::transport::{Certificate, ServerTlsConfig};
|
use tonic::transport::{Certificate, ServerTlsConfig};
|
||||||
use tracing::{info, Level};
|
use tracing::{info, Level};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
|
serve(&example::ExampleProvider {}).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve(provider: &dyn Provider) -> eyre::Result<()> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_max_level(Level::DEBUG)
|
.with_max_level(Level::ERROR)
|
||||||
.with_writer(std::io::stderr)
|
.with_writer(std::io::stderr)
|
||||||
.without_time()
|
.without_time()
|
||||||
.init();
|
.init();
|
||||||
|
|
@ -43,27 +49,25 @@ async fn main() -> eyre::Result<()> {
|
||||||
let uds = UnixListener::bind(socket).wrap_err("failed to bind unix listener")?;
|
let uds = UnixListener::bind(socket).wrap_err("failed to bind unix listener")?;
|
||||||
let uds_stream = tokio_stream::wrappers::UnixListenerStream::new(uds);
|
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()
|
let server = tonic::transport::Server::builder()
|
||||||
.tls_config(tls)
|
.tls_config(tls)
|
||||||
.wrap_err("invalid TLS config")?
|
.wrap_err("invalid TLS config")?
|
||||||
.add_service(server::tfplugin6::provider_server::ProviderServer::new(
|
.add_service(server::ProviderServer::new(
|
||||||
server::MyProvider {
|
server::ProviderHandler::new(shutdown.clone(), provider),
|
||||||
shutdown: token.clone(),
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
.add_service(
|
.add_service(
|
||||||
server::plugin::grpc_controller_server::GrpcControllerServer::new(
|
server::GrpcControllerServer::new(
|
||||||
server::MyController {
|
server::Controller {
|
||||||
shutdown: token.clone(),
|
shutdown: shutdown.clone(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.serve_with_incoming(uds_stream);
|
.serve_with_incoming(uds_stream);
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = token.cancelled() => {}
|
_ = shutdown.cancelled() => {}
|
||||||
result = server => {
|
result = server => {
|
||||||
result.wrap_err("failed to start 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;
|
use crate::values::Type;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MyProvider {
|
|
||||||
pub shutdown: CancellationToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn empty_schema() -> tfplugin6::Schema {
|
fn empty_schema() -> tfplugin6::Schema {
|
||||||
tfplugin6::Schema {
|
tfplugin6::Schema {
|
||||||
version: 1,
|
version: 1,
|
||||||
|
|
@ -41,12 +36,13 @@ fn empty_schema() -> tfplugin6::Schema {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tonic::async_trait]
|
#[tonic::async_trait]
|
||||||
impl Provider for MyProvider {
|
impl Provider for super::ProviderHandler {
|
||||||
/// GetMetadata returns upfront information about server capabilities and
|
/// GetMetadata returns upfront information about server capabilities and
|
||||||
/// supported resource types without requiring the server to instantiate all
|
/// supported resource types without requiring the server to instantiate all
|
||||||
/// schema information, which may be memory intensive. This RPC is optional,
|
/// schema information, which may be memory intensive. This RPC is optional,
|
||||||
/// where clients may receive an unimplemented RPC error. Clients should
|
/// where clients may receive an unimplemented RPC error. Clients should
|
||||||
/// ignore the error and call the GetProviderSchema RPC as a fallback.
|
/// 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(
|
async fn get_metadata(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::get_metadata::Request>,
|
request: Request<tfplugin6::get_metadata::Request>,
|
||||||
|
|
@ -58,11 +54,15 @@ impl Provider for MyProvider {
|
||||||
}
|
}
|
||||||
/// GetSchema returns schema information for the provider, data resources,
|
/// GetSchema returns schema information for the provider, data resources,
|
||||||
/// and managed resources.
|
/// and managed resources.
|
||||||
|
/// Returns provider schema, provider metaschema, all resource schemas and all data source schemas.
|
||||||
async fn get_provider_schema(
|
async fn get_provider_schema(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::get_provider_schema::Request>,
|
request: Request<tfplugin6::get_provider_schema::Request>,
|
||||||
) -> Result<Response<tfplugin6::get_provider_schema::Response>, Status> {
|
) -> Result<Response<tfplugin6::get_provider_schema::Response>, Status> {
|
||||||
info!("Received get_provider_schema");
|
info!("Received get_provider_schema");
|
||||||
|
|
||||||
|
let schemas = self.get_schemas();
|
||||||
|
|
||||||
let reply = tfplugin6::get_provider_schema::Response {
|
let reply = tfplugin6::get_provider_schema::Response {
|
||||||
provider: Some(empty_schema()),
|
provider: Some(empty_schema()),
|
||||||
provider_meta: Some(empty_schema()),
|
provider_meta: Some(empty_schema()),
|
||||||
|
|
@ -71,38 +71,16 @@ impl Provider for MyProvider {
|
||||||
get_provider_schema_optional: true,
|
get_provider_schema_optional: true,
|
||||||
move_resource_state: false,
|
move_resource_state: false,
|
||||||
}),
|
}),
|
||||||
data_source_schemas: HashMap::from([(
|
data_source_schemas: schemas.data_sources,
|
||||||
"terustform_kitty".to_owned(),
|
resource_schemas: schemas.resources,
|
||||||
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())]),
|
|
||||||
functions: HashMap::default(),
|
functions: HashMap::default(),
|
||||||
diagnostics: vec![],
|
diagnostics: schemas.diagnostics,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Response::new(reply))
|
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(
|
async fn validate_provider_config(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::validate_provider_config::Request>,
|
request: Request<tfplugin6::validate_provider_config::Request>,
|
||||||
|
|
@ -115,6 +93,8 @@ impl Provider for MyProvider {
|
||||||
|
|
||||||
Ok(Response::new(reply))
|
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(
|
async fn validate_resource_config(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::validate_resource_config::Request>,
|
request: Request<tfplugin6::validate_resource_config::Request>,
|
||||||
|
|
@ -127,6 +107,8 @@ impl Provider for MyProvider {
|
||||||
|
|
||||||
Ok(Response::new(reply))
|
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(
|
async fn validate_data_resource_config(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::validate_data_resource_config::Request>,
|
request: Request<tfplugin6::validate_data_resource_config::Request>,
|
||||||
|
|
@ -139,14 +121,24 @@ impl Provider for MyProvider {
|
||||||
|
|
||||||
Ok(Response::new(reply))
|
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(
|
async fn upgrade_resource_state(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::upgrade_resource_state::Request>,
|
request: Request<tfplugin6::upgrade_resource_state::Request>,
|
||||||
) -> Result<Response<tfplugin6::upgrade_resource_state::Response>, Status> {
|
) -> Result<Response<tfplugin6::upgrade_resource_state::Response>, Status> {
|
||||||
tracing::error!("upgrade_resource_state");
|
tracing::info!("upgrade_resource_state");
|
||||||
todo!("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
|
/// ////// One-time initialization, called before other functions below
|
||||||
|
/// Passes the practitioner supplied provider configuration to the provider.
|
||||||
async fn configure_provider(
|
async fn configure_provider(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::configure_provider::Request>,
|
request: Request<tfplugin6::configure_provider::Request>,
|
||||||
|
|
@ -158,13 +150,24 @@ impl Provider for MyProvider {
|
||||||
Ok(Response::new(reply))
|
Ok(Response::new(reply))
|
||||||
}
|
}
|
||||||
/// ////// Managed Resource Lifecycle
|
/// ////// Managed Resource Lifecycle
|
||||||
|
/// Called when refreshing a resource's state.
|
||||||
async fn read_resource(
|
async fn read_resource(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::read_resource::Request>,
|
request: Request<tfplugin6::read_resource::Request>,
|
||||||
) -> Result<Response<tfplugin6::read_resource::Response>, Status> {
|
) -> Result<Response<tfplugin6::read_resource::Response>, Status> {
|
||||||
tracing::error!("read_resource");
|
tracing::info!("read_resource");
|
||||||
todo!("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(
|
async fn plan_resource_change(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::plan_resource_change::Request>,
|
request: Request<tfplugin6::plan_resource_change::Request>,
|
||||||
|
|
@ -182,6 +185,9 @@ impl Provider for MyProvider {
|
||||||
|
|
||||||
Ok(Response::new(reply))
|
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(
|
async fn apply_resource_change(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::apply_resource_change::Request>,
|
request: Request<tfplugin6::apply_resource_change::Request>,
|
||||||
|
|
@ -197,6 +203,8 @@ impl Provider for MyProvider {
|
||||||
|
|
||||||
Ok(Response::new(reply))
|
Ok(Response::new(reply))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called when importing a resource into state so that the resource becomes managed.
|
||||||
async fn import_resource_state(
|
async fn import_resource_state(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::import_resource_state::Request>,
|
request: Request<tfplugin6::import_resource_state::Request>,
|
||||||
|
|
@ -213,26 +221,42 @@ impl Provider for MyProvider {
|
||||||
|
|
||||||
todo!("move_resource_state")
|
todo!("move_resource_state")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called when refreshing a data source's state.
|
||||||
async fn read_data_source(
|
async fn read_data_source(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tfplugin6::read_data_source::Request>,
|
request: Request<tfplugin6::read_data_source::Request>,
|
||||||
) -> Result<Response<tfplugin6::read_data_source::Response>, Status> {
|
) -> Result<Response<tfplugin6::read_data_source::Response>, Status> {
|
||||||
tracing::info!("read_data_source");
|
tracing::info!("read_data_source");
|
||||||
|
|
||||||
let reply = tfplugin6::read_data_source::Response {
|
let ds = self
|
||||||
state: Some(tfplugin6::DynamicValue {
|
.state
|
||||||
msgpack: crate::values::Value::Object(BTreeMap::from([(
|
.as_ref()
|
||||||
"kitten".to_owned(),
|
.unwrap()
|
||||||
Box::new(crate::values::Value::String("meow".to_owned())),
|
.data_sources
|
||||||
)]))
|
.get(&request.get_ref().type_name)
|
||||||
.msg_pack(),
|
.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![],
|
json: vec![],
|
||||||
}),
|
}),
|
||||||
deferred: None,
|
vec![],
|
||||||
diagnostics: 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))
|
Ok(Response::new(reply))
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +289,7 @@ impl Provider for MyProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MyController {
|
pub struct Controller {
|
||||||
pub shutdown: CancellationToken,
|
pub shutdown: CancellationToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,7 +300,7 @@ async fn shutdown(token: &CancellationToken) -> ! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tonic::async_trait]
|
#[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>> {
|
async fn shutdown(&self, request: Request<plugin::Empty>) -> Result<Response<plugin::Empty>> {
|
||||||
shutdown(&self.shutdown).await
|
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
|
// this is very dumb and wrong
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
String(String),
|
String(String),
|
||||||
Object(BTreeMap<String, Box<Value>>)
|
Object(BTreeMap<String, Value>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
|
|
||||||
13
test/main.tf
13
test/main.tf
|
|
@ -10,8 +10,17 @@ provider "terustform" {}
|
||||||
|
|
||||||
//resource "terustform_hello" "test1" {}
|
//resource "terustform_hello" "test1" {}
|
||||||
|
|
||||||
data "terustform_kitty" "kitty" {}
|
data "terustform_kitty" "kitty" {
|
||||||
|
name = "mykitten"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "terustform_kitty" "hellyes" {
|
||||||
|
name = "a cute kitty"
|
||||||
|
}
|
||||||
|
|
||||||
output "meow" {
|
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