Skip to main content

stackless_integrations/
lib.rs

1//! Integration provider routing and first-class adapters.
2//!
3//! Substrates call into this crate for `ProvisionIntegration` steps.
4//! Stripe-backed provisioning delegates to `stackless-stripe-projects`.
5
6pub mod providers;
7pub mod registry;
8
9use std::path::Path;
10
11use stackless_core::def::StackDef;
12use stackless_core::substrate::{Observation, StepResource};
13use stackless_stripe_projects::project;
14use stackless_stripe_projects::stripe::{CommandRunner, StripeProjects};
15
16pub use stackless_provider_sdk::{
17    self, CatalogResource, IntegrationError, IntegrationObservation, ProviderOps, ResourcePayload,
18    config, error, hostable, observation, resource,
19};
20
21pub use registry::{known_outputs, validate_all};
22
23pub async fn provision<R: CommandRunner>(
24    substrate: &str,
25    stripe: &StripeProjects<R>,
26    def: &StackDef,
27    definition_dir: &Path,
28    instance: &str,
29    name: &str,
30    skip_stripe_instance_context: bool,
31) -> Result<StepResource, IntegrationError> {
32    let spec = def
33        .integrations
34        .get(name)
35        .ok_or_else(|| IntegrationError::ConfigInvalid {
36            location: format!("integrations.{name}"),
37            detail: "integration not in definition".into(),
38        })?;
39    registry::validate_integration(
40        name,
41        spec,
42        Some(substrate),
43        registry::provider_host_keys(&spec.provider),
44    )?;
45    let ops = registry::ops_for(&spec.provider).ok_or_else(|| IntegrationError::ConfigInvalid {
46        location: format!("integrations.{name}"),
47        detail: format!("no adapter for provider {:?}", spec.provider),
48    })?;
49    let stripe = stripe.as_dyn();
50    let resource = ops
51        .provision(
52            &stripe,
53            def,
54            definition_dir,
55            instance,
56            name,
57            substrate,
58            skip_stripe_instance_context,
59        )
60        .await?;
61    ops.apply(&stripe, def, name, substrate, &resource).await?;
62    Ok(resource)
63}
64
65pub async fn observe<R: CommandRunner>(
66    substrate: &str,
67    stripe: &StripeProjects<R>,
68    checkpoint_payload: &str,
69    fallback_resource: &str,
70    resource_kind: &str,
71) -> Result<Observation, IntegrationError> {
72    let _ = substrate;
73    match registry::ops_for_resource_kind(resource_kind) {
74        Some(ops) => ops
75            .observe(&stripe.as_dyn(), checkpoint_payload, fallback_resource)
76            .await
77            .map(IntegrationObservation::into_substrate),
78        None => Ok(Observation::Gone),
79    }
80}
81
82pub async fn destroy<R: CommandRunner>(
83    substrate: &str,
84    stripe: &StripeProjects<R>,
85    checkpoint_payload: &str,
86    fallback_resource: &str,
87    resource_kind: &str,
88) -> Result<(), IntegrationError> {
89    let _ = substrate;
90    match registry::ops_for_resource_kind(resource_kind) {
91        Some(ops) => {
92            ops.destroy(&stripe.as_dyn(), checkpoint_payload, fallback_resource)
93                .await
94        }
95        None => Ok(()),
96    }
97}
98
99pub fn is_integration_resource(kind: &str) -> bool {
100    registry::is_integration_resource(kind)
101}
102
103/// Delete the instance's Stripe Projects environment after all resources
104/// are gone. Failures are ignored — the environment bills nothing.
105pub async fn finalize_stripe_instance<R: CommandRunner>(
106    stripe: &StripeProjects<R>,
107    instance: &str,
108) {
109    let _ = project::delete_environment(stripe, instance).await;
110}