Skip to main content

ephemeris_forge_github/plan/
environments.rs

1use std::collections::BTreeSet;
2
3use ephemeris_forge::{Collection, FieldDiff, Limitation, Report};
4
5use crate::api::model;
6use crate::api::request::EnvironmentBody;
7use crate::operation::environments::Operation;
8use crate::{BranchPolicy, Environment, EnvironmentReviewer, Environments};
9
10enum Change {
11    Create(BranchPolicy),
12    Delete { id: u64, name: String },
13}
14
15pub(super) fn plan(
16    config: Option<&Environments>,
17    live: &[model::Environment],
18    report: &mut Report,
19) -> Vec<Operation> {
20    let Some(config) = config else {
21        return Vec::new();
22    };
23    for name in config.environments.keys() {
24        let actual = live
25            .iter()
26            .find(|environment| environment.name == name.as_str());
27        for rule in actual.iter().flat_map(|actual| &actual.unknown_rules) {
28            report
29                .limitations
30                .push(Limitation::EnvironmentRuleUnmanaged {
31                    environment: name.to_string(),
32                    rule: rule.clone(),
33                });
34        }
35    }
36    Collection {
37        noun: "environment",
38        mode: config.mode,
39    }
40    .plan(
41        &config.environments,
42        live,
43        report,
44        |environment, name| environment.name == name,
45        |name, desired, actual, _| {
46            let mut diff = FieldDiff::default();
47            let settings = settings(desired, actual, &mut diff);
48            let policies = branch_policies(desired, actual, &mut diff);
49            let mut operations = Vec::new();
50            if settings {
51                operations.push(Operation::Put {
52                    name: name.to_owned(),
53                    body: body(desired),
54                });
55            }
56            operations.extend(policies.into_iter().map(|policy| match policy {
57                Change::Create(policy) => Operation::CreateBranchPolicy {
58                    environment: name.to_owned(),
59                    policy,
60                },
61                Change::Delete { id, name: policy } => Operation::DeleteBranchPolicy {
62                    environment: name.to_owned(),
63                    id,
64                    name: policy,
65                },
66            }));
67            (diff, operations)
68        },
69        |environment, _| {
70            Some((
71                environment.name.clone(),
72                Operation::Delete {
73                    name: environment.name.clone(),
74                },
75            ))
76        },
77    )
78}
79
80fn settings(
81    desired: &Environment,
82    actual: Option<&model::Environment>,
83    diff: &mut FieldDiff,
84) -> bool {
85    let mut changed = diff.compare(
86        "wait_timer",
87        actual.map(|actual| &actual.wait_timer),
88        &desired.wait_timer,
89    );
90    changed |= diff.compare(
91        "prevent_self_review",
92        actual.map(|actual| &actual.prevent_self_review),
93        &desired.prevent_self_review,
94    );
95    changed |= diff.compare(
96        "reviewers",
97        actual
98            .map(|actual| reviewer_set(&actual.reviewers))
99            .as_ref(),
100        &reviewer_set(&desired.reviewers),
101    );
102    changed |= diff.compare(
103        "deployment_branch_policy",
104        actual.map(|actual| &actual.deployment_branch_policy),
105        &desired.deployment_branch_policy,
106    );
107    changed
108}
109
110fn reviewer_set(reviewers: &[EnvironmentReviewer]) -> BTreeSet<EnvironmentReviewer> {
111    reviewers.iter().copied().collect()
112}
113
114fn body(desired: &Environment) -> EnvironmentBody {
115    EnvironmentBody {
116        wait_timer: desired.wait_timer,
117        prevent_self_review: desired.prevent_self_review,
118        reviewers: reviewer_set(&desired.reviewers).into_iter().collect(),
119        deployment_branch_policy: desired.deployment_branch_policy,
120    }
121}
122
123fn branch_policies(
124    desired: &Environment,
125    actual: Option<&model::Environment>,
126    diff: &mut FieldDiff,
127) -> Vec<Change> {
128    if !desired
129        .deployment_branch_policy
130        .is_some_and(|policy| policy.custom_branch_policies)
131    {
132        return Vec::new();
133    }
134    let wanted: BTreeSet<BranchPolicy> = desired.branch_policies.iter().cloned().collect();
135    let present: &[model::BranchPolicy] = actual.map_or(&[], |actual| &actual.branch_policies);
136    let known: BTreeSet<BranchPolicy> = present.iter().map(live_policy).collect();
137    let mut changes: Vec<Change> = wanted
138        .difference(&known)
139        .cloned()
140        .map(Change::Create)
141        .collect();
142    changes.extend(
143        present
144            .iter()
145            .filter(|policy| !wanted.contains(&live_policy(policy)))
146            .map(|policy| Change::Delete {
147                id: policy.id,
148                name: policy.name.clone(),
149            }),
150    );
151    diff.check(
152        "branch_policies",
153        changes.is_empty(),
154        &known.iter().map(|policy| &policy.name).collect::<Vec<_>>(),
155        &wanted.iter().map(|policy| &policy.name).collect::<Vec<_>>(),
156    );
157    changes
158}
159
160fn live_policy(policy: &model::BranchPolicy) -> BranchPolicy {
161    BranchPolicy {
162        name: policy.name.clone(),
163        kind: policy.kind,
164    }
165}