bmctools 0.1.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.
bmctools/__init__.py ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ """BMCTools CLI module."""
2
+
3
+ __version__ = '0.1.0'
@@ -0,0 +1 @@
1
+ """CLI command handlers."""
@@ -0,0 +1,138 @@
1
+ """Common command utilities shared across all commands."""
2
+
3
+ import sys
4
+ from bmctools.cli.formatters import format_output
5
+ from bmctools.cli.utils import (
6
+ handle_error,
7
+ get_exit_code,
8
+ print_verbose,
9
+ print_success
10
+ )
11
+
12
+
13
+ def wrap_command(func, args):
14
+ """Wrap command execution with common error handling.
15
+
16
+ Args:
17
+ func: Command function to execute
18
+ args: Parsed arguments
19
+
20
+ Returns:
21
+ Exit code (0 for success, non-zero for error)
22
+ """
23
+ try:
24
+ result = func(args)
25
+ if result is not None:
26
+ output = format_output(result, args.output)
27
+ print(output)
28
+ return 0
29
+ except Exception as e:
30
+ handle_error(e, args)
31
+ return get_exit_code(e)
32
+
33
+
34
+ def read_file_lines(file_path):
35
+ """Read lines from a file.
36
+
37
+ Args:
38
+ file_path: Path to file
39
+
40
+ Returns:
41
+ List of lines (stripped of whitespace)
42
+
43
+ Raises:
44
+ FileNotFoundError: If file doesn't exist
45
+ """
46
+ try:
47
+ with open(file_path, 'r') as f:
48
+ lines = [line.strip() for line in f if line.strip()]
49
+ return lines
50
+ except FileNotFoundError:
51
+ raise FileNotFoundError(f"File not found: {file_path}")
52
+ except Exception as e:
53
+ raise ValueError(f"Failed to read file {file_path}: {e}")
54
+
55
+
56
+ def parse_comma_list(value):
57
+ """Parse comma-separated list.
58
+
59
+ Args:
60
+ value: Comma-separated string
61
+
62
+ Returns:
63
+ List of strings
64
+ """
65
+ if not value:
66
+ return []
67
+ return [item.strip() for item in value.split(',') if item.strip()]
68
+
69
+
70
+ def confirm_action(prompt, force=False):
71
+ """Prompt user for confirmation on destructive operations.
72
+
73
+ Args:
74
+ prompt: Confirmation prompt message
75
+ force: If True, skip confirmation and return True
76
+
77
+ Returns:
78
+ True if confirmed, False otherwise
79
+ """
80
+ if force:
81
+ return True
82
+
83
+ try:
84
+ response = input(f"{prompt} [y/N]: ").strip().lower()
85
+ return response in ('y', 'yes')
86
+ except (KeyboardInterrupt, EOFError):
87
+ print() # New line after interrupt
88
+ return False
89
+
90
+
91
+ def show_progress(message, done=False):
92
+ """Display progress indicator.
93
+
94
+ Args:
95
+ message: Progress message
96
+ done: If True, show completion, else show in-progress
97
+ """
98
+ if done:
99
+ print(f"✓ {message}", file=sys.stderr)
100
+ else:
101
+ print(f"⋯ {message}...", file=sys.stderr, end='', flush=True)
102
+
103
+
104
+ def validate_file_exists(file_path):
105
+ """Validate that a file exists.
106
+
107
+ Args:
108
+ file_path: Path to file
109
+
110
+ Raises:
111
+ FileNotFoundError: If file doesn't exist
112
+ """
113
+ import os
114
+ if not os.path.exists(file_path):
115
+ raise FileNotFoundError(f"File not found: {file_path}")
116
+ if not os.path.isfile(file_path):
117
+ raise ValueError(f"Not a file: {file_path}")
118
+
119
+
120
+ def parse_key_value_pairs(pairs):
121
+ """Parse key=value pairs from list of strings.
122
+
123
+ Args:
124
+ pairs: List of "key=value" strings
125
+
126
+ Returns:
127
+ Dict of key-value pairs
128
+
129
+ Raises:
130
+ ValueError: If format is invalid
131
+ """
132
+ result = {}
133
+ for pair in pairs:
134
+ if '=' not in pair:
135
+ raise ValueError(f"Invalid key=value format: {pair}")
136
+ key, value = pair.split('=', 1)
137
+ result[key.strip()] = value.strip()
138
+ return result
@@ -0,0 +1,292 @@
1
+ """IPMI command handlers."""
2
+
3
+ import sys
4
+ from bmctools.cli.utils import (
5
+ establish_ipmi_connection,
6
+ print_verbose,
7
+ )
8
+ from bmctools.cli.commands.common import wrap_command
9
+
10
+
11
+ def setup_ipmi_commands(parser):
12
+ """Setup IPMI subcommands.
13
+
14
+ Args:
15
+ parser: IPMI subparser
16
+ """
17
+ subparsers = parser.add_subparsers(dest='ipmi_group', help='IPMI operation group')
18
+
19
+ # Power management commands
20
+ power_parser = subparsers.add_parser('power', help='Power management')
21
+ setup_power_commands(power_parser)
22
+
23
+ # BMC management commands
24
+ bmc_parser = subparsers.add_parser('bmc', help='BMC management')
25
+ setup_bmc_commands(bmc_parser)
26
+
27
+ # SEL commands
28
+ sel_parser = subparsers.add_parser('sel', help='System Event Log')
29
+ setup_sel_commands(sel_parser)
30
+
31
+ # SOL commands
32
+ sol_parser = subparsers.add_parser('sol', help='Serial Over LAN')
33
+ setup_sol_commands(sol_parser)
34
+
35
+ # Raw command
36
+ raw_parser = subparsers.add_parser('raw', help='Execute raw IPMI command')
37
+ raw_parser.add_argument('command', help='Raw IPMI command')
38
+
39
+
40
+ def setup_power_commands(parser):
41
+ """Setup power management subcommands."""
42
+ subparsers = parser.add_subparsers(dest='power_action', help='Power action')
43
+
44
+ subparsers.add_parser('status', help='Get power status')
45
+ subparsers.add_parser('on', help='Power on system')
46
+ subparsers.add_parser('off', help='Power off system')
47
+ subparsers.add_parser('reset', help='Hard reset system')
48
+
49
+
50
+ def setup_bmc_commands(parser):
51
+ """Setup BMC management subcommands."""
52
+ subparsers = parser.add_subparsers(dest='bmc_action', help='BMC action')
53
+
54
+ subparsers.add_parser('reset-warm', help='Warm reset BMC')
55
+ subparsers.add_parser('reset-cold', help='Cold reset BMC')
56
+
57
+
58
+ def setup_sel_commands(parser):
59
+ """Setup SEL subcommands."""
60
+ subparsers = parser.add_subparsers(dest='sel_action', help='SEL action')
61
+
62
+ p = subparsers.add_parser('list', help='List system event log')
63
+ p.add_argument('--elist', action='store_true',
64
+ help='Use extended list format')
65
+ p.add_argument('--raw', action='store_true',
66
+ help='Show raw data')
67
+ p.add_argument('--age',
68
+ help='Filter by age (e.g., 7d, 24h)')
69
+
70
+
71
+ def setup_sol_commands(parser):
72
+ """Setup SOL subcommands."""
73
+ subparsers = parser.add_subparsers(dest='sol_action', help='SOL action')
74
+
75
+ subparsers.add_parser('deactivate', help='Deactivate Serial Over LAN')
76
+
77
+ p = subparsers.add_parser('looptest', help='Run SOL loopback test')
78
+ p.add_argument('--loops', type=int, default=10,
79
+ help='Number of loops (default: 10)')
80
+
81
+
82
+ # Power Management Handlers
83
+
84
+ def handle_power_status(args):
85
+ """Handle 'ipmi power status' command."""
86
+ ipmi = establish_ipmi_connection(args)
87
+ status = ipmi.power_status()
88
+ return {'power_status': status}
89
+
90
+
91
+ def handle_power_on(args):
92
+ """Handle 'ipmi power on' command."""
93
+ ipmi = establish_ipmi_connection(args)
94
+ print_verbose("Powering on system...", args)
95
+ result = ipmi.power_on()
96
+ return {'message': 'Power on command sent', 'result': result}
97
+
98
+
99
+ def handle_power_off(args):
100
+ """Handle 'ipmi power off' command."""
101
+ ipmi = establish_ipmi_connection(args)
102
+ print_verbose("Powering off system...", args)
103
+ result = ipmi.power_off()
104
+ return {'message': 'Power off command sent', 'result': result}
105
+
106
+
107
+ def handle_power_reset(args):
108
+ """Handle 'ipmi power reset' command."""
109
+ ipmi = establish_ipmi_connection(args)
110
+ print_verbose("Resetting system...", args)
111
+ result = ipmi.power_reset()
112
+ return {'message': 'Power reset command sent', 'result': result}
113
+
114
+
115
+ # BMC Management Handlers
116
+
117
+ def handle_bmc_reset_warm(args):
118
+ """Handle 'ipmi bmc reset-warm' command."""
119
+ ipmi = establish_ipmi_connection(args)
120
+ print_verbose("Performing warm reset of BMC...", args)
121
+ result = ipmi.bmc_reset_warm()
122
+ return {'message': 'BMC warm reset command sent', 'result': result}
123
+
124
+
125
+ def handle_bmc_reset_cold(args):
126
+ """Handle 'ipmi bmc reset-cold' command."""
127
+ ipmi = establish_ipmi_connection(args)
128
+ print_verbose("Performing cold reset of BMC...", args)
129
+ result = ipmi.bmc_reset_cold()
130
+ return {'message': 'BMC cold reset command sent', 'result': result}
131
+
132
+
133
+ # SEL Handlers
134
+
135
+ def handle_sel_list(args):
136
+ """Handle 'ipmi sel list' command."""
137
+ ipmi = establish_ipmi_connection(args)
138
+
139
+ elist = getattr(args, 'elist', False)
140
+ raw = getattr(args, 'raw', False)
141
+ age = getattr(args, 'age', None)
142
+
143
+ print_verbose(f"Fetching SEL (elist={elist}, raw={raw}, age={age})...", args)
144
+
145
+ result = ipmi.sel_list(elist=elist, raw=raw, age=age)
146
+
147
+ return {'sel_entries': result}
148
+
149
+
150
+ # SOL Handlers
151
+
152
+ def handle_sol_deactivate(args):
153
+ """Handle 'ipmi sol deactivate' command."""
154
+ ipmi = establish_ipmi_connection(args)
155
+ print_verbose("Deactivating Serial Over LAN...", args)
156
+ result = ipmi.sol_deactivate()
157
+ return {'message': 'SOL deactivate command sent', 'result': result}
158
+
159
+
160
+ def handle_sol_looptest(args):
161
+ """Handle 'ipmi sol looptest' command."""
162
+ ipmi = establish_ipmi_connection(args)
163
+ loops = getattr(args, 'loops', 10)
164
+
165
+ print_verbose(f"Running SOL loopback test ({loops} loops)...", args)
166
+
167
+ result = ipmi.sol_looptest(num_loops=loops)
168
+
169
+ return {'message': f'SOL loopback test completed', 'result': result}
170
+
171
+
172
+ # Raw Command Handler
173
+
174
+ def handle_raw(args):
175
+ """Handle 'ipmi raw' command."""
176
+ ipmi = establish_ipmi_connection(args)
177
+
178
+ print_verbose(f"Executing raw IPMI command: {args.command}", args)
179
+
180
+ result = ipmi.ipmitool_command(args.command)
181
+
182
+ return {'command': args.command, 'result': result}
183
+
184
+
185
+ # Dispatch Functions
186
+
187
+ def dispatch(args):
188
+ """Dispatch IPMI command to appropriate handler.
189
+
190
+ Args:
191
+ args: Parsed arguments
192
+
193
+ Returns:
194
+ Exit code
195
+ """
196
+ group = args.ipmi_group
197
+
198
+ if group == 'power':
199
+ return dispatch_power(args)
200
+ elif group == 'bmc':
201
+ return dispatch_bmc(args)
202
+ elif group == 'sel':
203
+ return dispatch_sel(args)
204
+ elif group == 'sol':
205
+ return dispatch_sol(args)
206
+ elif group == 'raw':
207
+ return wrap_command(handle_raw, args)
208
+ else:
209
+ print(f"Error: Unknown IPMI group: {group}", file=sys.stderr)
210
+ return 1
211
+
212
+
213
+ def dispatch_power(args):
214
+ """Dispatch power command."""
215
+ action = args.power_action
216
+ handlers = {
217
+ 'status': handle_power_status,
218
+ 'on': handle_power_on,
219
+ 'off': handle_power_off,
220
+ 'reset': handle_power_reset,
221
+ }
222
+
223
+ if action in handlers:
224
+ return wrap_command(handlers[action], args)
225
+ else:
226
+ print(f"Error: Unknown power action: {action}", file=sys.stderr)
227
+ return 1
228
+
229
+
230
+ def dispatch_bmc(args):
231
+ """Dispatch BMC command."""
232
+ action = args.bmc_action
233
+ handlers = {
234
+ 'reset-warm': handle_bmc_reset_warm,
235
+ 'reset-cold': handle_bmc_reset_cold,
236
+ }
237
+
238
+ if action in handlers:
239
+ return wrap_command(handlers[action], args)
240
+ else:
241
+ print(f"Error: Unknown BMC action: {action}", file=sys.stderr)
242
+ return 1
243
+
244
+
245
+ def dispatch_sel(args):
246
+ """Dispatch SEL command."""
247
+ action = args.sel_action
248
+ handlers = {
249
+ 'list': handle_sel_list,
250
+ }
251
+
252
+ if action in handlers:
253
+ return wrap_command(handlers[action], args)
254
+ else:
255
+ print(f"Error: Unknown SEL action: {action}", file=sys.stderr)
256
+ return 1
257
+
258
+
259
+ def dispatch_sol(args):
260
+ """Dispatch SOL command."""
261
+ action = args.sol_action
262
+ handlers = {
263
+ 'deactivate': handle_sol_deactivate,
264
+ 'looptest': handle_sol_looptest,
265
+ }
266
+
267
+ if action in handlers:
268
+ return wrap_command(handlers[action], args)
269
+ else:
270
+ print(f"Error: Unknown SOL action: {action}", file=sys.stderr)
271
+ return 1
272
+
273
+
274
+ def handle_alias(args, target):
275
+ """Handle aliased commands.
276
+
277
+ Args:
278
+ args: Parsed arguments
279
+ target: Alias target identifier
280
+
281
+ Returns:
282
+ Exit code
283
+ """
284
+ if target == 'ipmi_power_on':
285
+ return wrap_command(handle_power_on, args)
286
+ elif target == 'ipmi_power_off':
287
+ return wrap_command(handle_power_off, args)
288
+ elif target == 'ipmi_power_status':
289
+ return wrap_command(handle_power_status, args)
290
+ else:
291
+ print(f"Error: Unknown IPMI alias: {target}", file=sys.stderr)
292
+ return 1
@@ -0,0 +1,266 @@
1
+ """RACADM command handlers."""
2
+
3
+ import sys
4
+ from bmctools.cli.utils import (
5
+ establish_racadm_connection,
6
+ print_verbose,
7
+ )
8
+ from bmctools.cli.commands.common import wrap_command
9
+
10
+
11
+ def setup_racadm_commands(parser):
12
+ """Setup RACADM subcommands.
13
+
14
+ Args:
15
+ parser: RACADM subparser
16
+ """
17
+ subparsers = parser.add_subparsers(dest='racadm_group', help='RACADM operation group')
18
+
19
+ # Get configuration
20
+ p = subparsers.add_parser('get', help='Get configuration')
21
+ p.add_argument('endpoint', help='Configuration endpoint')
22
+ p.add_argument('--format', action='store_true',
23
+ help='Use formatted output')
24
+
25
+ # Set configuration
26
+ p = subparsers.add_parser('set', help='Set configuration')
27
+ p.add_argument('endpoint', help='Configuration endpoint')
28
+ p.add_argument('--args',
29
+ help='Comma-separated arguments')
30
+
31
+ # Storage commands
32
+ storage_parser = subparsers.add_parser('storage', help='Storage management')
33
+ setup_storage_commands(storage_parser)
34
+
35
+ # Job queue commands
36
+ job_parser = subparsers.add_parser('job', help='Job queue management')
37
+ setup_job_commands(job_parser)
38
+
39
+
40
+ def setup_storage_commands(parser):
41
+ """Setup storage subcommands."""
42
+ subparsers = parser.add_subparsers(dest='storage_action', help='Storage action')
43
+
44
+ p = subparsers.add_parser('get', help='Get storage configuration')
45
+ p.add_argument('endpoint', help='Storage endpoint')
46
+
47
+ p = subparsers.add_parser('check-vdisk', help='Check virtual disks')
48
+ p.add_argument('--format', action='store_true',
49
+ help='Use formatted output')
50
+
51
+
52
+ def setup_job_commands(parser):
53
+ """Setup job queue subcommands."""
54
+ subparsers = parser.add_subparsers(dest='job_action', help='Job action')
55
+
56
+ p = subparsers.add_parser('view', help='View job details')
57
+ p.add_argument('-j', '--job-id', required=True,
58
+ help='Job ID')
59
+
60
+ p = subparsers.add_parser('status', help='Get job status')
61
+ p.add_argument('-j', '--job-id', required=True,
62
+ help='Job ID')
63
+
64
+ p = subparsers.add_parser('wait', help='Wait for job completion')
65
+ p.add_argument('-j', '--job-id', required=True,
66
+ help='Job ID')
67
+ p.add_argument('--timeout', type=int, default=300,
68
+ help='Timeout in seconds (default: 300)')
69
+
70
+
71
+ # Configuration Handlers
72
+
73
+ def handle_get(args):
74
+ """Handle 'racadm get' command."""
75
+ racadm = establish_racadm_connection(args)
76
+
77
+ use_format = getattr(args, 'format', False)
78
+
79
+ print_verbose(f"Getting config: {args.endpoint} (format={use_format})", args)
80
+
81
+ result = racadm.get(
82
+ endpoint=args.endpoint,
83
+ arguments=None,
84
+ format=use_format
85
+ )
86
+
87
+ return {
88
+ 'endpoint': args.endpoint,
89
+ 'result': result
90
+ }
91
+
92
+
93
+ def handle_set(args):
94
+ """Handle 'racadm set' command."""
95
+ racadm = establish_racadm_connection(args)
96
+
97
+ # Parse arguments
98
+ arguments = None
99
+ if args.args:
100
+ arguments = [arg.strip() for arg in args.args.split(',')]
101
+
102
+ print_verbose(f"Setting config: {args.endpoint} (args={arguments})", args)
103
+
104
+ result = racadm.set(
105
+ endpoint=args.endpoint,
106
+ arguments=arguments
107
+ )
108
+
109
+ return {
110
+ 'endpoint': args.endpoint,
111
+ 'arguments': arguments,
112
+ 'result': result
113
+ }
114
+
115
+
116
+ # Storage Handlers
117
+
118
+ def handle_storage_get(args):
119
+ """Handle 'racadm storage get' command."""
120
+ racadm = establish_racadm_connection(args)
121
+
122
+ print_verbose(f"Getting storage config: {args.endpoint}", args)
123
+
124
+ result = racadm.storage_get(
125
+ endpoint=args.endpoint,
126
+ arguments=None
127
+ )
128
+
129
+ return {
130
+ 'endpoint': args.endpoint,
131
+ 'result': result
132
+ }
133
+
134
+
135
+ def handle_storage_check_vdisk(args):
136
+ """Handle 'racadm storage check-vdisk' command."""
137
+ racadm = establish_racadm_connection(args)
138
+
139
+ use_format = getattr(args, 'format', False)
140
+
141
+ print_verbose(f"Checking virtual disks (format={use_format})", args)
142
+
143
+ result = racadm.check_vdisk(format=use_format)
144
+
145
+ return {
146
+ 'result': result
147
+ }
148
+
149
+
150
+ # Job Queue Handlers
151
+
152
+ def handle_job_view(args):
153
+ """Handle 'racadm job view' command."""
154
+ racadm = establish_racadm_connection(args)
155
+
156
+ print_verbose(f"Viewing job: {args.job_id}", args)
157
+
158
+ result = racadm.jobqueue_view(job=args.job_id)
159
+
160
+ return {
161
+ 'job_id': args.job_id,
162
+ 'details': result
163
+ }
164
+
165
+
166
+ def handle_job_status(args):
167
+ """Handle 'racadm job status' command."""
168
+ racadm = establish_racadm_connection(args)
169
+
170
+ print_verbose(f"Getting job status: {args.job_id}", args)
171
+
172
+ result = racadm.jobqueue_status(job=args.job_id)
173
+
174
+ return {
175
+ 'job_id': args.job_id,
176
+ 'status': result
177
+ }
178
+
179
+
180
+ def handle_job_wait(args):
181
+ """Handle 'racadm job wait' command."""
182
+ racadm = establish_racadm_connection(args)
183
+
184
+ timeout = getattr(args, 'timeout', 300)
185
+
186
+ print_verbose(f"Waiting for job completion: {args.job_id} (timeout={timeout}s)", args)
187
+
188
+ result = racadm.jobqueue_wait(job=args.job_id)
189
+
190
+ return {
191
+ 'job_id': args.job_id,
192
+ 'completed': True,
193
+ 'result': result
194
+ }
195
+
196
+
197
+ # Dispatch Functions
198
+
199
+ def dispatch(args):
200
+ """Dispatch RACADM command to appropriate handler.
201
+
202
+ Args:
203
+ args: Parsed arguments
204
+
205
+ Returns:
206
+ Exit code
207
+ """
208
+ group = args.racadm_group
209
+
210
+ if group == 'get':
211
+ return wrap_command(handle_get, args)
212
+ elif group == 'set':
213
+ return wrap_command(handle_set, args)
214
+ elif group == 'storage':
215
+ return dispatch_storage(args)
216
+ elif group == 'job':
217
+ return dispatch_job(args)
218
+ else:
219
+ print(f"Error: Unknown RACADM group: {group}", file=sys.stderr)
220
+ return 1
221
+
222
+
223
+ def dispatch_storage(args):
224
+ """Dispatch storage command."""
225
+ action = args.storage_action
226
+ handlers = {
227
+ 'get': handle_storage_get,
228
+ 'check-vdisk': handle_storage_check_vdisk,
229
+ }
230
+
231
+ if action in handlers:
232
+ return wrap_command(handlers[action], args)
233
+ else:
234
+ print(f"Error: Unknown storage action: {action}", file=sys.stderr)
235
+ return 1
236
+
237
+
238
+ def dispatch_job(args):
239
+ """Dispatch job queue command."""
240
+ action = args.job_action
241
+ handlers = {
242
+ 'view': handle_job_view,
243
+ 'status': handle_job_status,
244
+ 'wait': handle_job_wait,
245
+ }
246
+
247
+ if action in handlers:
248
+ return wrap_command(handlers[action], args)
249
+ else:
250
+ print(f"Error: Unknown job action: {action}", file=sys.stderr)
251
+ return 1
252
+
253
+
254
+ def handle_alias(args, target):
255
+ """Handle aliased commands.
256
+
257
+ Args:
258
+ args: Parsed arguments
259
+ target: Alias target identifier
260
+
261
+ Returns:
262
+ Exit code
263
+ """
264
+ # No RACADM aliases currently defined
265
+ print(f"Error: Unknown RACADM alias: {target}", file=sys.stderr)
266
+ return 1