Beispiel #1
0
    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')
Beispiel #2
0
    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,
            },
        )
Beispiel #3
0
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)
Beispiel #4
0
 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)))
Beispiel #5
0
 def _execute_popen(self, command):
     log.debug('Executing {}'.format(' '.join(command)))
     try:
         Popen(command)
     except:
         raise OobError('Could not open browser {}'.format(
             ' '.join(command)))
Beispiel #6
0
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 [], []
Beispiel #7
0
    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
Beispiel #8
0
 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()
Beispiel #9
0
    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
Beispiel #10
0
    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
Beispiel #11
0
    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)
Beispiel #12
0
    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)))
Beispiel #13
0
    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']
Beispiel #14
0
    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
Beispiel #15
0
    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
Beispiel #16
0
    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,
            }
Beispiel #17
0
    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)
Beispiel #18
0
    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!')