def set_ipmi_password(self): args = self.parsed_args stdout = self._execute(['user', 'list'], output=True).split('\n') header = stdout[0] name_start = header.find('Name') found = False for line in stdout[1:]: uid = line[:name_start].strip() username = line[name_start:line.find(' ', name_start)] if username == self.username: found = True log.debug('ID of user {} is {}'.format(username, uid)) break if not found: self._print('User {} not found'.format(self.username)) return self._print( self._execute(['user', 'set', 'password', uid, args.new_password], output=True)) if args.secret_role is not None: self._print('Updating {} NetBox secret'.format(args.secret_role)) r = self.dcim.set_secret(args.secret_role, self.oob_info['info'], username, args.new_password) if r.status_code >= 300: log.critical('Setting NetBox secret failed') log.critical('Error {}:\n{}'.format(r.status_code, r.text)) else: self._print('Successfully updated secret')
def set_secret(self, role_name, oob_info, secret_name, secret_text): log.debug('Will upsert secret {} for device {}'.format( role_name, oob_info['name'])) role_id = self._get_secret_role_id(role_name) if role_id is None: log.critical('unknown role slug {}'.format(role_name)) url = os.path.join(self.api_url, 'api/secrets/secrets/') f = requests.post existing_secrets = self.get_secrets(oob_info) for s in existing_secrets: if s['role'] == role_name and s['name'] == secret_name: log.debug('Updating secret with role {} and name {}'.format( role_name, secret_name)) url = os.path.join(url, '{}/'.format(s['id'])) f = requests.patch break return f( url=url, headers=self._get_headers(with_session_key=True), json={ 'device': oob_info['id'], 'role': role_id, 'name': secret_name, 'plaintext': secret_text, }, )
def get_config(config_path): try: extra_paths = [] if os.getenv('SNAP_COMMON'): extra_paths.extend([os.path.expandvars('$SNAP_COMMON/bmcmanager')]) if os.getenv('XDG_CONFIG_HOME'): extra_paths.extend([ os.path.expandvars('$XDG_CONFIG_HOME/.config/bmcmanager'), os.path.expandvars('$XDG_CONFIG_HOME/bmcmanager') ]) config = configparser.ConfigParser() which = config.read([ config_path, os.getenv('BMCMANAGER_CONFIG', ''), os.path.expanduser('~/.config/bmcmanager'), '/etc/bmcmanager', *extra_paths, ]) log.debug('Loaded config from {}'.format(which)) except configparser.ParsingError as e: log.error('Invalid configuration file: {}'.format(e)) sys.exit(1) return format_config(config)
def _execute_cmd(self, command): log.debug('Executing {}'.format(' '.join(command))) try: call(command) except CalledProcessError as e: raise RuntimeError('Command {} failed: {}'.format( ' '.join(command), str(e)))
def _execute_popen(self, command): log.debug('Executing {}'.format(' '.join(command))) try: Popen(command) except: raise OobError('Could not open browser {}'.format( ' '.join(command)))
def bmcmanager_take_action(cmd, parsed_args): cmd.parsed_args = parsed_args cmd.config = get_config(parsed_args.config_file) dcim = get_dcim(parsed_args, cmd.config) idx = None for idx, oob_info in enumerate(dcim.get_oobs()): oob_config = get_oob_config(cmd.config, dcim, oob_info) log.debug('Creating OOB object for {}'.format(oob_info['oob'])) try: oob = OOBS.get(oob_info['oob'])(parsed_args, dcim, oob_config, oob_info) except KeyError: raise BMCManagerError('Invalid OOB {}'.format(oob_info['oob'])) try: if hasattr(cmd, 'oob_method'): return getattr(oob, cmd.oob_method)() else: return cmd.action(oob) except Exception as e: log.exception('Unhandled exception: {}'.format(e)) if idx is None: log.fatal('No servers found for "{}"'.format(parsed_args.server)) return [], []
def info(self): log.debug('Executing info') info = self.oob_info['info'] columns = info.keys() values = [info[col] for col in columns] return columns, values
def _retrieve_info(self): log.debug('Querying the Netbox API for {}'.format(self.identifier)) url = os.path.join(self.api_url, 'api/dcim/devices/') params = self._get_params() params['limit'] = 0 json_response = self._do_request(url, params) log.debug('Decoding the response') # we expect the response to be a json object return json_response.json()
def _get_secret_role_id(self, role_name): log.debug('Searching for id of secret role {}'.format(role_name)) response = self._do_request(url=os.path.join( self.api_url, 'api/secrets/secret-roles/'), params={'slug': role_name}) try: return response.json()['results'][0]['id'] except (TypeError, KeyError, IndexError): return None
def _get_secrets(self, oob_info): log.debug('Searching for secrets of device {}'.format( oob_info['name'])) response = self._do_request(url=os.path.join(self.api_url, 'api/secrets/secrets/'), params={ 'device': oob_info['name'], }, with_session_key=True) return response
def _do_request(self, url, params, with_session_key=False, method='get'): headers = self._get_headers(with_session_key) log.debug('HTTP {} {}, {}, {}'.format(method.upper(), url, str(params), str(headers))) try: f = getattr(requests, method) return f(url, params=params, headers=headers, timeout=self.timeout) except (requests.exceptions.Timeout, requests.exceptions.ConnectionError): # useful instead of a long exception dump sys.stderr.write('Request timed out {}'.format(url)) exit(1)
def _execute_cmd(self, command, output=False): log.debug('Executing {}'.format(' '.join(command))) try: if output: return check_output(command).decode('utf-8') call(command) except CalledProcessError as e: raise OobError('Command {} failed: {}'.format( ' '.join(command), str(e))) except UnicodeError as e: raise OobError('Decoding output of {} failed: {}'.format( ' '.join(command), str(e)))
def _get_rack_id(self): log.debug('Querying the Netbox API for rack {}'.format( self.identifier)) url = os.path.join(self.api_url, 'api/dcim/racks/') params = {'name': self.identifier} json_response = self._do_request(url, params) log.debug('Decoding the response') # we expect the response to be a json object response = json_response.json() if len(response['results']) != 1: raise DcimError('Did not find valid results for rack {}'.format( self.identifier)) return response['results'][0]['id']
def get(self): url = LENOVO_URL.format(self.model_name, self.device_name) try: log.debug('GET {}'.format(url)) response = json.loads(urllib.request.urlopen(url).read()) items = response['body']['DownloadItems'] except (json.JSONDecodeError, urllib.error.URLError) as e: log.error('Could not fetch URL: {}'.format(e)) return {}, [] except KeyError as e: log.error('Invalid data format: {}'.format(e)) return {}, [] tracked_firmware = {**TRACKED_FIRMWARE, **self.extra_firmware} result = [] downloads = [] for item in items: try: component = tracked_firmware.get(item['Title']) if component is None: continue fw = next(filter( lambda f: f['TypeString'] != 'TXT README', item['Files'])) downloads.append(fw['URL']) timestamp = datetime.fromtimestamp(fw['Date']['Unix'] / 1000) result.append({ 'component': component, 'name': item['Title'], 'version': item['SummaryInfo']['Version'], 'date': str(timestamp), 'file': fw['URL'], }) except ValueError as e: log.error('Invalid data format: {}'.format(e)) except KeyError as e: log.error('Missing information: {}'.format(e)) return result, downloads
def _parse_response(self, text): """ Parse response and return (list of results, retryable) """ try: start = text.find('[') end = text.rfind(']') results = eval(text[start:end + 1]) # drop empty '{}' objects from list of responses return [r for r in results if r], False except (SyntaxError, TypeError, ValueError): if 'session_expired.html' in text: log.critical('Failed due to expired session') return [], True else: log.critical('Could not parse response text') log.debug('Response was:\n{}'.format(text)) return [], False
def get_secret(self, role, oob_info): device = oob_info['info']['name'] log.debug('Querying secret {} of device {}'.format(role, device)) response = self._do_request(url=os.path.join(self.api_url, 'api/secrets/secrets/'), params={ 'role': role, 'device': device.upper() }, with_session_key=True) try: return response.json()['results'][0] except (TypeError, KeyError, IndexError): log.warning('Did not find secret {} for device {}'.format( role, device)) return { 'name': None, 'plaintext': None, }
def factory_reset(self): args = self.parsed_args if not args.force: response = input('Factory reset? [y/N] ') if response != 'y': log.info('Aborted') return self._connect() log.info('Setting preserve config') r = self._get_rpc('setpreservecfg', params={ 'PRSRV_CFG': '0,0,0,0,0,0,0,0,0,0,0,', 'PRSRV_CFG_CNT': '11', 'PRSRV_SELECT': '0,1,2,3,4,5,6,7,8,9,10,', }) log.debug(r) log.info('Starting factory reset') r = self._get_rpc('setfactorydefaults') log.debug(r) log.info('Factory reset process started') if args.wait: begin = datetime.utcnow() while datetime.utcnow() < begin + timedelta(minutes=args.timeout): try: url = self._get_http_ipmi_host() + self.URL_VALIDATE answer = self._post(url, None, self.session_token, self.CSRF_token) if answer.status_code == 200: log.info('Done.') return log.debug(answer) except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError, ConnectionResetError, BrokenPipeError): log.info('In progress') time.sleep(10)
def firmware_upgrade_rpc(self): args = self.parsed_args handle = None if 1 in args.stages: log.info('Enter FW update mode') r = self._get_rpc('getenterfwupdatemode', params={'FWUPMODE': 1}) log.debug(r) if r and 'HANDLE' in r[0]: handle = r[0]['HANDLE'] log.info('Enter FW update mode: OK') else: log.fatal('Cannot enter FW update mode') sys.exit(-1) log.info('Update session handle: {}'.format(handle)) if 2 in args.stages: handle = handle or args.handle log.info('Rearm firmware update timer') r = self._get_rpc('rearmfwupdatetimer', params={'SESSION_ID': handle}) log.debug(r) if r[0]['NEWSESSIONID'] == handle: log.info('Rearm firmware update timer: OK') if 3 in args.stages: handle = handle or args.handle if not hasattr(self, 'CSRF_token'): self._connect() log.info('Uploading firmware bundle') ipmi = self.oob_info['ipmi'] url = ipmi + '/file_upload_firmware.html' r = requests.post( url, verify=False, cookies=self.session_token, headers=self.CSRF_token, files={'bundle?FWUPSessionid={}'.format(handle): args.bundle}) log.debug(r.status_code) if r.status_code == 200: log.info('Uploading firmware bundle: OK') if 4 in args.stages: log.info('Get Bundle Upload Status') r = self._get_rpc('getbundleupldstatus') log.debug(r) if r == []: log.info('Get Bundle Upload Status: OK') if 5 in args.stages: log.info('Validate Bundle') r = self._get_rpc('validatebundle', params={'BUNDLENAME': 'bundle_bkp.bdl'}) log.debug(r) if r[0]['STATUS'] == 0: log.info('Validate Bundle: OK') if 6 in args.stages: log.info('Replace Bundle') r = self._get_rpc('replacebundlebkp') log.debug(r) if r[0]['STATUS'] == 0: log.info('Replace Bundle: OK') if 7 in args.stages: log.info('Checking for new firmware') r = self._get_rpc('getimageinfo') log.debug(r) def has_update(x): new, cur = x['NEWIMG_VER'], x['CURIMG_VER'] try: return version_tuple(new) > version_tuple(cur) except (TypeError, ValueError): return new > cur to_update = next(filter(has_update, r), None) if to_update is None: log.info('No updates available') return else: log.info('Available update: {}'.format(to_update)) if 8 in args.stages and to_update: handle = handle or args.handle log.info('Choose component update') r = self._get_rpc('setupdatecomp', params={ 'UPDATE_FLAG': to_update['DEV_TYPE'], 'UPDATE_CNT': 1, 'FW_DEVICE_TYPE': to_update['DEV_TYPE'], 'SLOT_NO': to_update['SLOT_NO'], 'DEV_IDENTIFIER': to_update['DEV_IDENTIFIER'], 'SESSION_ID': handle, }) log.debug(r) if r == []: log.info('Choose component update: OK') log.info('Firmware upgrade process started') if 9 in args.stages: begin = datetime.utcnow() while datetime.utcnow() < begin + timedelta(minutes=args.timeout): try: r = self._get_rpc('getcompupdatestatus') log.debug(r) dev = next((x for x in r if self._matching(x, to_update)), None) log.debug(dev) progress = (dev or {}).get('UPDATE_PERCENTAGE') if progress is None: log.info('Update in progress') else: log.info('Update progress: {}%'.format(progress)) if progress == 100: log.info('Update complete!') break except (ConnectionResetError, BrokenPipeError): log.info('Update in progress') time.sleep(10) if 10 in args.stages: handle = handle or args.handle log.info('Exit FW update mode') r = self._get_rpc('getexitfwupdatemode', params={ 'MODE': 0, 'RNDNO': handle }) log.debug(r) if r == []: log.info('Exit FW update mode: OK') log.info('Done!')