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"
        );
    }
}