1use std::collections::BTreeMap;
25
26use magma_cty::{CtyType, DynamicValue};
27use magma_protocol::{PluginProtocol, tfplugin5, tfplugin6};
28
29use crate::H2Channel;
30use crate::schema;
31
32type Client5 = tfplugin5::provider_client::ProviderClient<H2Channel>;
33type Client6 = tfplugin6::provider_client::ProviderClient<H2Channel>;
34
35enum Client {
36 V5(Client5),
37 V6(Client6),
38}
39
40pub(crate) fn client_caps_v6() -> Option<tfplugin6::ClientCapabilities> {
53 Some(tfplugin6::ClientCapabilities {
54 deferral_allowed: false,
55 write_only_attributes_allowed: false,
56 })
57}
58
59fn client_caps_v5() -> Option<tfplugin5::ClientCapabilities> {
60 Some(tfplugin5::ClientCapabilities {
61 deferral_allowed: false,
62 write_only_attributes_allowed: false,
63 })
64}
65
66pub struct ProviderConn {
68 client: Client,
69}
70
71pub use magma_provider_api::{
85 Diag, PlannedChange, Provider, ProviderError, ProviderSchema, Severity, is_retryable,
86};
87
88impl ProviderConn {
89 pub fn new(channel: H2Channel, protocol: PluginProtocol) -> Self {
92 let client = match protocol {
93 PluginProtocol::V5 => {
94 Client::V5(Client5::new(channel).max_decoding_message_size(256 * 1024 * 1024))
95 }
96 PluginProtocol::V6 => {
97 Client::V6(Client6::new(channel).max_decoding_message_size(256 * 1024 * 1024))
98 }
99 };
100 Self { client }
101 }
102
103 pub async fn get_schema(&mut self) -> Result<ProviderSchema, ProviderError> {
106 match &mut self.client {
107 Client::V6(c) => {
108 let resp = c
109 .get_provider_schema(tfplugin6::get_provider_schema::Request::default())
110 .await
111 .map_err(transport)?
112 .into_inner();
113 check_diags(resp.diagnostics.iter().map(diag6))?;
114 let provider_config = match resp.provider.and_then(|s| s.block) {
115 Some(b) => schema::block_implied_type(&b)?,
116 None => CtyType::Object(BTreeMap::new()),
117 };
118 let mut resources = BTreeMap::new();
119 let mut resource_versions = BTreeMap::new();
120 for (name, sch) in resp.resource_schemas {
121 resource_versions.insert(name.clone(), sch.version);
126 if let Some(b) = sch.block {
127 resources.insert(name, schema::block_implied_type(&b)?);
128 }
129 }
130 let mut data_sources = BTreeMap::new();
131 for (name, sch) in resp.data_source_schemas {
132 if let Some(b) = sch.block {
133 data_sources.insert(name, schema::block_implied_type(&b)?);
134 }
135 }
136 Ok(ProviderSchema {
137 provider_config,
138 resources,
139 data_sources,
140 resource_versions,
141 })
142 }
143 Client::V5(c) => {
144 let resp = c
145 .get_schema(tfplugin5::get_provider_schema::Request::default())
146 .await
147 .map_err(transport)?
148 .into_inner();
149 check_diags(resp.diagnostics.iter().map(diag5))?;
150 let provider_config = match resp.provider.and_then(|s| s.block) {
151 Some(b) => schema::block5_implied_type(&b)?,
152 None => CtyType::Object(BTreeMap::new()),
153 };
154 let mut resources = BTreeMap::new();
155 let mut resource_versions = BTreeMap::new();
156 for (name, sch) in resp.resource_schemas {
157 resource_versions.insert(name.clone(), sch.version);
158 if let Some(b) = sch.block {
159 resources.insert(name, schema::block5_implied_type(&b)?);
160 }
161 }
162 let mut data_sources = BTreeMap::new();
163 for (name, sch) in resp.data_source_schemas {
164 if let Some(b) = sch.block {
165 data_sources.insert(name, schema::block5_implied_type(&b)?);
166 }
167 }
168 Ok(ProviderSchema {
169 provider_config,
170 resources,
171 data_sources,
172 resource_versions,
173 })
174 }
175 }
176 }
177
178 pub async fn configure(
180 &mut self,
181 config: &DynamicValue,
182 terraform_version: &str,
183 ) -> Result<(), ProviderError> {
184 match &mut self.client {
185 Client::V6(c) => {
186 let resp = c
187 .configure_provider(tfplugin6::configure_provider::Request {
188 terraform_version: terraform_version.to_string(),
189 config: Some(to_pb6(config)),
190 client_capabilities: client_caps_v6(),
191 ..Default::default()
192 })
193 .await
194 .map_err(transport)?
195 .into_inner();
196 check_diags(resp.diagnostics.iter().map(diag6))
197 }
198 Client::V5(c) => {
199 let resp = c
200 .configure(tfplugin5::configure::Request {
201 terraform_version: terraform_version.to_string(),
202 config: Some(to_pb5(config)),
203 client_capabilities: client_caps_v5(),
204 ..Default::default()
205 })
206 .await
207 .map_err(transport)?
208 .into_inner();
209 check_diags(resp.diagnostics.iter().map(diag5))
210 }
211 }
212 }
213
214 pub async fn plan_resource_change(
221 &mut self,
222 type_name: &str,
223 prior_state: &DynamicValue,
224 proposed_new_state: &DynamicValue,
225 config: &DynamicValue,
226 ) -> Result<PlannedChange, ProviderError> {
227 match &mut self.client {
228 Client::V6(c) => {
229 let resp = c
230 .plan_resource_change(tfplugin6::plan_resource_change::Request {
231 type_name: type_name.to_string(),
232 prior_state: Some(to_pb6(prior_state)),
233 proposed_new_state: Some(to_pb6(proposed_new_state)),
234 config: Some(to_pb6(config)),
235 client_capabilities: client_caps_v6(),
236 ..Default::default()
237 })
238 .await
239 .map_err(transport)?
240 .into_inner();
241 check_diags(resp.diagnostics.iter().map(diag6))?;
242 let requires_replace = resp
243 .requires_replace
244 .iter()
245 .map(attribute_path_to_string_v6)
246 .collect();
247 let state = resp
248 .planned_state
249 .map(from_pb6)
250 .ok_or(ProviderError::NoNewState)?;
251 Ok(PlannedChange {
252 state,
253 requires_replace,
254 })
255 }
256 Client::V5(c) => {
257 let resp = c
258 .plan_resource_change(tfplugin5::plan_resource_change::Request {
259 type_name: type_name.to_string(),
260 prior_state: Some(to_pb5(prior_state)),
261 proposed_new_state: Some(to_pb5(proposed_new_state)),
262 config: Some(to_pb5(config)),
263 client_capabilities: client_caps_v5(),
264 ..Default::default()
265 })
266 .await
267 .map_err(transport)?
268 .into_inner();
269 check_diags(resp.diagnostics.iter().map(diag5))?;
270 let requires_replace = resp
271 .requires_replace
272 .iter()
273 .map(attribute_path_to_string_v5)
274 .collect();
275 let state = resp
276 .planned_state
277 .map(from_pb5)
278 .ok_or(ProviderError::NoNewState)?;
279 Ok(PlannedChange {
280 state,
281 requires_replace,
282 })
283 }
284 }
285 }
286
287 pub async fn apply_resource_change(
289 &mut self,
290 type_name: &str,
291 prior_state: &DynamicValue,
292 planned_state: &DynamicValue,
293 config: &DynamicValue,
294 ) -> Result<DynamicValue, ProviderError> {
295 match &mut self.client {
296 Client::V6(c) => {
297 let resp = c
298 .apply_resource_change(tfplugin6::apply_resource_change::Request {
299 type_name: type_name.to_string(),
300 prior_state: Some(to_pb6(prior_state)),
301 planned_state: Some(to_pb6(planned_state)),
302 config: Some(to_pb6(config)),
303 ..Default::default()
304 })
305 .await
306 .map_err(transport)?
307 .into_inner();
308 apply_outcome(
313 error_diags(resp.diagnostics.iter().map(diag6)),
314 resp.new_state.map(from_pb6),
315 )
316 }
317 Client::V5(c) => {
318 let resp = c
319 .apply_resource_change(tfplugin5::apply_resource_change::Request {
320 type_name: type_name.to_string(),
321 prior_state: Some(to_pb5(prior_state)),
322 planned_state: Some(to_pb5(planned_state)),
323 config: Some(to_pb5(config)),
324 ..Default::default()
325 })
326 .await
327 .map_err(transport)?
328 .into_inner();
329 apply_outcome(
334 error_diags(resp.diagnostics.iter().map(diag5)),
335 resp.new_state.map(from_pb5),
336 )
337 }
338 }
339 }
340
341 pub async fn read_resource(
347 &mut self,
348 type_name: &str,
349 current_state: &DynamicValue,
350 ) -> Result<Option<DynamicValue>, ProviderError> {
351 match &mut self.client {
352 Client::V6(c) => {
353 let resp = c
354 .read_resource(tfplugin6::read_resource::Request {
355 type_name: type_name.to_string(),
356 current_state: Some(to_pb6(current_state)),
357 client_capabilities: client_caps_v6(),
358 ..Default::default()
359 })
360 .await
361 .map_err(transport)?
362 .into_inner();
363 check_diags(resp.diagnostics.iter().map(diag6))?;
364 Ok(resp.new_state.map(from_pb6).filter(|d| !d.is_null()))
365 }
366 Client::V5(c) => {
367 let resp = c
368 .read_resource(tfplugin5::read_resource::Request {
369 type_name: type_name.to_string(),
370 current_state: Some(to_pb5(current_state)),
371 client_capabilities: client_caps_v5(),
372 ..Default::default()
373 })
374 .await
375 .map_err(transport)?
376 .into_inner();
377 check_diags(resp.diagnostics.iter().map(diag5))?;
378 Ok(resp.new_state.map(from_pb5).filter(|d| !d.is_null()))
379 }
380 }
381 }
382
383 pub async fn read_data_source(
389 &mut self,
390 type_name: &str,
391 config: &DynamicValue,
392 ) -> Result<Option<DynamicValue>, ProviderError> {
393 match &mut self.client {
394 Client::V6(c) => {
395 let resp = c
396 .read_data_source(tfplugin6::read_data_source::Request {
397 type_name: type_name.to_string(),
398 config: Some(to_pb6(config)),
399 client_capabilities: client_caps_v6(),
400 ..Default::default()
401 })
402 .await
403 .map_err(transport)?
404 .into_inner();
405 check_diags(resp.diagnostics.iter().map(diag6))?;
406 Ok(resp.state.map(from_pb6).filter(|d| !d.is_null()))
407 }
408 Client::V5(c) => {
409 let resp = c
410 .read_data_source(tfplugin5::read_data_source::Request {
411 type_name: type_name.to_string(),
412 config: Some(to_pb5(config)),
413 client_capabilities: client_caps_v5(),
414 ..Default::default()
415 })
416 .await
417 .map_err(transport)?
418 .into_inner();
419 check_diags(resp.diagnostics.iter().map(diag5))?;
420 Ok(resp.state.map(from_pb5).filter(|d| !d.is_null()))
421 }
422 }
423 }
424
425 pub async fn import_resource_state(
433 &mut self,
434 type_name: &str,
435 id: &str,
436 ) -> Result<Option<DynamicValue>, ProviderError> {
437 match &mut self.client {
438 Client::V6(c) => {
439 let resp = c
440 .import_resource_state(tfplugin6::import_resource_state::Request {
441 type_name: type_name.to_string(),
442 id: id.to_string(),
443 client_capabilities: client_caps_v6(),
444 ..Default::default()
445 })
446 .await
447 .map_err(transport)?
448 .into_inner();
449 check_diags(resp.diagnostics.iter().map(diag6))?;
450 Ok(resp
451 .imported_resources
452 .into_iter()
453 .next()
454 .and_then(|ir| ir.state)
455 .map(from_pb6)
456 .filter(|d| !d.is_null()))
457 }
458 Client::V5(c) => {
459 let resp = c
460 .import_resource_state(tfplugin5::import_resource_state::Request {
461 type_name: type_name.to_string(),
462 id: id.to_string(),
463 client_capabilities: client_caps_v5(),
464 ..Default::default()
465 })
466 .await
467 .map_err(transport)?
468 .into_inner();
469 check_diags(resp.diagnostics.iter().map(diag5))?;
470 Ok(resp
471 .imported_resources
472 .into_iter()
473 .next()
474 .and_then(|ir| ir.state)
475 .map(from_pb5)
476 .filter(|d| !d.is_null()))
477 }
478 }
479 }
480
481 pub async fn upgrade_resource_state(
496 &mut self,
497 type_name: &str,
498 stored_version: i64,
499 raw_json: &[u8],
500 ) -> Result<DynamicValue, ProviderError> {
501 match &mut self.client {
502 Client::V6(c) => {
503 let resp = c
504 .upgrade_resource_state(tfplugin6::upgrade_resource_state::Request {
505 type_name: type_name.to_string(),
506 version: stored_version,
507 raw_state: Some(tfplugin6::RawState {
508 json: raw_json.to_vec(),
509 flatmap: Default::default(),
510 }),
511 })
512 .await
513 .map_err(transport)?
514 .into_inner();
515 check_diags(resp.diagnostics.iter().map(diag6))?;
516 resp.upgraded_state
517 .map(from_pb6)
518 .ok_or(ProviderError::NoNewState)
519 }
520 Client::V5(c) => {
521 let resp = c
522 .upgrade_resource_state(tfplugin5::upgrade_resource_state::Request {
523 type_name: type_name.to_string(),
524 version: stored_version,
525 raw_state: Some(tfplugin5::RawState {
526 json: raw_json.to_vec(),
527 flatmap: Default::default(),
528 }),
529 })
530 .await
531 .map_err(transport)?
532 .into_inner();
533 check_diags(resp.diagnostics.iter().map(diag5))?;
534 resp.upgraded_state
535 .map(from_pb5)
536 .ok_or(ProviderError::NoNewState)
537 }
538 }
539 }
540}
541
542fn transport(s: tonic::Status) -> ProviderError {
543 ProviderError::Transport(s.to_string())
544}
545
546fn to_pb6(dv: &DynamicValue) -> tfplugin6::DynamicValue {
547 tfplugin6::DynamicValue {
548 msgpack: dv.msgpack.clone(),
549 json: Vec::new(),
550 }
551}
552fn from_pb6(dv: tfplugin6::DynamicValue) -> DynamicValue {
553 DynamicValue {
554 msgpack: dv.msgpack,
555 }
556}
557fn to_pb5(dv: &DynamicValue) -> tfplugin5::DynamicValue {
558 tfplugin5::DynamicValue {
559 msgpack: dv.msgpack.clone(),
560 json: Vec::new(),
561 }
562}
563fn from_pb5(dv: tfplugin5::DynamicValue) -> DynamicValue {
564 DynamicValue {
565 msgpack: dv.msgpack,
566 }
567}
568
569enum PathStep {
574 Attribute(String),
575 ElementKeyString(String),
576 ElementKeyInt(i64),
577}
578
579fn render_attribute_path(steps: impl Iterator<Item = PathStep>) -> String {
585 let mut out = String::new();
586 for step in steps {
587 match step {
588 PathStep::Attribute(name) => {
589 if !out.is_empty() {
590 out.push('.');
591 }
592 out.push_str(&name);
593 }
594 PathStep::ElementKeyString(key) => {
595 out.push('[');
596 out.push_str(&key);
597 out.push(']');
598 }
599 PathStep::ElementKeyInt(i) => {
600 out.push('[');
601 out.push_str(&i.to_string());
602 out.push(']');
603 }
604 }
605 }
606 out
607}
608
609fn attribute_path_to_string_v6(path: &tfplugin6::AttributePath) -> String {
610 render_attribute_path(path.steps.iter().map(|s| match &s.selector {
611 Some(tfplugin6::attribute_path::step::Selector::AttributeName(n)) => {
612 PathStep::Attribute(n.clone())
613 }
614 Some(tfplugin6::attribute_path::step::Selector::ElementKeyString(k)) => {
615 PathStep::ElementKeyString(k.clone())
616 }
617 Some(tfplugin6::attribute_path::step::Selector::ElementKeyInt(i)) => {
618 PathStep::ElementKeyInt(*i)
619 }
620 None => PathStep::Attribute(String::new()),
624 }))
625}
626
627fn attribute_path_to_string_v5(path: &tfplugin5::AttributePath) -> String {
628 render_attribute_path(path.steps.iter().map(|s| match &s.selector {
629 Some(tfplugin5::attribute_path::step::Selector::AttributeName(n)) => {
630 PathStep::Attribute(n.clone())
631 }
632 Some(tfplugin5::attribute_path::step::Selector::ElementKeyString(k)) => {
633 PathStep::ElementKeyString(k.clone())
634 }
635 Some(tfplugin5::attribute_path::step::Selector::ElementKeyInt(i)) => {
636 PathStep::ElementKeyInt(*i)
637 }
638 None => PathStep::Attribute(String::new()),
639 }))
640}
641
642fn diag6(d: &tfplugin6::Diagnostic) -> (i32, String, String) {
644 (d.severity, d.summary.clone(), d.detail.clone())
645}
646fn diag5(d: &tfplugin5::Diagnostic) -> (i32, String, String) {
648 (d.severity, d.summary.clone(), d.detail.clone())
649}
650
651fn error_diags(diags: impl Iterator<Item = (i32, String, String)>) -> Vec<Diag> {
656 diags
657 .filter(|(sev, _, _)| *sev == 1)
658 .map(|(_, summary, detail)| Diag {
659 severity: Severity::Error,
660 summary,
661 detail,
662 })
663 .collect()
664}
665
666fn apply_outcome(
677 errs: Vec<Diag>,
678 new_state: Option<DynamicValue>,
679) -> Result<DynamicValue, ProviderError> {
680 match (errs.is_empty(), new_state) {
681 (true, Some(dv)) => Ok(dv),
682 (true, None) => Err(ProviderError::NoNewState),
683 (false, Some(dv)) => Err(ProviderError::PartiallyApplied {
684 diags: errs,
685 state: Box::new(dv),
686 }),
687 (false, None) => Err(ProviderError::Diagnostics(errs)),
688 }
689}
690
691fn check_diags(diags: impl Iterator<Item = (i32, String, String)>) -> Result<(), ProviderError> {
692 let errors = error_diags(diags);
693 if errors.is_empty() {
694 Ok(())
695 } else {
696 Err(ProviderError::Diagnostics(errors))
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703
704 fn err_diag(msg: &str) -> Vec<Diag> {
705 vec![Diag {
706 severity: Severity::Error,
707 summary: msg.to_string(),
708 detail: String::new(),
709 }]
710 }
711
712 fn eip_type() -> CtyType {
714 CtyType::Object(BTreeMap::from([("id".to_string(), CtyType::String)]))
715 }
716
717 fn some_state() -> DynamicValue {
718 DynamicValue::from_json(&serde_json::json!({"id": "eipalloc-1"}), &eip_type())
719 .expect("test fixture must encode")
720 }
721
722 #[test]
726 fn error_with_new_state_is_partially_applied_and_keeps_the_state() {
727 let out = apply_outcome(err_diag("tagging failed"), Some(some_state()));
728 match out {
729 Err(ProviderError::PartiallyApplied { diags, state }) => {
730 assert_eq!(diags.len(), 1);
731 assert_eq!(diags[0].summary, "tagging failed");
732 let attrs = state
735 .to_json(&eip_type())
736 .expect("partial state must decode");
737 assert_eq!(attrs["id"], "eipalloc-1");
738 }
739 other => panic!("expected PartiallyApplied, got {other:?}"),
740 }
741 }
742
743 #[test]
744 fn error_without_new_state_stays_plain_diagnostics() {
745 assert!(matches!(
746 apply_outcome(err_diag("boom"), None),
747 Err(ProviderError::Diagnostics(_))
748 ));
749 }
750
751 #[test]
752 fn clean_apply_with_state_is_ok() {
753 assert!(apply_outcome(Vec::new(), Some(some_state())).is_ok());
754 }
755
756 #[test]
757 fn clean_apply_without_state_is_no_new_state() {
758 assert!(matches!(
759 apply_outcome(Vec::new(), None),
760 Err(ProviderError::NoNewState)
761 ));
762 }
763
764 #[test]
769 fn a_partial_apply_is_never_retryable_even_when_the_text_looks_transient() {
770 let e = ProviderError::PartiallyApplied {
771 diags: err_diag("connection reset by peer: timeout"),
773 state: Box::new(some_state()),
774 };
775 assert!(
776 !is_retryable(&e),
777 "retrying a committed resource duplicates it"
778 );
779 }
780
781 #[test]
782 fn empty_diagnostics_is_ok() {
783 assert!(check_diags(std::iter::empty()).is_ok());
784 }
785
786 #[test]
787 fn warning_only_is_ok() {
788 let diags = vec![(2, "heads up".to_string(), String::new())];
789 assert!(check_diags(diags.into_iter()).is_ok());
790 }
791
792 #[test]
793 fn any_error_diagnostic_fails() {
794 let diags = vec![
795 (2, "warn".to_string(), String::new()),
796 (1, "boom".to_string(), "bad".to_string()),
797 ];
798 match check_diags(diags.into_iter()) {
799 Err(ProviderError::Diagnostics(errs)) => {
800 assert_eq!(errs.len(), 1, "only error-severity diags are fatal");
801 assert_eq!(errs[0].summary, "boom");
802 }
803 other => panic!("expected Diagnostics error, got {other:?}"),
804 }
805 }
806
807 #[test]
808 fn dynamic_value_pb_roundtrip_both_protocols() {
809 let dv = DynamicValue {
810 msgpack: vec![0xc0, 0x01, 0x02],
811 };
812 assert_eq!(from_pb6(to_pb6(&dv)), dv);
813 assert_eq!(from_pb5(to_pb5(&dv)), dv);
814 assert!(to_pb6(&dv).json.is_empty());
815 }
816
817 fn v6_attr_step(name: &str) -> tfplugin6::attribute_path::Step {
818 tfplugin6::attribute_path::Step {
819 selector: Some(tfplugin6::attribute_path::step::Selector::AttributeName(
820 name.to_string(),
821 )),
822 }
823 }
824
825 fn v6_index_step(i: i64) -> tfplugin6::attribute_path::Step {
826 tfplugin6::attribute_path::Step {
827 selector: Some(tfplugin6::attribute_path::step::Selector::ElementKeyInt(i)),
828 }
829 }
830
831 fn v6_key_step(k: &str) -> tfplugin6::attribute_path::Step {
832 tfplugin6::attribute_path::Step {
833 selector: Some(tfplugin6::attribute_path::step::Selector::ElementKeyString(
834 k.to_string(),
835 )),
836 }
837 }
838
839 #[test]
840 fn attribute_path_to_string_v6_single_attribute() {
841 let path = tfplugin6::AttributePath {
842 steps: vec![v6_attr_step("instance_types")],
843 };
844 assert_eq!(attribute_path_to_string_v6(&path), "instance_types");
845 }
846
847 #[test]
848 fn attribute_path_to_string_v6_nested_key() {
849 let path = tfplugin6::AttributePath {
850 steps: vec![v6_attr_step("tags"), v6_key_step("Name")],
851 };
852 assert_eq!(attribute_path_to_string_v6(&path), "tags[Name]");
853 }
854
855 #[test]
856 fn attribute_path_to_string_v6_indexed_then_attribute() {
857 let path = tfplugin6::AttributePath {
858 steps: vec![
859 v6_attr_step("rules"),
860 v6_index_step(2),
861 v6_attr_step("port"),
862 ],
863 };
864 assert_eq!(attribute_path_to_string_v6(&path), "rules[2].port");
865 }
866
867 fn v5_attr_step(name: &str) -> tfplugin5::attribute_path::Step {
868 tfplugin5::attribute_path::Step {
869 selector: Some(tfplugin5::attribute_path::step::Selector::AttributeName(
870 name.to_string(),
871 )),
872 }
873 }
874
875 #[test]
876 fn attribute_path_to_string_v5_matches_v6_shape() {
877 let path = tfplugin5::AttributePath {
878 steps: vec![v5_attr_step("ami")],
879 };
880 assert_eq!(attribute_path_to_string_v5(&path), "ami");
881 }
882
883 #[test]
890 fn planned_change_requires_replace_is_empty_iff_no_paths() {
891 let no_replace = PlannedChange {
892 state: DynamicValue {
893 msgpack: vec![0xc0],
894 },
895 requires_replace: vec![],
896 };
897 let must_replace = PlannedChange {
898 state: DynamicValue {
899 msgpack: vec![0xc0],
900 },
901 requires_replace: vec!["instance_types".to_string()],
902 };
903 assert!(no_replace.requires_replace.is_empty());
904 assert!(!must_replace.requires_replace.is_empty());
905 }
906}
907
908#[async_trait::async_trait]
924impl Provider for ProviderConn {
925 async fn get_schema(&mut self) -> Result<ProviderSchema, ProviderError> {
926 ProviderConn::get_schema(self).await
927 }
928
929 async fn configure(
930 &mut self,
931 config: &DynamicValue,
932 terraform_version: &str,
933 ) -> Result<(), ProviderError> {
934 ProviderConn::configure(self, config, terraform_version).await
935 }
936
937 async fn plan_resource_change(
938 &mut self,
939 type_name: &str,
940 prior_state: &DynamicValue,
941 proposed_new_state: &DynamicValue,
942 config: &DynamicValue,
943 ) -> Result<PlannedChange, ProviderError> {
944 ProviderConn::plan_resource_change(self, type_name, prior_state, proposed_new_state, config)
945 .await
946 }
947
948 async fn apply_resource_change(
949 &mut self,
950 type_name: &str,
951 prior_state: &DynamicValue,
952 planned_state: &DynamicValue,
953 config: &DynamicValue,
954 ) -> Result<DynamicValue, ProviderError> {
955 ProviderConn::apply_resource_change(self, type_name, prior_state, planned_state, config)
956 .await
957 }
958
959 async fn read_resource(
960 &mut self,
961 type_name: &str,
962 current_state: &DynamicValue,
963 ) -> Result<Option<DynamicValue>, ProviderError> {
964 ProviderConn::read_resource(self, type_name, current_state).await
965 }
966
967 async fn read_data_source(
968 &mut self,
969 type_name: &str,
970 config: &DynamicValue,
971 ) -> Result<Option<DynamicValue>, ProviderError> {
972 ProviderConn::read_data_source(self, type_name, config).await
973 }
974
975 async fn import_resource_state(
976 &mut self,
977 type_name: &str,
978 id: &str,
979 ) -> Result<Option<DynamicValue>, ProviderError> {
980 ProviderConn::import_resource_state(self, type_name, id).await
981 }
982
983 async fn upgrade_resource_state(
984 &mut self,
985 type_name: &str,
986 stored_version: i64,
987 raw_json: &[u8],
988 ) -> Result<DynamicValue, ProviderError> {
989 ProviderConn::upgrade_resource_state(self, type_name, stored_version, raw_json).await
990 }
991}