mantis_api_client 5.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,311 @@
1
+ # -*- coding: utf-8 -*-
2
+ import argparse
3
+ import json
4
+ import sys
5
+ from typing import Any
6
+
7
+ from mantis_scenario_model.lab_config_model import LabConfig
8
+ from pydantic.json import pydantic_encoder
9
+ from ruamel.yaml import YAML
10
+
11
+ from mantis_api_client import scenario_api
12
+ from mantis_api_client.oidc import get_oidc_client
13
+ from mantis_api_client.utils import colored
14
+ from mantis_api_client.utils import wait_lab
15
+
16
+
17
+ #
18
+ # 'scenario_list_handler' handler
19
+ #
20
+ def scenario_list_handler(args: Any) -> None:
21
+ try:
22
+ scenarios = sorted(scenario_api.fetch_scenarios(), key=lambda x: x.name)
23
+ except Exception as e:
24
+ print(colored(f"Error when fetching scenarios: '{e}'", "red"))
25
+ sys.exit(1)
26
+
27
+ if args.json:
28
+ print(json.dumps(scenarios, default=pydantic_encoder))
29
+ return
30
+
31
+ print(f"[+] Available scenarios ({len(scenarios)}):")
32
+ for scenario in scenarios:
33
+ print(f" [+] \033[1m{scenario.name}\033[0m")
34
+ print(f" {scenario.description}")
35
+
36
+
37
+ #
38
+ # 'scenario_info_handler' handler
39
+ #
40
+ def scenario_info_handler(args: Any) -> None:
41
+ # Parameters
42
+ scenario_name = args.scenario_name
43
+
44
+ try:
45
+ scenario = scenario_api.fetch_scenario_by_name(scenario_name)
46
+ except Exception as e:
47
+ print(
48
+ colored(
49
+ f"Error when fetching scenario {scenario_name}: '{e}'",
50
+ "red",
51
+ "on_white",
52
+ )
53
+ )
54
+ sys.exit(1)
55
+
56
+ if args.json:
57
+ print(scenario.json())
58
+ return
59
+
60
+ print("[+] Scenario information:")
61
+ print(f" [+] \033[1mName\033[0m: {scenario.name}")
62
+ print(" [+] \033[1mKeywords\033[0m: ", end="")
63
+ print(", ".join(scenario.keywords))
64
+ print(f" [+] \033[1mDescription\033[0m: {scenario.description}")
65
+ if len(scenario.long_description) > 0:
66
+ print(" [+] \033[1mLong description\033[0m:")
67
+ for ld in scenario.long_description:
68
+ print(f" - {ld}")
69
+ print(f" [+] \033[1mLearning context\033[0m: {scenario.learning_context}")
70
+ print(" [+] \033[1mAvailable scenario profiles\033[0m:")
71
+ for scenario_profile in scenario.scenario_profiles:
72
+ target = f", target: {scenario_profile.compromission.target_name}"
73
+ print(
74
+ f""" [+] {scenario_profile.name} (Topology: {scenario_profile.topology_name}){target}"""
75
+ )
76
+ print(" [+] \033[1mUnit attacks available\033[0m:")
77
+ for attack in scenario.attacks:
78
+ print(f" - {attack}")
79
+
80
+
81
+ def _scenario_create_or_run_lab(args: Any, do_run: bool = True):
82
+ # Parameters
83
+ scenario_name = args.scenario_name
84
+ lab_config_path = args.lab_config_path
85
+ scenario_profile = args.scenario_profile
86
+
87
+ if not args.workspace_id:
88
+ try:
89
+ workspace_id = get_oidc_client().get_default_workspace(raise_exc=True)
90
+ except Exception as e:
91
+ print(colored(f"Error when fetching attacks: '{e}'", "red"))
92
+ sys.exit(1)
93
+ else:
94
+ workspace_id = args.workspace_id
95
+
96
+ # Retrieve associated group id
97
+ if workspace_id is None:
98
+ print(colored("You have to specify a workspace with --workspace", "yellow"))
99
+ sys.exit(1)
100
+
101
+ # Manage lab configuration
102
+ if lab_config_path is None:
103
+ lab_config_dict = {}
104
+ else:
105
+ with open(lab_config_path, "r") as fd:
106
+ yaml_content = fd.read()
107
+ loader = YAML(typ="rt")
108
+ lab_config_dict = loader.load(yaml_content)
109
+ lab_config = LabConfig(**lab_config_dict)
110
+
111
+ # Launch scenario
112
+ try:
113
+ scenario = scenario_api.fetch_scenario_by_name(scenario_name)
114
+ print(f"[+] Going to execute scenario: {scenario.name}")
115
+
116
+ if scenario_profile is None:
117
+ print(
118
+ "Needed argument --scenario_profile in order to choose the unit scenario to run"
119
+ )
120
+ print("Available unit scenarios:")
121
+ for available_scenario_profile in scenario.scenario_profiles:
122
+ print(f" [+] {available_scenario_profile.name}")
123
+ sys.exit(-1)
124
+
125
+ if not any(scenario_profile == c.name for c in scenario.scenario_profiles):
126
+ print(
127
+ colored(
128
+ f"Error '{scenario_profile}' not supported for this scenario.",
129
+ "red",
130
+ "on_white",
131
+ )
132
+ )
133
+ sys.exit(-1)
134
+
135
+ if do_run:
136
+ lab_id = scenario_api.run_lab_scenario(
137
+ scenario,
138
+ scenario_profile,
139
+ lab_config,
140
+ workspace_id,
141
+ args.public,
142
+ )
143
+ else:
144
+ lab_id = scenario_api.create_lab_scenario(
145
+ scenario,
146
+ scenario_profile,
147
+ lab_config,
148
+ workspace_id,
149
+ args.public,
150
+ )
151
+
152
+ print(f"[+] Lab ID: {lab_id}")
153
+
154
+ if do_run:
155
+ wait_lab(lab_id)
156
+
157
+ except Exception as e:
158
+ print(colored(f"Error when running scenario {scenario_name}: '{e}'", "red"))
159
+ sys.exit(1)
160
+ finally:
161
+ if do_run and args.destroy_after_scenario:
162
+ print("[+] Stopping lab...")
163
+ scenario_api.stop_lab(lab_id)
164
+
165
+
166
+ #
167
+ # 'scenario_create_lab_handler' handler
168
+ #
169
+ def scenario_create_lab_handler(args: Any) -> None:
170
+ _scenario_create_or_run_lab(args, do_run=False)
171
+
172
+
173
+ #
174
+ # 'scenario_run_lab_handler' handler
175
+ #
176
+ def scenario_run_lab_handler(args: Any) -> None:
177
+ _scenario_create_or_run_lab(args, do_run=True)
178
+
179
+
180
+ def add_scenario_parser(
181
+ root_parser: argparse.ArgumentParser,
182
+ subparsers: Any,
183
+ ) -> None:
184
+ # --------------------
185
+ # --- Scenario API options (scenario)
186
+ # --------------------
187
+
188
+ parser_scenario = subparsers.add_parser(
189
+ "scenario",
190
+ help="Scenario API related commands (scenario)",
191
+ formatter_class=root_parser.formatter_class,
192
+ )
193
+ subparsers_scenario = parser_scenario.add_subparsers()
194
+
195
+ # 'scenario_list' command
196
+ parser_scenario_list = subparsers_scenario.add_parser(
197
+ "list",
198
+ help="List all available scenarios",
199
+ formatter_class=root_parser.formatter_class,
200
+ )
201
+ parser_scenario_list.set_defaults(func=scenario_list_handler)
202
+ parser_scenario_list.add_argument(
203
+ "--json", help="Return JSON result.", action="store_true"
204
+ )
205
+
206
+ # 'scenario_info' command
207
+ parser_scenario_info = subparsers_scenario.add_parser(
208
+ "info",
209
+ help="Get information about a scenario",
210
+ formatter_class=root_parser.formatter_class,
211
+ )
212
+ parser_scenario_info.set_defaults(func=scenario_info_handler)
213
+ parser_scenario_info.add_argument(
214
+ "scenario_name", type=str, help="The scenario name"
215
+ )
216
+ parser_scenario_info.add_argument(
217
+ "--json", help="Return JSON result.", action="store_true"
218
+ )
219
+
220
+ # 'scenario_create_lab' command
221
+ parser_scenario_create_lab = subparsers_scenario.add_parser(
222
+ "create",
223
+ help="Create a lab with a specific scenario",
224
+ formatter_class=root_parser.formatter_class,
225
+ )
226
+ parser_scenario_create_lab.set_defaults(func=scenario_create_lab_handler)
227
+ parser_scenario_create_lab.add_argument(
228
+ "scenario_name", type=str, help="The scenario name"
229
+ )
230
+ parser_scenario_create_lab.add_argument(
231
+ "--workspace",
232
+ dest="workspace_id",
233
+ help="The group ID that have ownership on lab",
234
+ )
235
+ parser_scenario_create_lab.add_argument(
236
+ "--destroy",
237
+ action="store_true",
238
+ dest="destroy_after_scenario",
239
+ help="Do not keep the lab up after scenario execution (False by default)",
240
+ )
241
+ parser_scenario_create_lab.add_argument(
242
+ "--lab_config",
243
+ "-lc",
244
+ action="store",
245
+ required=False,
246
+ dest="lab_config_path",
247
+ help="Input path of a YAML configuration for the scenario to run",
248
+ )
249
+ parser_scenario_create_lab.add_argument(
250
+ "--scenario_profile",
251
+ "-sp",
252
+ required=False,
253
+ dest="scenario_profile",
254
+ help="Allows to define the scenario config to run",
255
+ )
256
+ parser_scenario_create_lab.add_argument(
257
+ "--public",
258
+ action="store_true",
259
+ help="Create a public link to access this lab",
260
+ )
261
+
262
+ # 'scenario_run_lab' command
263
+ parser_scenario_run_lab = subparsers_scenario.add_parser(
264
+ "run",
265
+ help="Create and run a lab with a specific scenario",
266
+ formatter_class=root_parser.formatter_class,
267
+ )
268
+ parser_scenario_run_lab.set_defaults(func=scenario_run_lab_handler)
269
+ parser_scenario_run_lab.add_argument(
270
+ "scenario_name", type=str, help="The scenario name"
271
+ )
272
+ parser_scenario_run_lab.add_argument(
273
+ "--workspace",
274
+ dest="workspace_id",
275
+ help="The group ID that have ownership on lab",
276
+ )
277
+ parser_scenario_run_lab.add_argument(
278
+ "--topology",
279
+ action="store",
280
+ required=False,
281
+ dest="topology_file",
282
+ help="Input path of a YAML topology file to override the default one",
283
+ )
284
+ parser_scenario_run_lab.add_argument(
285
+ "--destroy",
286
+ action="store_true",
287
+ dest="destroy_after_scenario",
288
+ help="Do not keep the lab up after scenario execution (False by default)",
289
+ )
290
+ parser_scenario_run_lab.add_argument(
291
+ "--lab_config",
292
+ "-lc",
293
+ action="store",
294
+ required=False,
295
+ dest="lab_config_path",
296
+ help="Input path of a YAML configuration for the scenario run",
297
+ )
298
+ parser_scenario_run_lab.add_argument(
299
+ "--scenario_profile",
300
+ "-sp",
301
+ required=False,
302
+ dest="scenario_profile",
303
+ help="Allows to define the scenario config to run",
304
+ )
305
+ parser_scenario_run_lab.add_argument(
306
+ "--public",
307
+ action="store_true",
308
+ help="Create a public link to access this lab",
309
+ )
310
+
311
+ parser_scenario.set_defaults(func=lambda _: parser_scenario.print_help())
@@ -0,0 +1,147 @@
1
+ # -*- coding: utf-8 -*-
2
+ import argparse
3
+ import json
4
+ import sys
5
+ from typing import Any
6
+ from typing import List
7
+
8
+ from mantis_scenario_model.signature_model import Signature
9
+ from pydantic.json import pydantic_encoder
10
+
11
+ from mantis_api_client import scenario_api
12
+ from mantis_api_client.utils import colored
13
+
14
+
15
+ #
16
+ # 'signature_list_handler' handler
17
+ #
18
+ def signature_list_handler(args: Any) -> None:
19
+ try:
20
+ signatures = scenario_api.fetch_signatures()
21
+ except Exception as e:
22
+ print(colored(f"Error when fetching signatures: '{e}'", "red"))
23
+ sys.exit(1)
24
+
25
+ if args.json:
26
+ print(json.dumps(signatures, default=pydantic_encoder))
27
+ return
28
+ for key, signature in sorted(
29
+ signatures.items(), key=lambda kv: kv[1].attack_reference_id # (clé, Signature)
30
+ ):
31
+ print(f"[+] \033[1m{signature.attack_reference_id}\033[0m")
32
+ print(f" [+] id: {key}")
33
+ implems = ", ".join(
34
+ imp.implementation_type.value for imp in signature.implementations
35
+ )
36
+ print(f" [+] available implementations: {implems}")
37
+
38
+
39
+ def add_signature_parser(
40
+ root_parser: argparse.ArgumentParser,
41
+ subparsers: Any,
42
+ ) -> None:
43
+ # -------------------
44
+ # --- Scenario API options (signatures)
45
+ # -------------------
46
+
47
+ parser_signature = subparsers.add_parser(
48
+ "signature",
49
+ help="Scenario API related commands (signature)",
50
+ formatter_class=root_parser.formatter_class,
51
+ )
52
+ subparsers_signature = parser_signature.add_subparsers()
53
+
54
+ # 'signature_list' command
55
+ parser_signature_list = subparsers_signature.add_parser(
56
+ "list",
57
+ help="List all available signatures",
58
+ formatter_class=root_parser.formatter_class,
59
+ )
60
+ parser_signature_list.set_defaults(func=signature_list_handler)
61
+ parser_signature_list.add_argument(
62
+ "--json", help="Return JSON result.", action="store_true"
63
+ )
64
+
65
+ parser_signature_info = subparsers_signature.add_parser(
66
+ "info",
67
+ help="Get information about a signature",
68
+ formatter_class=root_parser.formatter_class,
69
+ )
70
+ parser_signature_info.set_defaults(func=signature_info_handler)
71
+ parser_signature_info.add_argument(
72
+ "signature_id", type=str, help="The signature id"
73
+ )
74
+ parser_signature_info.add_argument(
75
+ "--json", help="Return JSON result.", action="store_true"
76
+ )
77
+
78
+ group = parser_signature_info.add_mutually_exclusive_group()
79
+
80
+ # Ajoutez les arguments au groupe
81
+ group.add_argument(
82
+ "--attack",
83
+ help="The signature_id is a reference to an attack name. Cannot be used with --scenario.",
84
+ action="store_true",
85
+ )
86
+ group.add_argument(
87
+ "--scenario",
88
+ help="The signature_id is a reference to a scenario name. Cannot be used with --attack.",
89
+ action="store_true",
90
+ )
91
+
92
+ parser_signature.set_defaults(func=lambda _: parser_signature.print_help())
93
+
94
+
95
+ def print_signatures_of_scenario(signatures: List[Signature]) -> None:
96
+ for sig in signatures:
97
+ print_signature(sig)
98
+
99
+
100
+ def print_signature(signature) -> None:
101
+
102
+ print(f"[+] \033[1m{signature.attack_reference_id}\033[0m")
103
+ print(" [+] available implementations:")
104
+ for i in signature.implementations:
105
+ print(f" [>] {i.implementation_type.value}")
106
+ for k in i.signatures:
107
+ print(f" [+] Format \033[1m{k.signature_type.value}\033[0m")
108
+ print(f" {k.signature}")
109
+
110
+
111
+ def signature_info_handler(args: Any) -> None:
112
+ # Parameters
113
+ signature_id = args.signature_id
114
+ signatures = []
115
+ signature = None
116
+
117
+ try:
118
+ if args.attack is True:
119
+ # It's an attack ID
120
+ signature = scenario_api.fetch_signature_by_attack_id(signature_id)
121
+ elif args.scenario is True:
122
+ # It's a scenario ID
123
+ signatures = scenario_api.fetch_signatures_by_scenario_id(signature_id)
124
+ else:
125
+ # It's a signature ID
126
+ signature = scenario_api.fetch_signature_by_signature_id(signature_id)
127
+ except Exception as e:
128
+ print(colored(f"Error when fetching signature {signature_id}: '{e}'", "red"))
129
+ sys.exit(1)
130
+
131
+ if args.json and args.scenario is False and signature is not None:
132
+ print(signature.json())
133
+ return
134
+ elif args.json and args.scenario is True and signatures is not None:
135
+ json_data = [sig.json() for sig in signatures]
136
+
137
+ # Now json_data is a list of JSON strings, but if you want it in the final JSON format, use json.dumps
138
+ json_str = json.dumps(json_data)
139
+
140
+ # Print the final JSON string
141
+ print(json_str)
142
+ return
143
+
144
+ if args.scenario is True:
145
+ print_signatures_of_scenario(signatures)
146
+ else:
147
+ print_signature(signature)
@@ -0,0 +1,255 @@
1
+ # -*- coding: utf-8 -*-
2
+ import argparse
3
+ import json
4
+ import sys
5
+ from typing import Any
6
+
7
+ from mantis_scenario_model.lab_config_model import LabConfig
8
+ from mantis_scenario_model.node import VirtualMachine
9
+ from pydantic.json import pydantic_encoder
10
+ from ruamel.yaml import YAML
11
+
12
+ from mantis_api_client import scenario_api
13
+ from mantis_api_client.oidc import get_oidc_client
14
+ from mantis_api_client.utils import colored
15
+ from mantis_api_client.utils import wait_lab
16
+
17
+
18
+ #
19
+ # 'topology_list_handler' handler
20
+ #
21
+ def topology_list_handler(args: Any) -> None:
22
+ try:
23
+ topologies = sorted(scenario_api.fetch_topologies(), key=lambda x: x.name)
24
+ except Exception as e:
25
+ print(colored(f"Error when fetching topologies: '{e}'", "red"))
26
+ sys.exit(1)
27
+
28
+ if args.json:
29
+ print(json.dumps(topologies, default=pydantic_encoder))
30
+ return
31
+
32
+ print("[+] Available topologies:")
33
+ for topology in topologies:
34
+ print(f" [+] {topology.name}")
35
+
36
+
37
+ #
38
+ # 'topology_info_handler' handler
39
+ #
40
+ def topology_info_handler(args: Any) -> None:
41
+ # Parameters
42
+ topology_name = args.topology_name
43
+
44
+ try:
45
+ topology = scenario_api.fetch_topology_by_name(topology_name)
46
+ except Exception as e:
47
+ print(colored(f"Error when fetching topology {topology_name}: '{e}'", "red"))
48
+ sys.exit(1)
49
+
50
+ if args.json:
51
+ print(topology.json())
52
+ return
53
+
54
+ print("[+] Topology information:")
55
+ print(f" [+] \033[1mName\033[0m: {topology.name}")
56
+ print(" [+] \033[1mNodes\033[0m:")
57
+ for node in topology.nodes:
58
+ if type(node) is VirtualMachine:
59
+ print(f" [+] {node.type}: {node.name} ({node.basebox_id})")
60
+ else:
61
+ print(f" [+] {node.type}: {node.name}")
62
+ print(" [+] \033[1mLinks\033[0m:")
63
+ for link in topology.links:
64
+ print(
65
+ f" [+] {link.switch.name}({link.switch.type}) -- {link.params.ip} -- {link.node.name}({link.node.type})"
66
+ )
67
+
68
+
69
+ def _topology_create_or_run_lab(args: Any, do_run: bool = True):
70
+ # Parameters
71
+ topology_name = args.topology_name
72
+ lab_config_path = args.lab_config_path
73
+
74
+ if not args.workspace_id:
75
+ try:
76
+ workspace_id = get_oidc_client().get_default_workspace(raise_exc=True)
77
+ except Exception as e:
78
+ print(colored(f"Error when fetching attacks: '{e}'", "red"))
79
+ sys.exit(1)
80
+ else:
81
+ workspace_id = args.workspace_id
82
+
83
+ # Retrieve associated group id
84
+ if workspace_id is None:
85
+ print(colored("You have to specify a workspace with --workspace", "yellow"))
86
+ sys.exit(1)
87
+
88
+ # Manage lab configuration
89
+ if lab_config_path is None:
90
+ lab_config_dict = {}
91
+ else:
92
+ with open(lab_config_path, "r") as fd:
93
+ yaml_content = fd.read()
94
+ loader = YAML(typ="rt")
95
+ lab_config_dict = loader.load(yaml_content)
96
+ lab_config = LabConfig(**lab_config_dict)
97
+
98
+ # Launch topology
99
+ try:
100
+ topology = scenario_api.fetch_topology_by_name(topology_name)
101
+ print(f"[+] Going to execute topology: {topology.name}")
102
+
103
+ if do_run:
104
+ lab_id = scenario_api.run_lab_topology(
105
+ topology,
106
+ lab_config,
107
+ workspace_id,
108
+ args.public,
109
+ )
110
+ else:
111
+ lab_id = scenario_api.create_lab_topology(
112
+ topology,
113
+ lab_config,
114
+ workspace_id,
115
+ args.public,
116
+ )
117
+
118
+ print(f"[+] Lab ID: {lab_id}")
119
+
120
+ if do_run:
121
+ wait_lab(lab_id)
122
+
123
+ except Exception as e:
124
+ print(colored(f"Error when running topology {topology_name}: '{e}'", "red"))
125
+ sys.exit(1)
126
+ finally:
127
+ if do_run and args.destroy_after_scenario:
128
+ print("[+] Stopping lab...")
129
+ scenario_api.stop_lab(lab_id)
130
+
131
+
132
+ #
133
+ # 'topology_create_lab_handler' handler
134
+ #
135
+ def topology_create_lab_handler(args: Any) -> None:
136
+ _topology_create_or_run_lab(args, do_run=False)
137
+
138
+
139
+ #
140
+ # 'topology_run_lab_handler' handler
141
+ #
142
+ def topology_run_lab_handler(args: Any) -> None:
143
+ _topology_create_or_run_lab(args, do_run=True)
144
+
145
+
146
+ def add_topology_parser(
147
+ root_parser: argparse.ArgumentParser,
148
+ subparsers: Any,
149
+ ) -> None:
150
+ # -------------------
151
+ # --- Scenario API options (topologies)
152
+ # -------------------
153
+
154
+ parser_topology = subparsers.add_parser(
155
+ "topology",
156
+ help="Scenario API related commands (topology)",
157
+ formatter_class=root_parser.formatter_class,
158
+ )
159
+ subparsers_topology = parser_topology.add_subparsers()
160
+
161
+ # 'topology_list' command
162
+ parser_topology_list = subparsers_topology.add_parser(
163
+ "list",
164
+ help="List all available topologies",
165
+ formatter_class=root_parser.formatter_class,
166
+ )
167
+ parser_topology_list.set_defaults(func=topology_list_handler)
168
+ parser_topology_list.add_argument(
169
+ "--json", help="Return JSON result.", action="store_true"
170
+ )
171
+
172
+ # 'topology_info' command
173
+ parser_topology_info = subparsers_topology.add_parser(
174
+ "info",
175
+ help="Get information about a topology",
176
+ formatter_class=root_parser.formatter_class,
177
+ )
178
+ parser_topology_info.set_defaults(func=topology_info_handler)
179
+ parser_topology_info.add_argument(
180
+ "topology_name", type=str, help="The topology name"
181
+ )
182
+ parser_topology_info.add_argument(
183
+ "--json", help="Return JSON result.", action="store_true"
184
+ )
185
+
186
+ # 'topology_create_lab' command
187
+ parser_topology_create_lab = subparsers_topology.add_parser(
188
+ "create",
189
+ help="Create a lab with a specific topology",
190
+ formatter_class=root_parser.formatter_class,
191
+ )
192
+ parser_topology_create_lab.set_defaults(func=topology_create_lab_handler)
193
+ parser_topology_create_lab.add_argument(
194
+ "topology_name", type=str, help="The topology name"
195
+ )
196
+ parser_topology_create_lab.add_argument(
197
+ "--destroy",
198
+ action="store_true",
199
+ dest="destroy_after_scenario",
200
+ help="Do not keep the lab up after topology execution (False by default)",
201
+ )
202
+ parser_topology_create_lab.add_argument(
203
+ "--workspace",
204
+ dest="workspace_id",
205
+ help="The group ID that have ownership on lab",
206
+ )
207
+ parser_topology_create_lab.add_argument(
208
+ "--lab_config",
209
+ action="store",
210
+ required=False,
211
+ dest="lab_config_path",
212
+ help="Input path of a YAML configuration for the scenario to run",
213
+ )
214
+ parser_topology_create_lab.add_argument(
215
+ "--public",
216
+ action="store_true",
217
+ help="Create a public link to access this lab",
218
+ )
219
+
220
+ # 'topology_run_lab' command
221
+ parser_topology_run_lab = subparsers_topology.add_parser(
222
+ "run",
223
+ help="Create and run a lab with a specific topology",
224
+ formatter_class=root_parser.formatter_class,
225
+ )
226
+ parser_topology_run_lab.set_defaults(func=topology_run_lab_handler)
227
+ parser_topology_run_lab.add_argument(
228
+ "topology_name", type=str, help="The topology name"
229
+ )
230
+ parser_topology_run_lab.add_argument(
231
+ "--destroy",
232
+ action="store_true",
233
+ dest="destroy_after_scenario",
234
+ help="Do not keep the lab up after topology execution (False by default)",
235
+ )
236
+ parser_topology_run_lab.add_argument(
237
+ "--workspace",
238
+ dest="workspace_id",
239
+ help="The group ID that have ownership on lab",
240
+ )
241
+ parser_topology_run_lab.add_argument(
242
+ "--lab_config",
243
+ "-lc",
244
+ action="store",
245
+ required=False,
246
+ dest="lab_config_path",
247
+ help="Input path of a YAML configuration for the scenario run",
248
+ )
249
+ parser_topology_run_lab.add_argument(
250
+ "--public",
251
+ action="store_true",
252
+ help="Create a public link to access this lab",
253
+ )
254
+
255
+ parser_topology.set_defaults(func=lambda _: parser_topology.print_help())