def step_history_info(ctx, spec=""): def pkgs_split(pkgs): return pkgs.split(",") actions = ['Install', 'Erase', 'Upgrade', 'Upgraded', 'Reinstall', 'Downgrade'] keys = ['Command Line', 'Return-Code'] + actions table = table_utils.parse_kv_table(ctx, ['Key', 'Value'], keys) step_i_run_command(ctx, 'dnf history info ' + spec) text = getattr(ctx.cmd_result, 'stdout') lines = text.split('\n') assert text and lines, 'No output' brpmdb = None erpmdb = None cmdline = None g_cmdline = table['Command Line'] if 'Command Line' in table else None retcode = None g_retcode = table['Return-Code'] if 'Return-Code' in table else None for line in lines: if 'Begin rpmdb' in line: brpmdb = line.split(':') if 'End rpmdb' in line: erpmdb = line.split(':') if brpmdb and erpmdb: assert len(brpmdb) == len(erpmdb) == 3, 'Unexpected rpmdb version format' erpmdb = None if 'Return-Code' in line and g_retcode: retcode = line.split(':') assert len(retcode) == 2, 'Unexpected Return-Code format' rc = retcode[1].strip() assert rc == g_retcode, 'Return-Code "{}" not matched by "{}"'.format(rc, g_retcode) if 'Command Line' in line and g_cmdline: cmdline = line.split(':') assert len(cmdline) == 2, 'Unexpected Command Line format' cmd = cmdline[1].strip() assert cmd == g_cmdline, 'Command Line "{}" not matched by "{}"'.format(cmd, g_cmdline) assert brpmdb, 'Begin rpmdb version not found' if g_cmdline: assert cmdline, 'Command line not found' if g_retcode: assert retcode, 'Return-Code not found' for key in actions: if key in table: pkgs = pkgs_split(table[key]) for line in lines: for pkg in pkgs: if pkg in line and key in line: pkgs.remove(pkg) assert not pkgs, '"{}" not matched as "{}"'.format(pkgs[0], key)
def step_history_info(ctx, spec=""): def pkgs_split(pkgs): return pkgs.split(",") actions = ['Install', 'Removed', 'Upgrade', 'Upgraded', 'Reinstall', 'Downgrade'] keys = ['Command Line', 'Return-Code'] + actions table = table_utils.parse_kv_table(ctx, ['Key', 'Value'], keys) step_i_run_command(ctx, 'dnf history info ' + spec) text = getattr(ctx.cmd_result, 'stdout') lines = text.split('\n') assert text and lines, 'No output' brpmdb = None erpmdb = None cmdline = None g_cmdline = table['Command Line'] if 'Command Line' in table else None retcode = None g_retcode = table['Return-Code'] if 'Return-Code' in table else None for line in lines: if 'Begin rpmdb' in line: brpmdb = line.split(':') if 'End rpmdb' in line: erpmdb = line.split(':') if brpmdb and erpmdb: assert len(brpmdb) == len(erpmdb) == 3, 'Unexpected rpmdb version format' erpmdb = None if 'Return-Code' in line and g_retcode: retcode = line.split(':') assert len(retcode) == 2, 'Unexpected Return-Code format' rc = retcode[1].strip() assert rc == g_retcode, 'Return-Code "{}" not matched by "{}"'.format(rc, g_retcode) if 'Command Line' in line and g_cmdline: cmdline = line.split(':') assert len(cmdline) == 2, 'Unexpected Command Line format' cmd = cmdline[1].strip() assert cmd == g_cmdline, 'Command Line "{}" not matched by "{}"'.format(cmd, g_cmdline) assert brpmdb, 'Begin rpmdb version not found' if g_cmdline: assert cmdline, 'Command line not found' if g_retcode: assert retcode, 'Return-Code not found' for key in actions: if key in table: pkgs = pkgs_split(table[key]) for line in lines: for pkg in pkgs: if pkg in line and key in line: pkgs.remove(pkg) assert not pkgs, '"{}" not matched as "{}"'.format(pkgs[0], key)
def step_userinstalled_match(ctx): def pkgs_split(pkgs): for pkg in pkgs.split(","): yield pkg.strip() keys = ['Match', 'Not match'] table = table_utils.parse_kv_table(ctx, ['Action', 'Packages'], keys) step_i_run_command(ctx, 'dnf history userinstalled') text = getattr(ctx.cmd_result, 'stdout') match = table[keys[0]] if keys[0] in table else None notmatch = table[keys[1]] if keys[1] in table else None if match: for m in pkgs_split(match): # should be matched assert m in text, 'Package {} not matched as userinstalled'.format(m) if notmatch: for n in pkgs_split(notmatch): # should not be matched assert n not in text, 'Package {} matched as userinstalled'.format(m)
def step_rpmdb_changes_are(ctx): """ Compare saved by :ref:`When I save rpmdb` and current rpmdb's. Requires table with following headers: ======= ========== State Packages ======= ========== *State* is state of package ============ ============== ============= =============================== State rpmdb before rpmdb after Additional comments ============ ============== ============= =============================== installed Not installed Installed Package has been installed removed Installed Not installed Package has been removed absent Not installed Not installed Package has not been installed unchanged Installed Installed Package was not changed reinstalled Installed Installed Same packages was reinstalled updated Installed Installed Package has been updated downgraded Installed Installed Package has been downgraded ============ ============== ============= =============================== For each *State* you can specify multiple *Packages* which are separated by comma. Examples: .. code-block:: gherkin Scenario: Detect reinstalled package Given saved rpmdb When I successfully run "dnf -y reinstall util-linux" Then rpmdb changes are | State | Packages | | reinstalled | util-linux | .. _automatic rules: **Automatic rules which are additionally applied**: - Packages except listed in table must not appear/disappear - Packages except listed in table classified as *unchanged* """ ctx.assertion.assertIsNotNone(ctx.rpmdb, "Save rpmdb before comparison") table = table_utils.parse_kv_table(ctx, HEADINGS_RPMDB, rpm_utils.State) ctx.wipe_rpmdb = True rpmdb = rpm_utils.get_rpmdb() problems = [] def unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post): problems.append("Package {pkg!r} was supposed to be " "{expected_state!r}, but has been {state!r} " "({pkg_pre!r} -> {pkg_post!r})".format( pkg=pkg, state=state.value, expected_state=expected_state.value, pkg_pre=rpm_utils.hdr2nevra(pkg_pre), pkg_post=rpm_utils.hdr2nevra(pkg_post))) def pkgs_split(pkgs): for pkg in pkgs.split(","): yield pkg.strip() # Let's check what user has requested in table for expected_state, packages in table.items(): for pkg in pkgs_split(packages): pkg_pre = rpm_utils.find_pkg(ctx.rpmdb, pkg) if pkg_pre: ctx.rpmdb.remove(pkg_pre) pkg_post = rpm_utils.find_pkg(rpmdb, pkg) if pkg_post: rpmdb.remove(pkg_post) state = rpm_utils.analyze_state(pkg_pre, pkg_post) if state != expected_state: unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post) # Let's check if NEVRAs are still same def rpmdb2nevra(rpmdb): for hdr in rpmdb: yield rpm_utils.hdr2nevra(hdr) six.assertCountEqual(ctx.assertion, rpmdb2nevra(ctx.rpmdb), rpmdb2nevra(rpmdb)) # Even if we have same NEVRAs packages can be different or reinstalled for pkg_pre, pkg_post in zip(ctx.rpmdb, rpmdb): state = rpm_utils.analyze_state(pkg_pre, pkg_post) expected_state = rpm_utils.State.unchanged # At this point pkg_pre and pkg_post should have same name pkg = pkg_pre["name"].decode() if state != expected_state: unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post) assert not problems, "\n{!s}".format("\n".join(problems))
def step_rpmdb_changes_are(ctx): """ Compare saved by :ref:`When I save rpmdb` and current rpmdb's. Requires table with following headers: ======= ========== State Packages ======= ========== *State* is state of package ============ ============== ============= =============================== State rpmdb before rpmdb after Additional comments ============ ============== ============= =============================== installed Not installed Installed Package has been installed removed Installed Not installed Package has been removed absent Not installed Not installed Package has not been installed unchanged Installed Installed Package was not changed reinstalled Installed Installed Same packages was reinstalled updated Installed Installed Package has been updated downgraded Installed Installed Package has been downgraded ignored - - Package will be ignored ============ ============== ============= =============================== For each *State* you can specify multiple *Packages* which are separated by comma. For the *ignored* state you can use Unix shell-style wildcard to cover multiple package names. Packages with state explicitely stated won't be ignored even if they matches pattern. Examples: .. code-block:: gherkin Scenario: Detect reinstalled package When I save rpmdb And I successfully run "dnf -y reinstall util-linux" Then rpmdb changes are | State | Packages | | reinstalled | util-linux | .. code-block:: gherkin Scenario: Detect exact version When I save rpmdb And I successfully run "dnf -y update util-linux" Then rpmdb changes are | State | Packages | | updated | util-linux/2.29.0 | .. _automatic rules: **Automatic rules which are additionally applied**: - Packages except listed in table must not appear/disappear - Packages except listed in table classified as *unchanged* """ ctx.assertion.assertIsNotNone(ctx.rpmdb, "Save rpmdb before comparison") table = table_utils.parse_kv_table(ctx, HEADINGS_RPMDB, rpm_utils.State) ctx.wipe_rpmdb = True rpmdb = rpm_utils.get_rpmdb() problems = [] def unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post): problems.append("Package {pkg!r} was supposed to be " "{expected_state!r}, but has been {state!r} " "({pkg_pre!r} -> {pkg_post!r})".format( pkg=pkg, state=state.value, expected_state=expected_state.value, pkg_pre=rpm_utils.hdr2nevra(pkg_pre), pkg_post=rpm_utils.hdr2nevra(pkg_post))) def pkgs_split(pkgs): for pkg in pkgs.split(","): yield pkg.strip() # Let's check what user has requested in table ignore_list = "" for expected_state, packages in table.items(): if expected_state == rpm_utils.State.ignored: ignore_list = packages else: for pkg in pkgs_split(packages): pkg_pre = rpm_utils.find_pkg(ctx.rpmdb, pkg, only_by_name=True) if pkg_pre: ctx.rpmdb.remove(pkg_pre) pkg_post = rpm_utils.find_pkg(rpmdb, pkg) if pkg_post: rpmdb.remove(pkg_post) state = rpm_utils.analyze_state(pkg_pre, pkg_post) if state != expected_state: if state == State.unchanged and expected_state == State.reinstalled: # workaround for unchanged rpmdb timestamp text = getattr(ctx.cmd_result, 'stdout') if "Reinstalling : {}".format(pkg) in text: continue unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post) # Now exclude packages matching regexp pattern in the ignore_list def filter_rpmdb(pkgs, filters): # remove packages matching any filter remove = [] for pkg in pkgs: for f in filters: if f and fnmatch.fnmatchcase(pkg.name, f): remove.append(pkg) break # no need to process additional filters for pkg in remove: pkgs.remove(pkg) filter_rpmdb(rpmdb, list(pkgs_split(ignore_list))) filter_rpmdb(ctx.rpmdb, list(pkgs_split(ignore_list))) # Let's check if NEVRAs are still same def rpmdb2nevra(rpmdb): for hdr in rpmdb: yield rpm_utils.hdr2nevra(hdr) six.assertCountEqual(ctx.assertion, rpmdb2nevra(ctx.rpmdb), rpmdb2nevra(rpmdb)) # Even if we have same NEVRAs packages can be different or reinstalled for pkg_pre, pkg_post in zip(ctx.rpmdb, rpmdb): state = rpm_utils.analyze_state(pkg_pre, pkg_post) expected_state = rpm_utils.State.unchanged # At this point pkg_pre and pkg_post should have same name pkg = pkg_pre["name"].decode() if state != expected_state: unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post) assert not problems, "\n{!s}".format("\n".join(problems))
def step_rpmdb_changes_are(ctx): """ Compare saved by :ref:`When I save rpmdb` and current rpmdb's. Requires table with following headers: ======= ========== State Packages ======= ========== *State* is state of package ============ ============== ============= =============================== State rpmdb before rpmdb after Additional comments ============ ============== ============= =============================== installed Not installed Installed Package has been installed removed Installed Not installed Package has been removed absent Not installed Not installed Package has not been installed unchanged Installed Installed Package was not changed reinstalled Installed Installed Same packages was reinstalled updated Installed Installed Package has been updated downgraded Installed Installed Package has been downgraded ignored - - Package will be ignored ============ ============== ============= =============================== For each *State* you can specify multiple *Packages* which are separated by comma. For the *ignored* state you can use Unix shell-style wildcard to cover multiple package names. Packages with state explicitely stated won't be ignored even if they matches pattern. Examples: .. code-block:: gherkin Scenario: Detect reinstalled package When I save rpmdb And I successfully run "dnf -y reinstall util-linux" Then rpmdb changes are | State | Packages | | reinstalled | util-linux | .. code-block:: gherkin Scenario: Detect exact version When I save rpmdb And I successfully run "dnf -y update util-linux" Then rpmdb changes are | State | Packages | | updated | util-linux/2.29.0 | .. _automatic rules: **Automatic rules which are additionally applied**: - Packages except listed in table must not appear/disappear - Packages except listed in table classified as *unchanged* """ ctx.assertion.assertIsNotNone(ctx.rpmdb, "Save rpmdb before comparison") table = table_utils.parse_kv_table(ctx, HEADINGS_RPMDB, rpm_utils.State) ctx.wipe_rpmdb = True rpmdb = rpm_utils.get_rpmdb() problems = [] def unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post): problems.append("Package {pkg!r} was supposed to be " "{expected_state!r}, but has been {state!r} " "({pkg_pre!r} -> {pkg_post!r})".format( pkg=pkg, state=state.value, expected_state=expected_state.value, pkg_pre=rpm_utils.hdr2nevra(pkg_pre), pkg_post=rpm_utils.hdr2nevra(pkg_post))) def pkgs_split(pkgs): for pkg in pkgs.split(","): yield pkg.strip() # Let's check what user has requested in table ignore_list = "" for expected_state, packages in table.items(): if expected_state == rpm_utils.State.ignored: ignore_list = packages else: for pkg in pkgs_split(packages): pkg_pre = rpm_utils.find_pkg(ctx.rpmdb, pkg, only_by_name=True) if pkg_pre and rpm_utils.is_installonly_pkg(pkg_pre): # installonly package should match NVR pkg_pre = rpm_utils.find_pkg(ctx.rpmdb, pkg, only_by_name=False) if pkg_pre: ctx.rpmdb.remove(pkg_pre) pkg_post = rpm_utils.find_pkg(rpmdb, pkg) if pkg_post: rpmdb.remove(pkg_post) state = rpm_utils.analyze_state(pkg_pre, pkg_post) if state != expected_state: if state == State.unchanged and expected_state == State.reinstalled: # workaround for unchanged rpmdb timestamp text = getattr(ctx.cmd_result, 'stdout') if "Reinstalling : {}".format(pkg) in text: continue unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post) # Now exclude packages matching regexp pattern in the ignore_list def filter_rpmdb(pkgs, filters): # remove packages matching any filter remove = [] for pkg in pkgs: for f in filters: if f and fnmatch.fnmatchcase(pkg.name, f): remove.append(pkg) break # no need to process additional filters for pkg in remove: pkgs.remove(pkg) filter_rpmdb(rpmdb, list(pkgs_split(ignore_list))) filter_rpmdb(ctx.rpmdb, list(pkgs_split(ignore_list))) # Let's check if NEVRAs are still same def rpmdb2nevra(rpmdb): for hdr in rpmdb: yield rpm_utils.hdr2nevra(hdr) six.assertCountEqual(ctx.assertion, rpmdb2nevra(ctx.rpmdb), rpmdb2nevra(rpmdb)) # Even if we have same NEVRAs packages can be different or reinstalled for pkg_pre, pkg_post in zip(ctx.rpmdb, rpmdb): state = rpm_utils.analyze_state(pkg_pre, pkg_post) expected_state = rpm_utils.State.unchanged # At this point pkg_pre and pkg_post should have same name pkg = pkg_pre["name"].decode() if state != expected_state: unexpected_state(pkg, state, expected_state, pkg_pre, pkg_post) assert not problems, "\n{!s}".format("\n".join(problems))
def step_gpg_key(ctx, name_real): """ Generates for the root user GPG key with a given identity, a.k.a. the Name-Real attribute. GPG key attributes can be optionally specified using the table with following headers: ======= ========= Tag Value ======= ========= Supported GPG key attrubutes are: ============= =============== Tag Default value ============= =============== Key-Type RSA Key-Length 2048 Subkey-Type <not present> Subkey-Length <not present> Name-Comment No Comment Name-Email dnf@noreply Expire-Date 0 ============================= .. note:: GPG key configuration is saved in a file /root/${Name-Real}.keyconf respective public key is exported to a file /root/${Name-Real}.pubkey Examples: .. code-block:: gherkin Feature: Package signatures Scenario: Setup repository with signed packages Given GPG key "James Bond" And GPG key "James Bond" imported in rpm database And repository "TestRepo" with packages signed by "James Bond" | Package | Tag | Value | | TestA | | | """ if ctx.table: # additional GPG key configuration listed in the table GPGKEY_HEADINGS = ['Tag', 'Value'] GPGKEY_TAGS = ['Key-Type', 'Key-Length', 'Subkey-Type', 'Subkey-Length', 'Name-Comment', 'Name-Email', 'Expire-Date'] gpgkey_conf_table = table_utils.parse_kv_table(ctx, GPGKEY_HEADINGS, GPGKEY_TAGS) else: # no table present gpgkey_conf_table = {} template = JINJA_ENV.from_string(GPGKEY_CONF_TMPL) settings = {k.lower().replace('-', '_'): v for k, v in gpgkey_conf_table.items()} gpgkey_conf = template.render(name_real=name_real, **settings) # write gpgkey configuration to a file fpath = GPGKEY_FILEPATH_TMPL.format(name_real, "keyconf") with open(fpath, 'w') as fw: fw.write(gpgkey_conf) # generate the GPG key gpgbin = which("gpg2") cmd = "{!s} --batch --gen-key '{!s}'".format(gpgbin, fpath) step_i_successfully_run_command(ctx, cmd) # export the public key cmd = "{!s} --export --armor '{!s}'".format(gpgbin, name_real) step_i_successfully_run_command(ctx, cmd) fpath = GPGKEY_FILEPATH_TMPL.format(name_real, "pubkey") with open(fpath, 'w') as fw: fw.write(ctx.cmd_result.stdout)
def step_gpg_key(ctx, name_real): """ Generates for the root user GPG key with a given identity, a.k.a. the Name-Real attribute. GPG key attributes can be optionally specified using the table with following headers: ======= ========= Tag Value ======= ========= Supported GPG key attrubutes are: ============= =============== Tag Default value ============= =============== Key-Type RSA Key-Length 2048 Subkey-Type <not present> Subkey-Length <not present> Name-Comment No Comment Name-Email dnf@noreply Expire-Date 0 ============================= .. note:: GPG key configuration is saved in a file /root/${Name-Real}.keyconf respective public key is exported to a file /root/${Name-Real}.pubkey Examples: .. code-block:: gherkin Feature: Package signatures Scenario: Setup repository with signed packages Given GPG key "James Bond" And GPG key "James Bond" imported in rpm database And repository "TestRepo" with packages signed by "James Bond" | Package | Tag | Value | | TestA | | | """ if ctx.table: # additional GPG key configuration listed in the table GPGKEY_HEADINGS = ['Tag', 'Value'] GPGKEY_TAGS = [ 'Key-Type', 'Key-Length', 'Subkey-Type', 'Subkey-Length', 'Name-Comment', 'Name-Email', 'Expire-Date' ] gpgkey_conf_table = table_utils.parse_kv_table(ctx, GPGKEY_HEADINGS, GPGKEY_TAGS) else: # no table present gpgkey_conf_table = {} template = JINJA_ENV.from_string(GPGKEY_CONF_TMPL) settings = { k.lower().replace('-', '_'): v for k, v in gpgkey_conf_table.items() } gpgkey_conf = template.render(name_real=name_real, **settings) # write gpgkey configuration to a file fpath = GPGKEY_FILEPATH_TMPL.format(name_real, "keyconf") with open(fpath, 'w') as fw: fw.write(gpgkey_conf) # generate the GPG key gpgbin = which("gpg2") cmd = "{!s} --batch --gen-key '{!s}'".format(gpgbin, fpath) step_i_successfully_run_command(ctx, cmd) # export the public key cmd = "{!s} --export --armor '{!s}'".format(gpgbin, name_real) step_i_successfully_run_command(ctx, cmd) fpath = GPGKEY_FILEPATH_TMPL.format(name_real, "pubkey") with open(fpath, 'w') as fw: fw.write(ctx.cmd_result.stdout)