ephemeris_forge_github/plan/
environments.rs1use 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}