hop_cli/state/
mod.rs

1pub mod http;
2use anyhow::{ensure, Context as AnyhyowContext, Result};
3use hop::{Hop, HopOptions};
4
5use self::http::HttpClient;
6use crate::commands::auth::login::util::{token_options, TokenType};
7use crate::commands::ignite::groups::utils::fetch_grouped_deployments;
8use crate::commands::ignite::types::Deployment;
9use crate::commands::ignite::utils::{get_all_deployments, get_deployment};
10use crate::config::EXEC_NAME;
11use crate::store::auth::Auth;
12use crate::store::context::Context;
13use crate::store::Store;
14
15#[derive(Debug)]
16pub struct State {
17    pub is_ci: bool,
18    pub auth: Auth,
19    pub ctx: Context,
20    pub http: HttpClient,
21    pub debug: bool,
22    pub hop: Hop,
23    token: Option<String>,
24    token_type: Option<TokenType>,
25}
26
27#[derive(Debug, Default)]
28pub struct StateOptions {
29    pub override_project: Option<String>,
30    pub override_token: Option<String>,
31    pub debug: bool,
32}
33
34impl State {
35    pub async fn new(options: StateOptions) -> Result<Self> {
36        // do some logic to get current signed in user
37        let auth = Auth::new().await?;
38        let mut ctx = Context::new().await?;
39
40        // override the project id if provided
41        ctx.project_override = options
42            .override_project
43            .or_else(|| ctx.default_project.clone());
44
45        // use the override token if provided
46        let init_token = if let Some(override_token) = options.override_token {
47            Some(override_token)
48        // otherwise use the token from the store
49        } else if let Some(ref user) = ctx.default_user {
50            auth.authorized.get(user).map(|x| x.to_string())
51        // if all fail then no token
52        } else {
53            None
54        };
55
56        let (token, token_type) = Self::handle_token(init_token)?;
57
58        let api_url = std::env::var("API_URL")
59            .ok()
60            .or_else(|| ctx.override_api_url.clone());
61
62        // preffer the override token over the auth token
63        let http = HttpClient::new(token.clone(), api_url.clone());
64
65        let hop = Hop::new_with_options(HopOptions {
66            token: token.clone(),
67            api_url,
68        })?;
69
70        Ok(State {
71            is_ci: Self::check_if_ci(),
72            token_type,
73            token,
74            http,
75            hop,
76            auth,
77            ctx,
78            debug: options.debug,
79        })
80    }
81
82    fn handle_token(token: Option<String>) -> Result<(Option<String>, Option<TokenType>)> {
83        let token_type = token
84            .as_ref()
85            .map(|token| TokenType::from_token(token).context("Invalid token type"))
86            .transpose()?;
87
88        Ok((token, token_type))
89    }
90
91    /// Checks if the current environment is a CI environment.
92    fn check_if_ci() -> bool {
93        std::env::vars().any(|(key, _)| {
94            matches!(
95                key.as_str(),
96                "BUILD_NUMBER"
97                    | "CONTINUOUS_INTEGRATION"
98                    | "GITLAB_CI"
99                    | "CIRCLECI"
100                    | "APPVEYOR"
101                    | "RUN_ID"
102                    | "CI"
103            )
104        })
105    }
106
107    /// Login to the API
108    pub async fn login(&mut self, token: Option<String>) -> Result<()> {
109        ensure!(
110            token.is_some() || self.token.is_some(),
111            "You are not logged in. Please run `{} auth login` first.",
112            EXEC_NAME
113        );
114
115        if let Some(token) = token {
116            let (token, token_type) = Self::handle_token(Some(token))?;
117
118            self.token = token.clone();
119            self.token_type = token_type;
120            self.http = HttpClient::new(token, self.ctx.override_api_url.clone());
121        }
122
123        let response = token_options(self.http.clone(), self.token_type.clone()).await?;
124
125        if !response.email_verified {
126            log::warn!("You need to verify your email address before you can use Hop.")
127        }
128
129        // get current user to global
130        self.ctx.current = Some(response);
131
132        // if the token is a ptk override the project
133        if let Some(TokenType::Ptk) = self.token_type {
134            self.ctx.project_override = self.ctx.current.as_ref().map(|cur| cur.id.clone())
135        }
136
137        Ok(())
138    }
139
140    pub fn token(&self) -> Option<String> {
141        self.token.clone()
142    }
143
144    pub async fn get_deployment_by_name_or_id(&self, name_or_id: &str) -> Result<Deployment> {
145        // deployments cannot contain underscores so we can use this to determine if
146        // it's an id
147        if name_or_id.starts_with("deployment_") {
148            return get_deployment(&self.http, name_or_id).await;
149        }
150
151        let deployments =
152            get_all_deployments(&self.http, &self.ctx.current_project_error()?.id).await?;
153
154        let deployment = deployments
155            .into_iter()
156            .find(|d| d.name == name_or_id)
157            .context("Deployment not found")?;
158
159        Ok(deployment)
160    }
161
162    pub async fn get_deployment_by_opt_name_or_id(
163        &self,
164        name_or_id: Option<&str>,
165    ) -> Result<Deployment> {
166        if let Some(name_or_id) = name_or_id {
167            self.get_deployment_by_name_or_id(name_or_id).await
168        } else {
169            let (deployments_fmt, deployments, validator) =
170                fetch_grouped_deployments(self, false, true).await?;
171
172            let idx = loop {
173                let idx = dialoguer::Select::new()
174                    .with_prompt("Select a deployment")
175                    .items(&deployments_fmt)
176                    .default(0)
177                    .interact()?;
178
179                if let Ok(idx) = validator(idx) {
180                    break idx;
181                }
182
183                console::Term::stderr().clear_last_lines(1)?
184            };
185
186            Ok(deployments[idx].clone())
187        }
188    }
189}