File size: 5,709 Bytes
c01fd1c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | // Copyright (C) 2026 ERRANT DIGITAL INSTITUTE OF TECHNOLOGY (SEIT NGO) % SNAPKITTY COLLECTIVE
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// See LICENSE-AGPL and LICENSE-COMMERCIAL in this repository for license terms.
//! A thin facade re-exporting `cloud-forge`'s Phase 1 primitive
//! kernel under one crate, so a caller composing several primitives
//! doesn't need to depend on all eleven kernel crates by hand. This
//! crate owns no new logic of its own -- see each re-exported type's
//! home crate for its actual implementation and tests.
//!
//! Its own test suite is a single integration test showing the
//! primitives actually compose into something resembling a real
//! provisioning flow -- the whole point of Phase 1, per this
//! workspace's non-negotiable rule (see
//! `cloud-forge/docs/CLOUD_ARCHITECTURE.md`).
#![forbid(unsafe_code)]
pub use cloud_account::{Account, AccountRegistry};
pub use cloud_errors::{CloudError, CloudResult};
pub use cloud_events::{Event, EventLog};
pub use cloud_identity::Principal;
pub use cloud_lifecycle::Lifecycle;
pub use cloud_policy::{Decision, Effect, Policy, PrincipalMatcher, Statement};
pub use cloud_quota::QuotaTracker;
pub use cloud_region::{RegionInfo, RegionRegistry};
pub use cloud_resource::Resource;
pub use cloud_tags::Tags;
pub use cloud_types::{AccountId, Arn, AzId, RegionId, ResourceId, ResourceType, Timestamp};
#[cfg(test)]
mod tests {
use super::*;
/// A minimal service-specific payload, standing in for a future
/// `InstanceSpec`/`BucketSpec`/etc.
#[derive(Debug, Clone, PartialEq, Eq)]
struct DemoInstanceSpec {
cpu_count: u32,
}
#[test]
fn provisioning_a_resource_composes_every_phase_1_primitive() {
// 1. Register a region with two availability zones.
let mut regions = RegionRegistry::new();
let region = RegionId::new("us-west-1").unwrap();
regions.register(region.clone(), 2).unwrap();
let az = AzId::new("us-west-1a").unwrap();
assert!(regions.contains_az(&az));
// 2. Register an owning account.
let mut accounts = AccountRegistry::new();
let account_id = AccountId::new("000000000001").unwrap();
accounts
.register(Account::new(account_id.clone(), "demo-account", Timestamp::EPOCH).unwrap())
.unwrap();
// 3. A policy allowing the account's admin role to manage
// compute resources, but explicitly denying termination --
// demonstrating deny-dominates even though a broader Allow
// also matches.
let admin = Principal::Role(ResourceId::new("admin").unwrap());
let mut policy = Policy::new();
policy.add_statement(Statement {
effect: Effect::Allow,
principals: PrincipalMatcher::OneOf(vec![admin.clone()]),
actions: vec!["compute:*".to_string()],
resources: vec!["*".to_string()],
});
policy.add_statement(Statement {
effect: Effect::Deny,
principals: PrincipalMatcher::Any,
actions: vec!["compute:terminate".to_string()],
resources: vec!["*".to_string()],
});
policy
.authorize(&admin, "compute:describe", "i-1")
.expect("admin may describe");
assert!(
policy
.authorize(&admin, "compute:terminate", "i-1")
.is_err(),
"the explicit deny must win even for the admin"
);
// 4. Quota: this account may create at most 2 instances.
let mut quota = QuotaTracker::new();
quota.set_limit("instances", 2);
quota.try_reserve("instances", 1).unwrap();
// 5. Create the resource itself, tag it, and move it through
// its lifecycle -- checking the version invariant holds
// across every step.
let mut instance = Resource::new(
ResourceId::new("i-000001").unwrap(),
ResourceType::new("compute-instance").unwrap(),
Some(region.clone()),
account_id.clone(),
Timestamp::from_millis(1_000),
DemoInstanceSpec { cpu_count: 4 },
);
assert_eq!(instance.version(), 1);
assert_eq!(instance.lifecycle(), Lifecycle::Creating);
instance
.set_tag("env", "demo", Timestamp::from_millis(1_100))
.unwrap();
instance
.transition_lifecycle(Lifecycle::Active, Timestamp::from_millis(1_200))
.unwrap();
assert_eq!(instance.lifecycle(), Lifecycle::Active);
assert_eq!(instance.version(), 3); // 1 (created) + tag + transition
// 6. This system's internal ARN for the resource, and an
// event recording that it was created.
let arn = Arn::new(
"core",
"compute",
Some(region),
Some(account_id),
instance.id().clone(),
)
.unwrap();
assert_eq!(
arn.to_string(),
"jxcl:cloud:core:compute:us-west-1:000000000001:i-000001"
);
let mut events = EventLog::new();
let event_id = events.append(
instance.id().clone(),
"resource.created",
Timestamp::from_millis(1_000),
arn.to_string(),
);
assert_eq!(event_id, 1);
assert_eq!(events.len(), 1);
// 7. A second instance would exceed the quota.
assert!(quota.try_reserve("instances", 2).is_err());
assert_eq!(
quota.usage("instances"),
1,
"the rejected reservation must not have committed"
);
}
}
|