seaplane_cli/context/
restrict.rs

1use std::collections::HashSet;
2
3use seaplane::api::{
4    restrict::v1::{RestrictedDirectory, RestrictionDetails},
5    shared::v1::{Provider as ProviderModel, Region as RegionModel},
6};
7
8use crate::{
9    cli::cmds::restrict::{
10        common::{Provider, Region},
11        SeaplaneRestrictCommonArgMatches, SeaplaneRestrictListArgMatches,
12        SeaplaneRestrictSetArgMatches,
13    },
14    error::Result,
15};
16
17/// Represents the "Source of Truth" i.e. it combines all the CLI options, ENV vars, and config
18/// values into a single structure that can be used later to build models for the API or local
19/// structs for serializing
20// TODO: we may not want to derive this we implement circular references
21#[derive(Debug, Default, Clone)]
22pub struct RestrictCtx {
23    pub api: Option<String>,
24    /// A base64 encoded directory
25    pub directory: Option<RestrictedDirectory>,
26    pub from_api: Option<String>,
27    /// A base64 encoded key
28    pub from_dir: Option<String>,
29    /// Use actual API model since that is ultimately what we want
30    pub providers_allowed: HashSet<ProviderModel>,
31    /// Use actual API model since that is ultimately what we want
32    pub providers_denied: HashSet<ProviderModel>,
33    /// Use actual API model since that is ultimately what we want
34    pub regions_allowed: HashSet<RegionModel>,
35    /// Use actual API model since that is ultimately what we want
36    pub regions_denied: HashSet<RegionModel>,
37    /// Is the directory already URL safe base64 encoded
38    pub base64: bool,
39    /// Print with decoding
40    pub decode: bool,
41    /// Skip the headers in --format=table
42    pub no_header: bool,
43}
44
45impl RestrictCtx {
46    /// Builds a RestictCtx from ArgMatches
47    pub fn from_restrict_common(matches: &SeaplaneRestrictCommonArgMatches) -> Result<RestrictCtx> {
48        let matches = matches.0;
49        let base64 = matches.get_flag("base64");
50        let api = matches.get_one::<String>("api").unwrap();
51        let dir = matches.get_one::<String>("directory").unwrap();
52
53        Ok(RestrictCtx {
54            api: Some(api.into()),
55            directory: if base64 {
56                // Check that what the user passed really is valid base64
57                let engine = ::base64::engine::fast_portable::FastPortable::from(
58                    &::base64::alphabet::URL_SAFE,
59                    ::base64::engine::fast_portable::NO_PAD,
60                );
61                let _ = base64::decode_engine(dir, &engine)?;
62                Some(RestrictedDirectory::from_encoded(dir))
63            } else {
64                Some(RestrictedDirectory::from_unencoded(dir))
65            },
66            base64: true, // At this point all keys and values should be encoded as base64
67            ..RestrictCtx::default()
68        })
69    }
70
71    /// Builds a RestictCtx from ArgMatches
72    pub fn from_restrict_list(matches: &SeaplaneRestrictListArgMatches) -> Result<RestrictCtx> {
73        let api = matches.0.get_one::<String>("api").map(|a| a.to_owned());
74
75        Ok(RestrictCtx { api, ..RestrictCtx::default() })
76    }
77
78    /// Builds a RestictCtx from ArgMatches
79    pub fn from_restrict_set(matches: &SeaplaneRestrictSetArgMatches) -> Result<RestrictCtx> {
80        let matches = matches.0;
81        let base64 = matches.get_flag("base64");
82        let raw_api = matches.get_one::<String>("api").unwrap();
83        let raw_dir = matches.get_one::<String>("directory").unwrap();
84
85        let providers_allowed: HashSet<ProviderModel> = matches
86            .get_many::<Provider>("provider")
87            .unwrap_or_default()
88            .filter_map(Provider::into_model)
89            .collect();
90        let providers_denied: HashSet<ProviderModel> = matches
91            .get_many::<Provider>("exclude-provider")
92            .unwrap_or_default()
93            .filter_map(Provider::into_model)
94            .collect();
95        let regions_allowed: HashSet<RegionModel> = matches
96            .get_many::<Region>("region")
97            .unwrap_or_default()
98            .filter_map(Region::into_model)
99            .collect();
100        let regions_denied: HashSet<RegionModel> = matches
101            .get_many::<Region>("exclude-region")
102            .unwrap_or_default()
103            .filter_map(Region::into_model)
104            .collect();
105        Ok(RestrictCtx {
106            api: Some(raw_api.into()),
107            directory: if base64 {
108                // Check that what the user passed really is valid base64
109                let engine = ::base64::engine::fast_portable::FastPortable::from(
110                    &::base64::alphabet::URL_SAFE,
111                    ::base64::engine::fast_portable::NO_PAD,
112                );
113                let _ = base64::decode_engine(raw_dir, &engine)?;
114                Some(RestrictedDirectory::from_encoded(raw_dir))
115            } else {
116                Some(RestrictedDirectory::from_unencoded(raw_dir))
117            },
118            base64: true, // At this point all keys and values should be encoded as base64
119            providers_allowed,
120            providers_denied,
121            regions_allowed,
122            regions_denied,
123            ..RestrictCtx::default()
124        })
125    }
126
127    pub fn restriction_details(&self) -> Result<RestrictionDetails> {
128        let mut builder = RestrictionDetails::builder();
129
130        for item in &self.providers_allowed {
131            builder = builder.add_allowed_provider(*item);
132        }
133        for item in &self.providers_denied {
134            builder = builder.add_denied_provider(*item);
135        }
136        for item in &self.regions_allowed {
137            builder = builder.add_allowed_region(*item);
138        }
139        for item in &self.regions_denied {
140            builder = builder.add_denied_region(*item);
141        }
142
143        Ok(builder.build()?)
144    }
145}