Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bindep module #22159

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/BOTMETA.yml
Expand Up @@ -530,6 +530,7 @@ files:
$modules/packaging/os/apt_key.py: jvantuyl
$modules/packaging/os/apt_repository.py: $team_ansible sashka
$modules/packaging/os/apt_rpm.py: evgkrsk
$modules/packaging/os/bindep.py: mordred pabelanger rcarrillocruz
$modules/packaging/os/dnf.py: DJMuggs berenddeschouwer ignatenkobrain
$modules/packaging/os/dpkg_selections.py: brian-brazil
$modules/packaging/os/homebrew.py: andrew-d danieljaouen indrajitr
Expand Down
197 changes: 197 additions & 0 deletions lib/ansible/modules/packaging/os/bindep.py
@@ -0,0 +1,197 @@
#!/usr/bin/python
# Copyright 2017 Red Hat, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}

DOCUMENTATION = '''
---
module: bindep
short_description: Get lists of missing distro packages to be installed
author: Monty Taylor (@mordred)
version_added: "2.4"
description:
- Retreive a list of packages that are not already installed based on a
list of requirements in bindep format. For more information on bindep,
please see https://docs.openstack.org/infra/bindep/.
requirements:
- "python >= 2.7"
- "bindep >= 2.2.0"
options:
path:
description:
- Path to a bindep.txt file or to a directory that contains either
a bindep.txt file or an other-requirements.txt file that should
be used as input. Mutually exclusive with I(requirements).
required: false
default: None
requirements:
description:
- A list of bindep requirements strings. Mutually exclusive with I(path).
required: false
default: None
profiles:
description:
- An explicit list of profiles to filter by.
required: false
default: [default]
'''

EXAMPLES = '''
- name: Get the list of packages to install on a given host from a file
bindep:
path: /home/example/bindep.txt

- name: Get the list of test packages to install from current directory
bindep:
profiles:
- test
register: ret
- name: Install the missing packages
package:
name: "{{ item }}"
state: present
with_items: "{{ ret.bindep_packages.missing }}"

- name: Get the list of packages to install from an explicit list
bindep:
requirements:
- "build-essential [platform:dpkg test]"
- "gcc [platform:rpm test]"
- "language-pack-en [platform:ubuntu]"
- "libffi-dev [platform:dpkg test]"
- "libffi-devel [platform:rpm test]"

- name: Get the list of packages to install for the test profile
bindep:
requirements:
- "build-essential [platform:dpkg test]"
- "gcc [platform:rpm test]"
- "language-pack-en [platform:ubuntu]"
- "libffi-dev [platform:dpkg test]"
- "libffi-devel [platform:rpm test]"
profiles:
- test
'''


RETURN = '''
bindep_packages:
description: Dictionary containing information about the requirements.
returned: On success.
type: complex
contains:
missing:
description: Packages that are missing from the system
returned: success
type: list
sample:
- libmysqlclient-dev
- libxml2-dev
badversion:
description: Packages that are installed but at bad versions.
returned: success
type: list
sample:
- package: libxml2-dev
version: 2.9.4+dfsg1-2
constraint: ">= 3.0"
up_to_date:
description: Flag indicating all packages are up to date
returned: success
type: bool
'''

import os

from ansible.module_utils.basic import AnsibleModule

try:
import bindep.depends
import ometa.runtime
HAS_BINDEP = True
except ImportError:
HAS_BINDEP = False


def main():
module = AnsibleModule(
argument_spec=dict(
path=dict(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path type could be used (some vars would be expanded) for path parameter.

requirements=dict(required=False, type="list"),
profiles=dict(required=False, default=['default'], type='list'),
),
mutually_exclusive=[['path', 'requirements']],
required_one_of=[['path', 'requirements']],
supports_check_mode=True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module should support check mode but I don't see any support for that in the code. Can you explain?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module doesn't actually make any changes itself - so all invocations of it are safe for check mode.

)

if not HAS_BINDEP:
module.fail_json(msg='bindep is required for this module')
if not hasattr(bindep.depends, 'get_depends'):
module.fail_json(
msg='bindep is required at version >= 2.2 for this module')

path = module.params['path']
requirements = module.params['requirements']
profiles = module.params['profiles']

if requirements:
req_string = '\n'.join(requirements) + '\n'
try:
depends = bindep.depends.Depends(req_string)
except ometa.runtime.ParseError as e:
module.fail_json(msg='bindep parse error: %s' % str(e))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

str is useless here (same for the two other str(e) below).

else:
if path and os.path.isdir(path):
os.chdir(path)
try:
depends = bindep.depends.get_depends()
except ometa.runtime.ParseError as e:
module.fail_json(msg='bindep parse error: %s' % str(e))
if not depends:
module.fail_json(msg="no bindep.txt file found at %s" % path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_depends try to read other-requirements.txt too.

elif path and not os.path.isdir(path):
module.fail_json(msg="path %s was given but does not exist" % path)
else:
try:
depends = bindep.depends.get_depends(filename=path)
except ometa.runtime.ParseError as e:
module.fail_json(msg='bindep parse error: %s' % str(e))
if not depends:
module.fail_json(msg="bindep file %s not found" % path)

profiles = profiles + depends.platform_profiles()
rules = depends.active_rules(profiles)
results = depends.check_rules(rules)

ret = {
'up_to_date': True,
'missing': [],
'badversion': []
}

if results:
ret['up_to_date'] = False
for result in results:
if result[0] == 'missing':
ret['missing'].append(result[1])
if result[0] == 'badversion':
for pkg, constraint, version in result[1]:
ret['badversion'].append({
'package': pkg,
'version': version,
'constraint': constraint,
})

module.exit_json(changed=False, bindep_packages=ret)

if __name__ == '__main__':
main()
4 changes: 4 additions & 0 deletions test/integration/targets/bindep/aliases
@@ -0,0 +1,4 @@
destructive
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment explaining why some systems are skipped.

skip/freebsd
skip/osx
skip/rhel
2 changes: 2 additions & 0 deletions test/integration/targets/bindep/files/bindep.txt
@@ -0,0 +1,2 @@
hello [platform:dpkg]
sos [platform:rpm]
2 changes: 2 additions & 0 deletions test/integration/targets/bindep/meta/main.yml
@@ -0,0 +1,2 @@
dependencies:
- prepare_tests
55 changes: 55 additions & 0 deletions test/integration/targets/bindep/tasks/dpkg.yml
@@ -0,0 +1,55 @@
- name: make sure packages aren't installed
package:
name: "{{ item }}"
state: absent
with_items:
- finger
- hello

- name: ask bindep what should be installed
bindep:
register: bindep_missing

- name: verify bindep wants us to install hello
assert:
that:
- "'hello' in bindep_missing.missing"

- name: ask bindep directly if finger is installed
bindep:
requirements:
- finger
register: bindep_finger

- name: verify bindep wants us to install finger
assert:
that:
- "'finger' in bindep_finger.missing"

- name: ask bindep directly if about a mixed list
bindep:
requirements:
- "finger [platform:dpkg]"
- "libssl-devel [platform:rpm]"
register: bindep_mixed

- name: verify bindep wants us to install finger and not libssl-devel
assert:
that:
- "'finger' in bindep_mixed.missing"
- "'libssl-devel' not in bindep_mixed.missing"

- name: install packages
package:
name: "{{ item }}"
state: present
with_items: "{{ bindep_missing.missing }}"

- name: ask bindep what should be installed again
bindep:
register: bindep_missing_again

- name: verify uninstallation of hello
assert:
that:
- "not bindep_missing_again.missing"
34 changes: 34 additions & 0 deletions test/integration/targets/bindep/tasks/main.yml
@@ -0,0 +1,34 @@
# Copyright 2017 Red Hat, Inc.

# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.

- name: show python version
debug: var=ansible_python_version

- name: install bindep
pip:
name: bindep

- name: Copy bindep file to target
copy:
src: bindep.txt
dest: "{{ ansible_user_dir }}/bindep.txt"

- include: 'dpkg.yml'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ansible_os_family should be used instead of ansible_distribution.

I am not sure how the integration tests could succeed on OpenSuse 42 ? Integration tests were not run by the CI (see), for example for linux/opensuse42.2/1:

2017-09-02 16:06:09 Processing pull request for branch devel commit 43938f4c617430230647985a1c61774a994df859
2017-09-02 16:06:09 Detected changes in 8 file(s).
2017-09-02 16:06:09 .github/BOTMETA.yml
2017-09-02 16:06:09 lib/ansible/modules/packaging/os/bindep.py
2017-09-02 16:06:09 test/integration/targets/bindep/aliases
2017-09-02 16:06:09 test/integration/targets/bindep/files/bindep.txt
2017-09-02 16:06:09 test/integration/targets/bindep/meta/main.yml
2017-09-02 16:06:09 test/integration/targets/bindep/tasks/dpkg.yml
2017-09-02 16:06:09 test/integration/targets/bindep/tasks/main.yml
2017-09-02 16:06:09 test/integration/targets/bindep/tasks/rpm.yml
2017-09-02 16:06:09 Mapping 8 changed file(s) to tests.
2017-09-02 16:06:09 .github/BOTMETA.yml -> integration: none
2017-09-02 16:06:09 lib/ansible/modules/packaging/os/bindep.py -> integration: bindep (targeted)
2017-09-02 16:06:09 test/integration/targets/bindep/aliases -> integration: bindep (targeted)
2017-09-02 16:06:09 test/integration/targets/bindep/files/bindep.txt -> integration: bindep (targeted)
2017-09-02 16:06:09 test/integration/targets/bindep/meta/main.yml -> integration: bindep (targeted)
2017-09-02 16:06:09 test/integration/targets/bindep/tasks/dpkg.yml -> integration: bindep (targeted)
2017-09-02 16:06:09 test/integration/targets/bindep/tasks/main.yml -> integration: bindep (targeted)
2017-09-02 16:06:09 test/integration/targets/bindep/tasks/rpm.yml -> integration: bindep (targeted)
2017-09-02 16:06:09 WARNING: All targets skipped.

If I remember well, posix/ci/group3 (or group1 or group2) should be added to aliases.

By the way, rpm.yml and dpkg.yml could be factorized (using a list of packages and bindep requirements as variables).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests should have the alias posix/ci/group3 (group1 and group2 are currently disabled for opensuse due to unrelated issues, so adding it to group3 is advised). Without the alias the tests won't run in CI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one warning:

'include' for tasks has been deprecated. Use 'import_tasks' for static inclusions or 'include_tasks' for dynamic inclusions.

when: ansible_distribution in ('Ubuntu', 'Debian')

- include: 'rpm.yml'
when: ansible_distribution in ('RedHat', 'CentOS', 'ScientificLinux')
55 changes: 55 additions & 0 deletions test/integration/targets/bindep/tasks/rpm.yml
@@ -0,0 +1,55 @@
- name: make sure packages aren't installed
package:
name: "{{ item }}"
state: absent
with_items:
- sos
- finger

- name: ask bindep what should be installed
bindep:
register: bindep_missing

- name: verify bindep wants us to install sos
assert:
that:
- "'sos' in bindep_missing.missing"

- name: ask bindep directly if finger is installed
bindep:
requirements:
- finger
register: bindep_finger

- name: verify bindep wants us to install finger
assert:
that:
- "'finger' in bindep_finger.missing"

- name: ask bindep directly if about a mixed list
bindep:
requirements:
- "finger [platform:rpm]"
- "libssl-dev [platform:dpkg]"
register: bindep_mixed

- name: verify bindep wants us to install finger and not libssl-dev
assert:
that:
- "'finger' in bindep_mixed.missing"
- "'libssl-dev' not in bindep_mixed.missing"

- name: install packages
package:
name: "{{ item }}"
state: present
with_items: "{{ bindep_missing.missing }}"

- name: ask bindep what should be installed again
bindep:
register: bindep_missing_again

- name: verify uninstallation of sos
assert:
that:
- "not bindep_missing_again.missing"