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
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//! OpenStackCLI - a.k.a. `osc` rewritten in Rust.
//!
//! This is a new OpenStackCLI written in Rust.
//!
//! Facts:
//!  - not specifying `-o` will cause a Table output with a CLI
//!  normalized and known attribute names only. Requesting
//!  unsupported fields present in the API response is not going
//!  to be supported (due to the name convention collision)
//!
//! - `-o wide` is still considered a human response and support
//! normalized/supported names only
//!
//! - `--plain` may be implemented to output a text form table
//! without borders and separators
//!
//! - `-o json` is treated as machine response and returns
//! server side names and does not support requesting certain
//! fields (they are not known in advance). This decision may be
//! re-evaluated
//!
//! More description to come
#![deny(missing_docs)]
// #![allow(unused_variables, dead_code, unused_imports)]
use std::io::{self, IsTerminal};

use clap::Parser;

use tracing::Level;

use openstack_sdk::AsyncOpenStack;

mod api;
mod auth;
mod block_storage;
mod catalog;
mod common;
mod compute;
mod identity;
mod image;
mod network;
mod object_store;

pub mod cli;
pub mod error;
pub mod output;

use crate::error::OpenStackCliError;

pub use cli::Cli;
use cli::TopLevelCommands;

pub(crate) use output::OutputConfig;
pub(crate) use output::StructTable;

/// Entry point for the CLI wrapper
pub async fn entry_point() -> Result<(), OpenStackCliError> {
    let cli = Cli::parse();

    tracing_subscriber::fmt()
        .with_writer(io::stderr)
        .with_max_level(match cli.global_opts.verbose {
            0 => Level::ERROR,
            1 => Level::INFO,
            2 => Level::DEBUG,
            _ => Level::TRACE,
        })
        .init();

    let cfg = openstack_sdk::config::ConfigFile::new().unwrap();
    let profile = cfg
        .get_cloud_config(
            cli.global_opts
                .os_cloud
                .clone()
                .expect("--os-cloud or OS_CLOUD env must be given"),
        )?
        .ok_or(OpenStackCliError::ConnectionNotFound(
            cli.global_opts.os_cloud.clone().unwrap(),
        ))?;
    let mut renew_auth: bool = false;

    // Login command need to be analyzed before authorization
    if let TopLevelCommands::Auth(args) = &cli.command {
        if let auth::AuthCommands::Login(login_args) = &args.command {
            if login_args.renew {
                renew_auth = true;
            }
        }
    }

    let mut session;
    if std::io::stdin().is_terminal() {
        session = AsyncOpenStack::new_interactive(&profile, renew_auth).await?;
    } else {
        session = AsyncOpenStack::new(&profile).await?;
    }

    // Invoke the command
    cli.take_action(&mut session).await
}