Coverage for harbor_cli/commands/cli/init.py: 25%
116 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-09 12:09 +0100
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-09 12:09 +0100
1from __future__ import annotations
3from pathlib import Path
4from typing import Optional
6import typer
8from ...app import app
9from ...config import create_config
10from ...config import DEFAULT_CONFIG_FILE
11from ...config import HarborCLIConfig
12from ...config import load_config
13from ...config import save_config
14from ...exceptions import ConfigError
15from ...exceptions import OverwriteError
16from ...format import output_format_emoji
17from ...format import output_format_repr
18from ...format import OutputFormat
19from ...logs import logger
20from ...logs import LogLevel
21from ...output.console import console
22from ...output.console import success
23from ...output.formatting import path_link
24from ...output.prompts import bool_prompt
25from ...output.prompts import int_prompt
26from ...output.prompts import path_prompt
27from ...output.prompts import str_prompt
30TITLE_STYLE = "bold" # Style for titles used by each config category
31MAIN_TITLE_STYLE = "bold underline" # Style for the main title
34@app.command("init")
35def init(
36 ctx: typer.Context,
37 path: Optional[Path] = typer.Option(
38 None,
39 help="Path to create config file.",
40 ),
41 overwrite: bool = typer.Option(
42 False,
43 help="Overwrite existing config file.",
44 is_flag=True,
45 ),
46 wizard: bool = typer.Option(
47 True,
48 "--wizard",
49 help="Run the configuration wizard after creating the config file.",
50 is_flag=True,
51 ),
52) -> None:
53 """Initialize Harbor CLI configuration file.
55 Runs the configuration wizard by default unless otherwise specified.
56 """
58 logger.debug("Initializing Harbor CLI...")
59 try:
60 config_path = create_config(path, overwrite=overwrite)
61 except OverwriteError:
62 if not wizard:
63 raise typer.Exit()
64 # TODO: verify that this path is always correct
65 p = path or DEFAULT_CONFIG_FILE
66 console.print(
67 f"WARNING: Config file already exists ({path_link(p)})", style="yellow"
68 )
69 wizard = bool_prompt(
70 "Are you sure you want to run the configuration wizard?",
71 default=False,
72 )
73 config_path = None
74 else:
75 logger.info(f"Created config file at {config_path}")
77 if wizard:
78 run_config_wizard(config_path)
81def run_config_wizard(config_path: Optional[Path] = None) -> None:
82 """Loads the config file, and runs the configuration wizard.
84 Delegates to subroutines for each config category, that modify
85 the loaded config object in-place."""
86 conf_exists = config_path is None
88 config = load_config(config_path)
89 assert config.config_file is not None
91 console.print()
92 console.rule(":sparkles: Harbor CLI Configuration Wizard :mage:")
94 # We only ask the user to configure mandatory sections if the config
95 # file existed prior to running wizard.
96 # Otherwise, we force the user to configure Harbor settings, because
97 # we can't do anything without them.
98 if not conf_exists or bool_prompt("\nConfigure harbor settings?", default=False):
99 init_harbor_settings(config)
100 console.print()
102 # These categories are optional, and as such we always ask the user
103 if bool_prompt("Configure output settings?", default=False):
104 init_output_settings(config)
105 console.print()
107 if bool_prompt("Configure logging settings?", default=False):
108 init_logging_settings(config)
109 console.print()
111 conf_path = config_path or config.config_file
112 if not conf_path:
113 raise ConfigError("Could not determine config file path.")
114 save_config(config, conf_path)
115 console.print("Configuration complete! :tada:")
116 success(f"Saved config to {path_link(conf_path)}")
119def init_harbor_settings(config: HarborCLIConfig) -> None:
120 """Initialize Harbor settings."""
121 console.print("\n:ship: Harbor Configuration", style=TITLE_STYLE)
123 hconf = config.harbor
124 config.harbor.url = str_prompt(
125 "Harbor API URL (e.g. https://harbor.example.com/api/v2.0)",
126 default=hconf.url,
127 show_default=True,
128 )
130 base_msg = "Authentication method [bold magenta](\[u]sername/password, \[b]asic auth, \[f]ile, \[s]kip)[/]"
131 choices = ["u", "b", "f", "s"]
133 auth_method = str_prompt(base_msg, choices=choices, default="s", show_choices=False)
134 if auth_method == "u":
135 hconf.username = str_prompt(
136 "Harbor username",
137 default=hconf.username,
138 empty_ok=False,
139 )
140 hconf.secret = str_prompt(
141 "Harbor secret",
142 default=hconf.secret,
143 password=True,
144 empty_ok=False,
145 ) # type: ignore # pydantic.SecretStr
146 elif auth_method == "b":
147 hconf.basicauth = str_prompt(
148 f"Harbor Base64 Basic Auth token",
149 default=hconf.basicauth,
150 password=True,
151 empty_ok=False,
152 ) # type: ignore # pydantic.SecretStr
153 elif auth_method == "f":
154 hconf.credentials_file = path_prompt(
155 "Harbor credentials file",
156 default=hconf.credentials_file,
157 show_default=True,
158 must_exist=True,
159 exist_ok=True,
160 )
162 # Explain what will happen if no auth method is provided
163 if not hconf.has_auth_method:
164 console.print(
165 ":warning: No authentication info provided. "
166 "You will be prompted for username and password when required.",
167 style="yellow",
168 )
171def init_logging_settings(config: HarborCLIConfig) -> None:
172 """Initialize logging settings."""
173 console.print("\n:mag: Logging Configuration", style=TITLE_STYLE)
175 lconf = config.logging
177 lconf.enabled = bool_prompt(
178 "Enable logging?", default=lconf.enabled, show_default=True
179 )
181 loglevel = str_prompt(
182 "Logging level",
183 choices=[lvl.value.lower() for lvl in LogLevel],
184 default=lconf.level.value.lower(), # use lower to match choices
185 show_default=True,
186 )
187 lconf.level = LogLevel(loglevel.upper())
190def init_output_settings(config: HarborCLIConfig) -> None:
191 """Initialize output settings."""
192 console.print("\n:desktop_computer: Output Configuration", style=TITLE_STYLE)
194 oconf = config.output
196 # Output format configuration has numerous sub-options,
197 # so we delegate to a separate function.
198 _init_output_format(config)
200 oconf.paging = bool_prompt(
201 "Show output in pager? (requires 'less' or other pager to be installed and configured)",
202 default=oconf.paging,
203 show_default=True,
204 )
205 # Custom pager support NYI (see: OutputSettings)
206 if False and oconf.paging:
207 use_custom = bool_prompt(
208 "Use custom pager command?", default=False, show_default=True
209 )
210 if use_custom:
211 oconf.pager = str_prompt(
212 "Pager command",
213 default=oconf.pager,
214 show_default=True,
215 empty_ok=False,
216 )
219def _init_output_format(config: HarborCLIConfig) -> None:
220 oconf = config.output
221 fmt_in = str_prompt(
222 "Default output format",
223 choices=[f.value for f in OutputFormat],
224 default=oconf.format.value,
225 )
226 oconf.format = OutputFormat(fmt_in)
228 def conf_fmt(fmt: OutputFormat) -> None:
229 if fmt == OutputFormat.JSON:
230 _init_output_json_settings(config)
231 elif fmt == OutputFormat.TABLE:
232 _init_output_table_settings(config)
233 else:
234 logger.error(f"Unknown configuration format {fmt.value}")
236 # Configure the chosen format first
237 conf_fmt(oconf.format)
239 # Optionally configure other formats afterwards
240 formats = [f for f in OutputFormat if f != oconf.format]
241 for fmt in formats:
242 if bool_prompt(
243 f"Configure {output_format_repr(fmt)} output settings?", default=False
244 ):
245 conf_fmt(fmt)
248def _init_output_json_settings(config: HarborCLIConfig) -> None:
249 """Initialize JSON output settings."""
250 _print_output_title(OutputFormat.JSON)
252 oconf = config.output.JSON
254 oconf.indent = int_prompt(
255 "Indentation",
256 default=oconf.indent,
257 show_default=True,
258 )
260 oconf.sort_keys = bool_prompt(
261 "Sort keys",
262 default=oconf.sort_keys,
263 show_default=True,
264 )
267def _init_output_table_settings(config: HarborCLIConfig) -> None:
268 """Initialize table output settings."""
269 _print_output_title(OutputFormat.TABLE)
271 oconf = config.output.table
273 oconf.max_depth = int_prompt(
274 "Max number of subtables [bold magenta](0 or omit for unlimited)[/]",
275 default=oconf.max_depth,
276 show_default=True,
277 )
279 oconf.description = bool_prompt(
280 "Show descriptions",
281 default=oconf.description,
282 show_default=True,
283 )
285 oconf.compact = bool_prompt(
286 "Compact tables",
287 default=oconf.compact,
288 show_default=True,
289 )
292def _print_output_title(fmt: OutputFormat) -> None:
293 fmt_repr = output_format_repr(fmt)
294 emoji = output_format_emoji(fmt)
295 console.print(
296 f"\n:desktop_computer: {emoji} Output Configuration ({fmt_repr})",
297 style=TITLE_STYLE,
298 )