def then_update_repository_with_packages(ctx, repository): """ Examples: Feature: Working with repositories Given I update http repository base with packages | Package | Tag | Value | | foo | | | Given I update http repository "updates" with packages | Package | Tag | Value | | foo | Version | 2.1 | | foo v3 | Version | 3.1 | """ packages = table_utils.parse_skv_table(ctx, HEADINGS_REPO, PKG_TAGS, PKG_TAGS_REPEATING) rpmbuild = which("rpmbuild") ctx.assertion.assertIsNotNone(rpmbuild, "rpmbuild is required") createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") repodir = repo_utils.get_repo_dir(repository) tmpdir = repodir srpm_tmpdir = '{}-source'.format(tmpdir.rstrip('/')) template = JINJA_ENV.from_string(PKG_TMPL) for name, settings in packages.items(): name = name.split()[0] # cut-off the pkg name _suffix_ to allow defining multiple package versions disttag = "" if '/' in name: # using the module/pkgname notation, module would be placed to a disttag (module, name) = name.split('/', 1) disttag = ".{}".format(module) # before processing the template # lower all characters # replace '%' in Tag name with '_' # replace '(' in Tag name with '_' # delete all ')' in Tag settings = {k.lower().replace('%', '_').replace('(', '_').replace(')', ''): v for k, v in settings.items()} ctx.text = template.render(name=name, disttag=disttag, **settings) fname = "{!s}/{!s}.spec".format(tmpdir, name) step_a_file_filepath_with(ctx, fname) buildname = '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm' if 'arch' not in settings or settings['arch'] == 'noarch': cmd = "{!s} --define '_rpmdir {!s}' --define '_srcrpmdir {!s}' \ --define '_build_name_fmt {!s}' -ba {!s}" \ .format(rpmbuild, tmpdir, srpm_tmpdir, buildname, fname) else: cmd = "setarch {!s} {!s} --define '_rpmdir {!s}' --define '_srcrpmdir {!s}' \ --define '_build_name_fmt {!s}' --target {!s} -ba {!s}" \ .format(settings['arch'], rpmbuild, tmpdir, srpm_tmpdir, buildname, settings['arch'], fname) step_i_successfully_run_command(ctx, cmd) file_utils.set_dir_content_ownership(ctx, repodir, 'root') # change file ownership to root so we can change it cmd = "{!s} --update {!s}".format(createrepo, repodir) step_i_successfully_run_command(ctx, cmd) file_utils.set_dir_content_ownership(ctx, repodir) # restore file ownership
def step_an_ini_file_filepath_should_contain(ctx, filepath, extra_value_processing=False): """ Tests whether an INI file contain respective Section, Key, Value triples. Requires table with following headers: ========= ===== ======= Section Key Value ========= ===== ======= Examples: .. code-block:: gherkin Feature: Testing an INI file content Scenario: Check if repozitory Test was enabled Then an INI file "/etc/yum.repos.d/test.repo" should contain | Section | Key | Value | | Test | enabled | True | """ ini_table = table_utils.parse_skv_table(ctx, HEADINGS_INI) sections = ini_table.keys() conf = configparser.ConfigParser() conf.read(filepath) for section in sections: settings = ini_table[section] ctx.assertion.assertTrue( conf.has_section(section), "No such section '%s' in '%s'" % (section, filepath)) keys = settings.keys() for key in keys: ctx.assertion.assertTrue( conf.has_option(section, key), "No such option '%s' in section '%s' in '%s'" % (key, section, filepath)) value = settings[key] ini_value = conf.get(section, key) if not extra_value_processing: ctx.assertion.assertEqual(value, ini_value) else: # an extra processing is enabled if value.startswith('(set)'): # consider the value to be command or \n separated set of values value_set = [v.strip() for v in value[5:].split(",")] ini_value_set = [ v.strip() for v in ini_value.replace("\n", ",").split(",") ] if six.PY2: ctx.assertion.assertEqual(sorted(value_set), sorted(ini_value_set)) else: ctx.assertion.assertCountEqual(value_set, ini_value_set) else: # fallback ctx.assertion.assertEqual(value, ini_value)
def step_an_ini_file_filepath_modified_with(ctx, filepath): """ Similar to :ref:`Given an INI file "{filepath}" with`, but accepts table with modifications that should be made in the respective INI file. Requires table with following headers: ========= ===== ======= Section Key Value ========= ===== ======= Examples: .. code-block:: gherkin Feature: Modifying an INI file Scenario: Editing /etc/dnf/dnf.conf Given an INI file "/etc/dnf/dnf.conf" modified with | Section | Key | Value | | main | debuglevel | 1 | | | -gpgcheck | | .. note:: Section or Key prefixed with '-' results in the removal of the respective record. """ updates = table_utils.parse_skv_table(ctx, HEADINGS_INI) sections = list( updates.keys()) # convert to list as py3 returns an iterator sections.sort() # sort so we have removal first conf = configparser.ConfigParser() conf.read(filepath) for section in sections: settings = updates[section] if section.startswith("-"): section = section[1:] ctx.assertion.assertTrue( conf.remove_section(section), "No such section '%s' in '%s'" % (section, filepath)) else: if not conf.has_section(section): conf.add_section(section) keys = settings.keys() for key in keys: if key.startswith("-"): key = key[1:] ctx.assertion.assertTrue( conf.remove_option(section, key), "No such key '%s' in section '%s' in '%s'" % (key, section, filepath)) else: conf.set(section, key, settings[key]) file_utils.create_file_with_contents(filepath, conf)
def step_an_ini_file_filepath_modified_with(ctx, filepath): """ Similar to :ref:`Given an INI file "{filepath}" with`, but accepts table with modifications that should be made in the respective INI file. Requires table with following headers: ========= ===== ======= Section Key Value ========= ===== ======= Examples: .. code-block:: gherkin Feature: Modifying an INI file Scenario: Editing /etc/dnf/dnf.conf Given an INI file "/etc/dnf/dnf.conf" modified with | Section | Key | Value | | main | debuglevel | 1 | | | -gpgcheck | | .. note:: Section or Key prefixed with '-' results in the removal of the respective record. """ updates = table_utils.parse_skv_table(ctx, HEADINGS_INI) sections = list(updates.keys()) # convert to list as py3 returns an iterator sections.sort() # sort so we have removal first conf = configparser.ConfigParser() conf.read(filepath) for section in sections: settings = updates[section] if section.startswith("-"): section = section[1:] ctx.assertion.assertTrue(conf.remove_section(section), "No such section '%s' in '%s'" % (section, filepath)) else: if not conf.has_section(section): conf.add_section(section) keys = settings.keys() for key in keys: if key.startswith("-"): key = key[1:] ctx.assertion.assertTrue(conf.remove_option(section, key), "No such key '%s' in section '%s' in '%s'" % (key, section, filepath)) else: conf.set(section, key, settings[key]) file_utils.create_file_with_contents(filepath, conf)
def step_an_ini_file_filepath_should_contain(ctx, filepath, extra_value_processing=False): """ Tests whether an INI file contain respective Section, Key, Value triples. Requires table with following headers: ========= ===== ======= Section Key Value ========= ===== ======= Examples: .. code-block:: gherkin Feature: Testing an INI file content Scenario: Check if repozitory Test was enabled Then an INI file "/etc/yum.repos.d/test.repo" should contain | Section | Key | Value | | Test | enabled | True | """ ini_table = table_utils.parse_skv_table(ctx, HEADINGS_INI) sections = ini_table.keys() conf = configparser.ConfigParser() conf.read(filepath) for section in sections: settings = ini_table[section] ctx.assertion.assertTrue(conf.has_section(section), "No such section '%s' in '%s'" % (section, filepath)) keys = settings.keys() for key in keys: ctx.assertion.assertTrue(conf.has_option(section, key), "No such option '%s' in section '%s' in '%s'" % (key, section, filepath)) value = settings[key] ini_value = conf.get(section, key) if not extra_value_processing: ctx.assertion.assertEqual(value, ini_value) else: # an extra processing is enabled if value.startswith('(set)'): # consider the value to be command or \n separated set of values value_set = [v.strip() for v in value[5:].split(",")] ini_value_set = [v.strip() for v in ini_value.replace("\n", ",").split(",")] if six.PY2: ctx.assertion.assertEqual(sorted(value_set), sorted(ini_value_set)) else: ctx.assertion.assertCountEqual(value_set, ini_value_set) else: # fallback ctx.assertion.assertEqual(value, ini_value)
def step_an_ini_file_filepath_with(ctx, filepath): """ Same as :ref:`Given a file "{filepath}" with`, but accepts table with sections/keys/values structure to construct INI file. Requires table with following headers: ========= ===== ======= Section Key Value ========= ===== ======= Examples: .. code-block:: gherkin Feature: Creating INI files Scenario: Empty section Given an INI file "/etc/dnf/plugins/debuginfo-install.conf" with | Section | Key | Value | | main | | | Scenario: Section with one key Given an INI file "/etc/dnf/plugins/debuginfo-install.conf" with | Section | Key | Value | | main | enabled | False | Scenarion: Section with multiple keys Given an INI file "/etc/yum.repos.d/mnt.repo" with | Section | Key | Value | | mnt | name | Mounted repo - $basearch | | | baseurl | file:///mnt/repo/$basearch | | | enabled | True | | | gpgcheck | False | """ sections = table_utils.parse_skv_table(ctx, HEADINGS_INI) conf = configparser.ConfigParser() for section, settings in sections.items(): if six.PY2: conf.add_section(section) for key, value in settings.items(): conf.set(section, key, value) else: conf[section] = settings file_utils.create_file_with_contents(filepath, conf)
def given_repository_with_packages(ctx, enabled, rtype, repository, gpgkey=None): """ Builds dummy packages, creates repo and *.repo* file. Supported repo types are http, https, ftp or local (default). Supported architectures are x86_64, i686 and noarch (default). .. note:: Along with the repository also *-source repository with src.rpm packages is built. The repository is disabled. .. note:: *https* repositories are configured to use certificates at following locations: /etc/pki/tls/certs/testcerts/ca/cert.pem /etc/pki/tls/certs/testcerts/client/key.pem /etc/pki/tls/certs/testcerts/client/cert.pem .. note:: Requires *rpmbuild* and *createrepo_c*. Requires table with following headers: ========= ===== ======= Package Tag Value ========= ===== ======= *Tag* is tag in RPM. Supported ones are: ================== =============== Tag Default value ================== =============== Summary Empty Version 1 Release 1 Arch x86_64 License Public Domain BuildRequires [] Requires [] Recommends [] Suggests [] Supplements [] Enhances [] Requires(pretrans) [] Requires(pre) [] Requires(post) [] Requires(preun) [] Obsoletes [] Provides [] Conflicts [] %pretrans Empty %pre Empty %post Empty %preun Empty %postun Empty %posttrans Empty ================== =============== All packages are built during step execution. .. note:: *BuildRequires* are ignored for build-time (*rpmbuild* is executed with ``--nodeps`` option). If there is a space character in the package name only the preceding part is used. .. note:: Scriptlets such as *%pre* can be listed multiple time so that the entering of a multi-line script is more comfortable. Examples: .. code-block:: gherkin Feature: Working with repositories Background: Repository base with dummy package Given http repository base with packages | Package | Tag | Value | | foo | | | Scenario: Installing dummy package from background When I enable repository base Then I successfully run "dnf -y install foo" Scenario: Creating repository with multiple package versions Given http repository "updates" with packages | Package | Tag | Value | | foo | Version | 2.0 | | foo v3 | Version | 3.0 | Scenario: Creating a package with %pre scriptlet failing Given http repository "more_updates" with packages | Package | Tag | Value | | foo | Version | 4.0 | | | %pre | exit 1 | """ packages = table_utils.parse_skv_table(ctx, HEADINGS_REPO, PKG_TAGS, PKG_TAGS_REPEATING) rpmbuild = which("rpmbuild") ctx.assertion.assertIsNotNone(rpmbuild, "rpmbuild is required") createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") if rtype == 'http' or rtype == 'https': tmpdir = tempfile.mkdtemp(dir='/var/www/html') repopath = os.path.join('localhost', os.path.basename(tmpdir)) elif rtype == 'ftp': tmpdir = tempfile.mkdtemp(dir='/var/ftp/pub') repopath = os.path.join('localhost/pub', os.path.basename(tmpdir)) else: tmpdir = tempfile.mkdtemp() repopath = tmpdir srpm_tmpdir = '{}-source'.format(tmpdir.rstrip('/')) srpm_repopath = '{}-source'.format(repopath.rstrip('/')) os.mkdir(srpm_tmpdir) # create a directory for src.rpm pkgs template = JINJA_ENV.from_string(PKG_TMPL) for name, settings in packages.items(): name = name.split( )[0] # cut-off the pkg name _suffix_ to allow defining multiple package versions # before processing the template # lower all characters # replace '%' in Tag name with '_' # replace '(' in Tag name with '_' # delete all ')' in Tag settings = { k.lower().replace('%', '_').replace('(', '_').replace(')', ''): v for k, v in settings.items() } ctx.text = template.render(name=name, **settings) fname = "{!s}/{!s}.spec".format(tmpdir, name) step_a_file_filepath_with(ctx, fname) buildname = '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm' if 'arch' not in settings or settings['arch'] == 'noarch': cmd = "{!s} --define '_rpmdir {!s}' --define '_srcrpmdir {!s}' --define '_build_name_fmt {!s}' -ba {!s}".format( rpmbuild, tmpdir, srpm_tmpdir, buildname, fname) else: cmd = "setarch {!s} {!s} --define '_rpmdir {!s}' --define '_srcrpmdir {!s}' --define '_build_name_fmt {!s}' --target {!s} -ba {!s}".format( settings['arch'], rpmbuild, tmpdir, srpm_tmpdir, buildname, settings['arch'], fname) step_i_successfully_run_command(ctx, cmd) if gpgkey: # sign all rpms built rpmsign = which("rpmsign") rpms = glob.glob("{!s}/*.rpm".format(tmpdir)) srpms = glob.glob("{!s}/*.rpm".format(srpm_tmpdir)) cmd = "{!s} --addsign --key-id '{!s}' {!s} {!s}".format( rpmsign, gpgkey, ' '.join(rpms), ' '.join(srpms)) step_i_successfully_run_command(ctx, cmd) cmd = "{!s} {!s}".format(createrepo, tmpdir) step_i_successfully_run_command(ctx, cmd) cmd = "{!s} {!s}".format(createrepo, srpm_tmpdir) step_i_successfully_run_command(ctx, cmd) # set proper directory content ownership file_utils.set_dir_content_ownership(ctx, tmpdir) file_utils.set_dir_content_ownership(ctx, srpm_tmpdir) repofile = REPO_TMPL.format(repository) ctx.table = Table(HEADINGS_INI) ctx.table.add_row([repository, "name", repository]) ctx.table.add_row(["", "enabled", six.text_type(enabled)]) ctx.table.add_row(["", "baseurl", "{!s}://{!s}".format(rtype, repopath)]) if gpgkey: ctx.table.add_row(["", "gpgcheck", "True"]) else: ctx.table.add_row(["", "gpgcheck", "False"]) if rtype == 'https': ctx.table.add_row( ["", "sslcacert", "/etc/pki/tls/certs/testcerts/ca/cert.pem"]) ctx.table.add_row([ "", "sslclientkey", "/etc/pki/tls/certs/testcerts/client/key.pem" ]) ctx.table.add_row([ "", "sslclientcert", "/etc/pki/tls/certs/testcerts/client/cert.pem" ]) step_an_ini_file_filepath_with(ctx, repofile) # create -source repository too srpm_repository = '{}-source'.format(repository) repofile = REPO_TMPL.format(srpm_repository) ctx.table = Table(HEADINGS_INI) ctx.table.add_row([srpm_repository, "name", srpm_repository]) ctx.table.add_row(["", "enabled", "False"]) ctx.table.add_row( ["", "baseurl", "{!s}://{!s}".format(rtype, srpm_repopath)]) if gpgkey: ctx.table.add_row(["", "gpgcheck", "True"]) else: ctx.table.add_row(["", "gpgcheck", "False"]) if rtype == 'https': ctx.table.add_row( ["", "sslcacert", "/etc/pki/tls/certs/testcerts/ca/cert.pem"]) ctx.table.add_row([ "", "sslclientkey", "/etc/pki/tls/certs/testcerts/client/key.pem" ]) ctx.table.add_row([ "", "sslclientcert", "/etc/pki/tls/certs/testcerts/client/cert.pem" ]) step_an_ini_file_filepath_with(ctx, repofile)
def given_repository_with_packages(ctx, rtype, repository): """ Builds dummy packages, creates repo and *.repo* file. Supported repo types are http, ftp or local (default). Supported architectures are x86_64, i686 and noarch (default). .. note:: Requires *rpmbuild* and *createrepo_c*. Requires table with following headers: ========= ===== ======= Package Tag Value ========= ===== ======= *Tag* is tag in RPM. Supported ones are: ============= =============== Tag Default value ============= =============== Summary Empty Version 1 Release 1 Arch x86_64 License Public Domain BuildRequires [] Requires [] Obsoletes [] Provides [] Conflicts [] ============= =============== All packages are built during step execution. .. note:: *BuildRequires* are ignored for build-time (*rpmbuild* is executed with ``--nodeps`` option). Examples: .. code-block:: gherkin Feature: Working with repositories Background: Repository base with dummy package Given http repository base with packages | Package | Tag | Value | | foo | | | Scenario: Installing dummy package from background When I enable repository base Then I successfully run "dnf -y install foo" """ packages = table_utils.parse_skv_table(ctx, HEADINGS_REPO, PKG_TAGS, PKG_TAGS_REPEATING) rpmbuild = which("rpmbuild") ctx.assertion.assertIsNotNone(rpmbuild, "rpmbuild is required") createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") if rtype == 'http': tmpdir = tempfile.mkdtemp(dir='/var/www/html') repopath = os.path.join('localhost', os.path.basename(tmpdir)) elif rtype == 'ftp': tmpdir = tempfile.mkdtemp(dir='/var/ftp/pub') repopath = os.path.join('localhost/pub', os.path.basename(tmpdir)) else: tmpdir = tempfile.mkdtemp() repopath = tmpdir template = JINJA_ENV.from_string(PKG_TMPL) for name, settings in packages.items(): settings = {k.lower(): v for k, v in settings.items()} ctx.text = template.render(name=name, **settings) fname = "{!s}/{!s}.spec".format(tmpdir, name) step_a_file_filepath_with(ctx, fname) if 'arch' not in settings or settings['arch'] == 'noarch': cmd = "{!s} --define '_rpmdir {!s}' -bb {!s}".format( rpmbuild, tmpdir, fname) else: cmd = "setarch {!s} {!s} --define '_rpmdir {!s}' --target {!s} -bb {!s}".format( settings['arch'], rpmbuild, tmpdir, settings['arch'], fname) step_i_successfully_run_command(ctx, cmd) cmd = "{!s} {!s}".format(createrepo, tmpdir) step_i_successfully_run_command(ctx, cmd) # set proper directory content ownership file_utils.set_dir_content_ownership(ctx, tmpdir) repofile = REPO_TMPL.format(repository) ctx.table = Table(HEADINGS_INI) ctx.table.add_row([repository, "name", repository]) ctx.table.add_row(["", "enabled", "False"]) ctx.table.add_row(["", "gpgcheck", "False"]) ctx.table.add_row(["", "baseurl", "{!s}://{!s}".format(rtype, repopath)]) step_an_ini_file_filepath_with(ctx, repofile)
def given_package_groups_defined_in_repository(ctx, repository): """ For a given repository creates comps.xml file with described package groups and recreates the repo .. note:: Requires *createrepo_c* and the repo to be already created. Requires table with following headers: ========= ===== ======= Group Tag Value ========= ===== ======= *Tag* is describing characteristics of the respective package group.Supported tags are: ============== =============== Tag Default value ============== =============== is_default false is_uservisible true description "" mandatory [] default [] optional [] conditional [] ============== =============== Examples: .. code-block:: gherkin Feature: Installing a package group @setup Scenario: Repository base with package group minimal Given repository "base" with packages | Package | Tag | Value | | foo | | | | bar | | | | baz | | | | qux | | | And package groups defined in repository "base" | Group | Tag | Value | | minimal | mandatory | foo | | | default | bar | | | conditional | baz qux | Scenario: Installing package group from background When I enable repository "base" Then I successfully run "dnf -y group install minimal" .. note:: Conditional packages are described in a form PKG REQUIREDPKG """ HEADINGS_GROUP = ['Group', 'Tag', 'Value'] GROUP_TAGS_REPEATING = ['mandatory', 'default', 'optional', 'conditional'] GROUP_TAGS = ['is_default', 'is_uservisible', 'description'] + GROUP_TAGS_REPEATING pkg_groups = table_utils.parse_skv_table(ctx, HEADINGS_GROUP, GROUP_TAGS, GROUP_TAGS_REPEATING) createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") # prepare the comps.xml comps_xml = COMPS_PREFIX template = JINJA_ENV.from_string(COMPS_TMPL) for name, settings in pkg_groups.items(): settings = {k.lower(): v for k, v in settings.items()} comps_xml += template.render(name=name, **settings) comps_xml += COMPS_SUFFIX # save comps.xml and recreate the repo repodir = repo_utils.get_repo_dir(repository) with open(os.path.join(repodir, "comps.xml"), "w") as f_comps: f_comps.write(comps_xml) file_utils.set_dir_content_ownership(ctx, repodir, 'root') # change file ownership to root so we can change it cmd = "{!s} -g comps.xml --update {!s}".format(createrepo, repodir) step_i_successfully_run_command(ctx, cmd) file_utils.set_dir_content_ownership(ctx, repodir) # restore file ownership
def step_updateinfo_defined_in_repository(ctx, repository): """ For a given repository creates updateinfo.xml file with described updates and recreates the repo .. note:: Requires *modifyrepo_c* and the repo to be already created. Requires table with following headers: ==== ===== ======= Id Tag Value ==== ===== ======= *Tag* is describing attributes of the respective update. Supported tags are: ============ ========================= Tag Default value ============ ========================= Title Default title of Id Type security Description Default description of Id Summary Default summary of Id Severity Low Solution Default solution of Id Rights nobody Issued 2017-01-01 00:00:01 Updated 2017-01-01 00:00:01 Reference none Package none ============ ========================= Examples: .. code-block:: gherkin Feature: Defining updateinfo in a repository @setup Scenario: Repository base with updateinfo defined Given repository "base" with packages | Package | Tag | Value | | foo | Version | 2 | | bar | Version | 2 | And updateinfo defined in repository "base" | Id | Tag | Value | | RHSA-2017-001 | Title | foo bar security update | | | Type | security | | | Description | Fixes buffer overflow | | | Summary | Critical bug is fixed | | | Severity | Critical | | | Solution | Update to the new version | | | Rights | Copyright 2017 Baz Inc | | | Reference | CVE-2017-0001 | | | Reference | BZ123456 | | | Package | foo-2 | | | Package | bar-2 | .. note:: Specifying Version or Release in Package tag is not necessary, however when multiple RPMs matches the string the last one from the sorted list is used. """ HEADINGS_GROUP = ['Id', 'Tag', 'Value'] UPDATEINFO_TAGS_REPEATING = ['Package', 'Reference'] UPDATEINFO_TAGS = ['Title', 'Type', 'Description', 'Solution', 'Summary', 'Severity', 'Rights', 'Issued', 'Updated'] + \ UPDATEINFO_TAGS_REPEATING updateinfo_table = table_utils.parse_skv_table(ctx, HEADINGS_GROUP, UPDATEINFO_TAGS, UPDATEINFO_TAGS_REPEATING) # verify that modifyrepo_c is present modifyrepo = which("modifyrepo_c") ctx.assertion.assertIsNotNone(modifyrepo, "modifyrepo_c is required") # prepare updateinfo.xml content repodir = repo_utils.get_repo_dir(repository) updateinfo_xml = repo_utils.get_updateinfo_xml(repository, updateinfo_table) # save it to the updateinfo.xml file and recreate the repodata tmpdir = tempfile.mkdtemp() with open(os.path.join(tmpdir, "updateinfo.xml"), 'w') as fw: fw.write(updateinfo_xml) file_utils.set_dir_content_ownership(ctx, repodir, 'root') # change file ownership to root so we can change it cmd = "{!s} {!s} {!s}".format(modifyrepo, os.path.join(tmpdir, 'updateinfo.xml'), os.path.join(repodir, 'repodata')) step_i_successfully_run_command(ctx, cmd) file_utils.set_dir_content_ownership(ctx, repodir) # restore file ownership
def given_repository_with_packages(ctx, rtype, repository, gpgkey=None): """ Builds dummy packages, creates repo and *.repo* file. Supported repo types are http, https, ftp or local (default). Supported architectures are x86_64, i686 and noarch (default). .. note:: *https* repositories are configured to use certificates at following locations: /etc/pki/tls/certs/testcerts/ca/cert.pem /etc/pki/tls/certs/testcerts/client/key.pem /etc/pki/tls/certs/testcerts/client/cert.pem .. note:: Requires *rpmbuild* and *createrepo_c*. Requires table with following headers: ========= ===== ======= Package Tag Value ========= ===== ======= *Tag* is tag in RPM. Supported ones are: ============= =============== Tag Default value ============= =============== Summary Empty Version 1 Release 1 Arch x86_64 License Public Domain BuildRequires [] Requires [] Obsoletes [] Provides [] Conflicts [] ============= =============== All packages are built during step execution. .. note:: *BuildRequires* are ignored for build-time (*rpmbuild* is executed with ``--nodeps`` option). Examples: .. code-block:: gherkin Feature: Working with repositories Background: Repository base with dummy package Given http repository base with packages | Package | Tag | Value | | foo | | | Scenario: Installing dummy package from background When I enable repository base Then I successfully run "dnf -y install foo" """ packages = table_utils.parse_skv_table(ctx, HEADINGS_REPO, PKG_TAGS, PKG_TAGS_REPEATING) rpmbuild = which("rpmbuild") ctx.assertion.assertIsNotNone(rpmbuild, "rpmbuild is required") createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") if rtype == 'http' or rtype == 'https': tmpdir = tempfile.mkdtemp(dir='/var/www/html') repopath = os.path.join('localhost', os.path.basename(tmpdir)) elif rtype == 'ftp': tmpdir = tempfile.mkdtemp(dir='/var/ftp/pub') repopath = os.path.join('localhost/pub', os.path.basename(tmpdir)) else: tmpdir = tempfile.mkdtemp() repopath = tmpdir template = JINJA_ENV.from_string(PKG_TMPL) for name, settings in packages.items(): settings = {k.lower(): v for k, v in settings.items()} ctx.text = template.render(name=name, **settings) fname = "{!s}/{!s}.spec".format(tmpdir, name) step_a_file_filepath_with(ctx, fname) buildname = '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm' if 'arch' not in settings or settings['arch'] == 'noarch': cmd = "{!s} --define '_rpmdir {!s}' --define '_build_name_fmt {!s}' -bb {!s}".format( rpmbuild, tmpdir, buildname, fname) else: cmd = "setarch {!s} {!s} --define '_rpmdir {!s}' --define '_build_name_fmt {!s}' --target {!s} -bb {!s}".format( settings['arch'], rpmbuild, tmpdir, buildname, settings['arch'], fname) step_i_successfully_run_command(ctx, cmd) if gpgkey: # sign all rpms built rpmsign = which("rpmsign") rpms = glob.glob("{!s}/*.rpm".format(tmpdir)) cmd = "{!s} --addsign --key-id '{!s}' {!s}".format(rpmsign, gpgkey, ' '.join(rpms)) step_i_successfully_run_command(ctx, cmd) cmd = "{!s} {!s}".format(createrepo, tmpdir) step_i_successfully_run_command(ctx, cmd) # set proper directory content ownership file_utils.set_dir_content_ownership(ctx, tmpdir) repofile = REPO_TMPL.format(repository) ctx.table = Table(HEADINGS_INI) ctx.table.add_row([repository, "name", repository]) ctx.table.add_row(["", "enabled", "False"]) ctx.table.add_row(["", "baseurl", "{!s}://{!s}".format(rtype, repopath)]) if gpgkey: ctx.table.add_row(["", "gpgcheck", "True"]) else: ctx.table.add_row(["", "gpgcheck", "False"]) if rtype == 'https': ctx.table.add_row(["", "sslcacert", "/etc/pki/tls/certs/testcerts/ca/cert.pem"]) ctx.table.add_row(["", "sslclientkey", "/etc/pki/tls/certs/testcerts/client/key.pem"]) ctx.table.add_row(["", "sslclientcert", "/etc/pki/tls/certs/testcerts/client/cert.pem"]) step_an_ini_file_filepath_with(ctx, repofile)
def given_repository_with_packages(ctx, enabled, rtype, repository, gpgkey=None): """ Builds dummy packages, creates repo and *.repo* file. Supported repo types are http, https, ftp or local (default). Supported architectures are x86_64, i686 and noarch (default). .. note:: Along with the repository also *-source repository with src.rpm packages is built. The repository is disabled. .. note:: *https* repositories are configured to use certificates at following locations: /etc/pki/tls/certs/testcerts/ca/cert.pem /etc/pki/tls/certs/testcerts/client/key.pem /etc/pki/tls/certs/testcerts/client/cert.pem .. note:: Requires *rpmbuild* and *createrepo_c*. Requires table with following headers: ========= ===== ======= Package Tag Value ========= ===== ======= *Tag* is tag in RPM. Supported ones are: ================== =============== Tag Default value ================== =============== Summary Empty Version 1 Release 1 Arch x86_64 License Public Domain BuildRequires [] Requires [] Recommends [] Suggests [] Supplements [] Enhances [] Requires(pretrans) [] Requires(pre) [] Requires(post) [] Requires(preun) [] Obsoletes [] Provides [] Conflicts [] %pretrans Empty %pre Empty %post Empty %preun Empty %postun Empty %posttrans Empty ================== =============== All packages are built during step execution. .. note:: *BuildRequires* are ignored for build-time (*rpmbuild* is executed with ``--nodeps`` option). If there is a space character in the package name only the preceding part is used. .. note:: Scriptlets such as *%pre* can be listed multiple time so that the entering of a multi-line script is more comfortable. Examples: .. code-block:: gherkin Feature: Working with repositories Background: Repository base with dummy package Given http repository base with packages | Package | Tag | Value | | foo | | | Scenario: Installing dummy package from background When I enable repository base Then I successfully run "dnf -y install foo" Scenario: Creating repository with multiple package versions Given http repository "updates" with packages | Package | Tag | Value | | foo | Version | 2.0 | | foo v3 | Version | 3.0 | Scenario: Creating a package with %pre scriptlet failing Given http repository "more_updates" with packages | Package | Tag | Value | | foo | Version | 4.0 | | | %pre | exit 1 | """ packages = table_utils.parse_skv_table(ctx, HEADINGS_REPO, PKG_TAGS, PKG_TAGS_REPEATING) rpmbuild = which("rpmbuild") ctx.assertion.assertIsNotNone(rpmbuild, "rpmbuild is required") createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") if rtype == 'http' or rtype == 'https': tmpdir = tempfile.mkdtemp(dir='/var/www/html') repopath = os.path.join('localhost', os.path.basename(tmpdir)) elif rtype == 'ftp': tmpdir = tempfile.mkdtemp(dir='/var/ftp/pub') repopath = os.path.join('localhost/pub', os.path.basename(tmpdir)) else: tmpdir = tempfile.mkdtemp() repopath = tmpdir srpm_tmpdir = '{}-source'.format(tmpdir.rstrip('/')) srpm_repopath = '{}-source'.format(repopath.rstrip('/')) os.mkdir(srpm_tmpdir) # create a directory for src.rpm pkgs template = JINJA_ENV.from_string(PKG_TMPL) for name, settings in packages.items(): name = name.split()[0] # cut-off the pkg name _suffix_ to allow defining multiple package versions disttag = "" if '/' in name: # using the module/pkgname notation, module would be placed to a disttag (module, name) = name.split('/', 1) disttag = ".{}".format(module) # before processing the template # lower all characters # replace '%' in Tag name with '_' # replace '(' in Tag name with '_' # delete all ')' in Tag settings = {k.lower().replace('%', '_').replace('(', '_').replace(')', ''): v for k, v in settings.items()} ctx.text = template.render(name=name, disttag=disttag, **settings) fname = "{!s}/{!s}.spec".format(tmpdir, name) step_a_file_filepath_with(ctx, fname) buildname = '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm' if 'arch' not in settings or settings['arch'] == 'noarch': cmd = "{!s} --define '_rpmdir {!s}' --define '_srcrpmdir {!s}' --define '_build_name_fmt {!s}' -ba {!s}".format( rpmbuild, tmpdir, srpm_tmpdir, buildname, fname) else: cmd = "setarch {!s} {!s} --define '_rpmdir {!s}' --define '_srcrpmdir {!s}' --define '_build_name_fmt {!s}' --target {!s} -ba {!s}".format( settings['arch'], rpmbuild, tmpdir, srpm_tmpdir, buildname, settings['arch'], fname) step_i_successfully_run_command(ctx, cmd) if gpgkey: # sign all rpms built rpmsign = which("rpmsign") rpms = glob.glob("{!s}/*.rpm".format(tmpdir)) srpms = glob.glob("{!s}/*.rpm".format(srpm_tmpdir)) cmd = "{!s} --addsign --key-id '{!s}' {!s} {!s}".format(rpmsign, gpgkey, ' '.join(rpms), ' '.join(srpms)) step_i_successfully_run_command(ctx, cmd) cmd = "{!s} {!s}".format(createrepo, tmpdir) step_i_successfully_run_command(ctx, cmd) cmd = "{!s} {!s}".format(createrepo, srpm_tmpdir) step_i_successfully_run_command(ctx, cmd) # set proper directory content ownership file_utils.set_dir_content_ownership(ctx, tmpdir) file_utils.set_dir_content_ownership(ctx, srpm_tmpdir) repofile = REPO_TMPL.format(repository) ctx.table = Table(HEADINGS_INI) ctx.table.add_row([repository, "name", repository]) ctx.table.add_row(["", "enabled", six.text_type(enabled)]) ctx.table.add_row(["", "baseurl", "{!s}://{!s}".format(rtype, repopath)]) if gpgkey: ctx.table.add_row(["", "gpgcheck", "True"]) else: ctx.table.add_row(["", "gpgcheck", "False"]) if rtype == 'https': ctx.table.add_row(["", "sslcacert", "/etc/pki/tls/certs/testcerts/ca/cert.pem"]) ctx.table.add_row(["", "sslclientkey", "/etc/pki/tls/certs/testcerts/client/key.pem"]) ctx.table.add_row(["", "sslclientcert", "/etc/pki/tls/certs/testcerts/client/cert.pem"]) step_an_ini_file_filepath_with(ctx, repofile) # create -source repository too srpm_repository = '{}-source'.format(repository) repofile = REPO_TMPL.format(srpm_repository) ctx.table = Table(HEADINGS_INI) ctx.table.add_row([srpm_repository, "name", srpm_repository]) ctx.table.add_row(["", "enabled", "False"]) ctx.table.add_row(["", "baseurl", "{!s}://{!s}".format(rtype, srpm_repopath)]) if gpgkey: ctx.table.add_row(["", "gpgcheck", "True"]) else: ctx.table.add_row(["", "gpgcheck", "False"]) if rtype == 'https': ctx.table.add_row(["", "sslcacert", "/etc/pki/tls/certs/testcerts/ca/cert.pem"]) ctx.table.add_row(["", "sslclientkey", "/etc/pki/tls/certs/testcerts/client/key.pem"]) ctx.table.add_row(["", "sslclientcert", "/etc/pki/tls/certs/testcerts/client/cert.pem"]) step_an_ini_file_filepath_with(ctx, repofile)
def given_repository_with_packages(ctx, repository): """ Builds dummy noarch packages, creates repo and *.repo* file. .. note:: Requires *rpmbuild* and *createrepo_c*. Requires table with following headers: ========= ===== ======= Package Tag Value ========= ===== ======= *Tag* is tag in RPM. Supported ones are: ============= =============== Tag Default value ============= =============== Summary Empty Version 1 Release 1 License Public Domain BuildRequires [] Requires [] Obsoletes [] Provides [] ============= =============== All packages are built during step execution. .. note:: *BuildRequires* are ignored for build-time (*rpmbuild* is executed with ``--nodeps`` option). Examples: .. code-block:: gherkin Feature: Working with repositories Background: Repository base with dummy package Given repository base with packages | Package | Tag | Value | | foo | | | Scenario: Installing dummy package from background When I enable repository base Then I successfully run "dnf -y install foo" """ packages = table_utils.parse_skv_table(ctx, HEADINGS_REPO, PKG_TAGS, PKG_TAGS_REPEATING) rpmbuild = which("rpmbuild") ctx.assertion.assertIsNotNone(rpmbuild, "rpmbuild is required") createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") tmpdir = tempfile.mkdtemp() template = JINJA_ENV.from_string(PKG_TMPL) for name, settings in packages.items(): settings = {k.lower(): v for k, v in settings.items()} ctx.text = template.render(name=name, **settings) fname = "{!s}/{!s}.spec".format(tmpdir, name) step_a_file_filepath_with(ctx, fname) cmd = "{!s} --define '_rpmdir {!s}' -bb {!s}".format( rpmbuild, tmpdir, fname) step_i_successfully_run_command(ctx, cmd) cmd = "{!s} {!s}".format(createrepo, tmpdir) step_i_successfully_run_command(ctx, cmd) repofile = REPO_TMPL.format(repository) ctx.table = Table(HEADINGS_INI) ctx.table.add_row([repository, "name", repository]) ctx.table.add_row(["", "enabled", "False"]) ctx.table.add_row(["", "gpgcheck", "False"]) ctx.table.add_row(["", "baseurl", "file://{!s}".format(tmpdir)]) step_an_ini_file_filepath_with(ctx, repofile)
def step_updateinfo_defined_in_repository(ctx, repository): """ For a given repository creates updateinfo.xml file with described updates and recreates the repo .. note:: Requires *modifyrepo_c* and the repo to be already created. Requires table with following headers: ==== ===== ======= Id Tag Value ==== ===== ======= *Tag* is describing attributes of the respective update. Supported tags are: ============ ========================= Tag Default value ============ ========================= Title Default title of Id Type security Description Default description of Id Summary Default summary of Id Severity Low Solution Default solution of Id Rights nobody Issued 2017-01-01 00:00:01 Updated 2017-01-01 00:00:01 Reference none Package none ============ ========================= Examples: .. code-block:: gherkin Feature: Defining updateinfo in a repository @setup Scenario: Repository base with updateinfo defined Given repository "base" with packages | Package | Tag | Value | | foo | Version | 2 | | bar | Version | 2 | And updateinfo defined in repository "base" | Id | Tag | Value | | RHSA-2017-001 | Title | foo bar security update | | | Type | security | | | Description | Fixes buffer overflow | | | Summary | Critical bug is fixed | | | Severity | Critical | | | Solution | Update to the new version | | | Rights | Copyright 2017 Baz Inc | | | Reference | CVE-2017-0001 | | | Reference | BZ123456 | | | Package | foo-2 | | | Package | bar-2 | .. note:: Specifying Version or Release in Package tag is not necessary, however when multiple RPMs matches the string the last one from the sorted list is used. """ HEADINGS_GROUP = ['Id', 'Tag', 'Value'] UPDATEINFO_TAGS_REPEATING = ['Package', 'Reference'] UPDATEINFO_TAGS = ['Title', 'Type', 'Description', 'Solution', 'Summary', 'Severity', 'Rights', 'Issued', 'Updated'] + \ UPDATEINFO_TAGS_REPEATING updateinfo_table = table_utils.parse_skv_table(ctx, HEADINGS_GROUP, UPDATEINFO_TAGS, UPDATEINFO_TAGS_REPEATING) # verify that modifyrepo_c is present modifyrepo = which("modifyrepo_c") ctx.assertion.assertIsNotNone(modifyrepo, "modifyrepo_c is required") # prepare updateinfo.xml content repodir = repo_utils.get_repo_dir(repository) updateinfo_xml = repo_utils.get_updateinfo_xml(repository, updateinfo_table) # save it to the updateinfo.xml file and recreate the repodata tmpdir = tempfile.mkdtemp() with open(os.path.join(tmpdir, "updateinfo.xml"), 'w') as fw: fw.write(updateinfo_xml) file_utils.set_dir_content_ownership( ctx, repodir, 'root') # change file ownership to root so we can change it cmd = "{!s} {!s} {!s}".format(modifyrepo, os.path.join(tmpdir, 'updateinfo.xml'), os.path.join(repodir, 'repodata')) step_i_successfully_run_command(ctx, cmd) file_utils.set_dir_content_ownership(ctx, repodir) # restore file ownership
def given_repository_with_packages(ctx, repository): """ Builds dummy noarch packages, creates repo and *.repo* file. .. note:: Requires *rpmbuild* and *createrepo_c*. Requires table with following headers: ========= ===== ======= Package Tag Value ========= ===== ======= *Tag* is tag in RPM. Supported ones are: ============= =============== Tag Default value ============= =============== Summary Empty Version 1 Release 1 License Public Domain BuildRequires [] Requires [] Obsoletes [] Provides [] Conflicts [] ============= =============== All packages are built during step execution. .. note:: *BuildRequires* are ignored for build-time (*rpmbuild* is executed with ``--nodeps`` option). Examples: .. code-block:: gherkin Feature: Working with repositories Background: Repository base with dummy package Given repository base with packages | Package | Tag | Value | | foo | | | Scenario: Installing dummy package from background When I enable repository base Then I successfully run "dnf -y install foo" """ packages = table_utils.parse_skv_table(ctx, HEADINGS_REPO, PKG_TAGS, PKG_TAGS_REPEATING) rpmbuild = which("rpmbuild") ctx.assertion.assertIsNotNone(rpmbuild, "rpmbuild is required") createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") tmpdir = tempfile.mkdtemp() template = JINJA_ENV.from_string(PKG_TMPL) for name, settings in packages.items(): settings = {k.lower(): v for k, v in settings.items()} ctx.text = template.render(name=name, **settings) fname = "{!s}/{!s}.spec".format(tmpdir, name) step_a_file_filepath_with(ctx, fname) cmd = "{!s} --define '_rpmdir {!s}' -bb {!s}".format( rpmbuild, tmpdir, fname) step_i_successfully_run_command(ctx, cmd) cmd = "{!s} {!s}".format(createrepo, tmpdir) step_i_successfully_run_command(ctx, cmd) repofile = REPO_TMPL.format(repository) ctx.table = Table(HEADINGS_INI) ctx.table.add_row([repository, "name", repository]) ctx.table.add_row(["", "enabled", "False"]) ctx.table.add_row(["", "gpgcheck", "False"]) ctx.table.add_row(["", "baseurl", "file://{!s}".format(tmpdir)]) step_an_ini_file_filepath_with(ctx, repofile)
def given_package_groups_defined_in_repository(ctx, repository): """ For a given repository creates comps.xml file with described package groups and recreates the repo .. note:: Requires *createrepo_c* and the repo to be already created. Requires table with following headers: ========= ===== ======= Group Tag Value ========= ===== ======= *Tag* is describing characteristics of the respective package group.Supported tags are: ============== =============== Tag Default value ============== =============== is_default false is_uservisible true description "" mandatory [] default [] optional [] conditional [] ============== =============== Examples: .. code-block:: gherkin Feature: Installing a package group @setup Scenario: Repository base with package group minimal Given repository "base" with packages | Package | Tag | Value | | foo | | | | bar | | | | baz | | | | qux | | | And package groups defined in repository "base" | Group | Tag | Value | | minimal | mandatory | foo | | | default | bar | | | conditional | baz qux | Scenario: Installing package group from background When I enable repository "base" Then I successfully run "dnf -y group install minimal" .. note:: Conditional packages are described in a form PKG REQUIREDPKG """ HEADINGS_GROUP = ['Group', 'Tag', 'Value'] GROUP_TAGS_REPEATING = ['mandatory', 'default', 'optional', 'conditional'] GROUP_TAGS = ['is_default', 'is_uservisible', 'description' ] + GROUP_TAGS_REPEATING pkg_groups = table_utils.parse_skv_table(ctx, HEADINGS_GROUP, GROUP_TAGS, GROUP_TAGS_REPEATING) createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") createrepo = "{!s} {!s}".format(createrepo, "--no-database") # prepare the comps.xml comps_xml = COMPS_PREFIX template = JINJA_ENV.from_string(COMPS_TMPL) for name, settings in pkg_groups.items(): settings = {k.lower(): v for k, v in settings.items()} comps_xml += template.render(name=name, **settings) comps_xml += COMPS_SUFFIX # save comps.xml and recreate the repo repodir = repo_utils.get_repo_dir(repository) with open(os.path.join(repodir, "comps.xml"), "w") as f_comps: f_comps.write(comps_xml) file_utils.set_dir_content_ownership( ctx, repodir, 'root') # change file ownership to root so we can change it cmd = "{!s} -g comps.xml --update {!s}".format(createrepo, repodir) step_i_successfully_run_command(ctx, cmd) file_utils.set_dir_content_ownership(ctx, repodir) # restore file ownership
def then_update_repository_with_packages(ctx, repository): """ Examples: Feature: Working with repositories Given I update http repository base with packages | Package | Tag | Value | | foo | | | Given I update http repository "updates" with packages | Package | Tag | Value | | foo | Version | 2.1 | | foo v3 | Version | 3.1 | """ packages = table_utils.parse_skv_table(ctx, HEADINGS_REPO, PKG_TAGS, PKG_TAGS_REPEATING) rpmbuild = which("rpmbuild") ctx.assertion.assertIsNotNone(rpmbuild, "rpmbuild is required") createrepo = which("createrepo_c") ctx.assertion.assertIsNotNone(createrepo, "createrepo_c is required") repodir = repo_utils.get_repo_dir(repository) tmpdir = repodir srpm_tmpdir = '{}-source'.format(tmpdir.rstrip('/')) template = JINJA_ENV.from_string(PKG_TMPL) for name, settings in packages.items(): name = name.split( )[0] # cut-off the pkg name _suffix_ to allow defining multiple package versions disttag = "" if '/' in name: # using the module/pkgname notation, module would be placed to a disttag (module, name) = name.split('/', 1) disttag = ".{}".format(module) # before processing the template # lower all characters # replace '%' in Tag name with '_' # replace '(' in Tag name with '_' # delete all ')' in Tag settings = { k.lower().replace('%', '_').replace('(', '_').replace(')', ''): v for k, v in settings.items() } ctx.text = template.render(name=name, disttag=disttag, **settings) fname = "{!s}/{!s}.spec".format(tmpdir, name) step_a_file_filepath_with(ctx, fname) buildname = '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm' if 'arch' not in settings or settings['arch'] == 'noarch': cmd = "{!s} --define '_rpmdir {!s}' --define '_srcrpmdir {!s}' \ --define '_build_name_fmt {!s}' -ba {!s}" \ .format(rpmbuild, tmpdir, srpm_tmpdir, buildname, fname) else: cmd = "setarch {!s} {!s} --define '_rpmdir {!s}' --define '_srcrpmdir {!s}' \ --define '_build_name_fmt {!s}' --target {!s} -ba {!s}" \ .format(settings['arch'], rpmbuild, tmpdir, srpm_tmpdir, buildname, settings['arch'], fname) step_i_successfully_run_command(ctx, cmd) file_utils.set_dir_content_ownership( ctx, repodir, 'root') # change file ownership to root so we can change it cmd = "{!s} --update {!s}".format(createrepo, repodir) step_i_successfully_run_command(ctx, cmd) file_utils.set_dir_content_ownership(ctx, repodir) # restore file ownership