diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..08c6abf9 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "VoxBox", + "image": "ghcr.io/voxpupuli/voxbox:latest" +} diff --git a/.editorconfig b/.editorconfig index b79ef58d..ecb10a80 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,20 +1,15 @@ # editorconfig.org +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + root = true [*] charset = utf-8 end_of_line = lf -indent_size = 4 -tab_width = 4 -indent_style = tab +indent_size = 2 +tab_width = 2 +indent_style = space insert_final_newline = true trim_trailing_whitespace = true - -[*.txt] -trim_trailing_whitespace = false - -[*.{md,json,yml}] -trim_trailing_whitespace = false -indent_style = space -indent_size = 2 diff --git a/.fixtures.yml b/.fixtures.yml index 5db2d52c..4a4f5b85 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -1,10 +1,6 @@ +--- fixtures: repositories: - 'stdlib': - repo: 'git://github.com/puppetlabs/puppetlabs-stdlib.git' - ref: '4.6.0' - 'epel': - repo: 'git://github.com/stahnma/puppet-module-epel.git' - ref: '1.0.2' - symlinks: - python: "#{source_dir}" + epel: https://github.com/voxpupuli/puppet-epel.git + stdlib: https://github.com/puppetlabs/puppetlabs-stdlib.git + yumrepo_core: https://github.com/puppetlabs/puppetlabs-yumrepo_core.git diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..593e7aa8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,26 @@ + + +## Affected Puppet, Ruby, OS and module versions/distributions + +- Puppet: +- Ruby: +- Distribution: +- Module version: + +## How to reproduce (e.g Puppet code you use) + +## What are you seeing + +## What behaviour did you expect instead + +## Output log + +## Any additional information you'd like to impart diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..342807bc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ + +#### Pull Request (PR) description + + +#### This Pull Request (PR) fixes the following issues + diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..f2d08d6b --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,6 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +skip-changelog: + - head-branch: ['^release-*', 'release'] diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..f5b5d7a9 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,42 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes + +changelog: + exclude: + labels: + - duplicate + - invalid + - modulesync + - question + - skip-changelog + - wont-fix + - wontfix + + categories: + - title: Breaking Changes 🛠 + labels: + - backwards-incompatible + + - title: New Features 🎉 + labels: + - enhancement + + - title: Bug Fixes 🐛 + labels: + - bug + + - title: Documentation Updates 📚 + labels: + - documentation + - docs + + - title: Dependency Updates ⬆️ + labels: + - dependencies + + - title: Other Changes + labels: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e9744066 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +name: CI + +# yamllint disable-line rule:truthy +on: + pull_request: {} + push: + branches: + - main + - master + workflow_dispatch: + inputs: + beaker_staging_url: + description: |- + URL to a staging Server to test unreleased packages. + We will append the version to the Server and assume all packages are in the same directory. + Only supported for AIO packages. + required: false + type: string + default: 'https://artifacts.voxpupuli.org/openvox-agent' + beaker_collection: + description: |- + When set to staging, we will download the packages from staging_url. + Otherwise we will use the official repos. Supported values: puppet7, puppet8, openvox7, openvox8, staging. + When unset, we will generate a list of supported collections based on metadata.json. + required: false + type: string + beaker_staging_version: + description: |- + The package version we want to test. + Only used for beaker_collection = staging + required: false + type: string + +concurrency: + group: ${{ github.ref_name }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + puppet: + name: Puppet + uses: voxpupuli/gha-puppet/.github/workflows/beaker.yml@v4 + with: + beaker_staging_url: ${{ inputs.beaker_staging_url }} + beaker_collection: ${{ inputs.beaker_collection }} + beaker_staging_version: ${{ inputs.beaker_staging_version }} diff --git a/.github/workflows/create_tag.yml b/.github/workflows/create_tag.yml new file mode 100644 index 00000000..c835e75a --- /dev/null +++ b/.github/workflows/create_tag.yml @@ -0,0 +1,22 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +name: Create Git tag + +on: + workflow_dispatch: + +permissions: {} + +jobs: + create_tag: + uses: 'voxpupuli/gha-puppet/.github/workflows/create_tag.yml@v4' + with: + allowed_owner: 'voxpupuli' + git_name: 'pccibot' + git_email: '12855858+pccibot@users.noreply.github.com' + secrets: + # Configure secrets here: + # https://docs.github.com/en/actions/security-guides/encrypted-secrets + ssh_private_key: ${{ secrets.PCCI_SSH_PRIVATE_KEY }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 00000000..eacd0b33 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,22 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +name: "Pull Request Labeler" + +# yamllint disable-line rule:truthy +on: + pull_request_target: {} + +permissions: + contents: read + pull-requests: write + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml new file mode 100644 index 00000000..b3725319 --- /dev/null +++ b/.github/workflows/prepare_release.yml @@ -0,0 +1,30 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +name: 'Prepare Release' + +on: + workflow_dispatch: + inputs: + version: + description: 'Module version to be released. Must be a valid semver string without leading v. (1.2.3)' + required: false + +permissions: + contents: write + pull-requests: write + +jobs: + release_prep: + uses: 'voxpupuli/gha-puppet/.github/workflows/prepare_release.yml@v4' + with: + version: ${{ github.event.inputs.version }} + allowed_owner: 'voxpupuli' + git_name: 'pccibot' + git_email: '12855858+pccibot@users.noreply.github.com' + secrets: + # Configure secrets here: + # https://docs.github.com/en/actions/security-guides/encrypted-secrets + github_pat: '${{ secrets.PCCI_PAT_RELEASE_PREP }}' + ssh_private_key: '${{ secrets.PCCI_SSH_PRIVATE_KEY }}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..9062a93c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +name: Release + +# yamllint disable-line rule:truthy +on: + push: + tags: + # https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#onpushbranchestagsbranches-ignoretags-ignore + # https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#filter-pattern-cheat-sheet + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: write + +jobs: + release: + name: Release + uses: voxpupuli/gha-puppet/.github/workflows/release.yml@v4 + with: + allowed_owner: 'voxpupuli' + secrets: + # Configure secrets here: + # https://docs.github.com/en/actions/security-guides/encrypted-secrets + username: ${{ secrets.PUPPET_FORGE_USERNAME }} + api_key: ${{ secrets.PUPPET_FORGE_API_KEY }} diff --git a/.gitignore b/.gitignore index 57718ecc..adea1b01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,25 @@ -# Editors -project.xml -project.properties -/nbproject/private/ -.buildpath -.project -.settings* -sftp-config.json -.idea +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ -# Modules -pkg/ -Gemfile.lock -vendor/ -spec/fixtures/ -.bundle/ -.vagrant/ -coverage/ - -# Beaker -.vagrant/ -log/ -junit/ - -# OS X metadata -.DS_Store - -# Windows junk -Thumbs.db +/pkg/ +/Gemfile.lock +/Gemfile.local +/vendor/ +/.vendor/ +/spec/fixtures/manifests/ +/spec/fixtures/modules/ +/.vagrant/ +/.bundle/ +/.ruby-version +/coverage/ +/log/ +/.idea/ +/.dependencies/ +/.librarian/ +/Puppetfile.lock +*.iml +.*.sw? +/.yardoc/ +/Guardfile +bolt-debug.log +.rerun.json diff --git a/.msync.yml b/.msync.yml new file mode 100644 index 00000000..bfb9bee8 --- /dev/null +++ b/.msync.yml @@ -0,0 +1,5 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +modulesync_config_version: '10.8.0' diff --git a/.overcommit.yml b/.overcommit.yml new file mode 100644 index 00000000..4ed994cc --- /dev/null +++ b/.overcommit.yml @@ -0,0 +1,67 @@ +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ +# +# Hooks are only enabled if you take action. +# +# To enable the hooks run: +# +# ``` +# bundle exec overcommit --install +# # ensure .overcommit.yml does not harm to you and then +# bundle exec overcommit --sign +# ``` +# +# (it will manage the .git/hooks directory): +# +# Examples howto skip a test for a commit or push: +# +# ``` +# SKIP=RuboCop git commit +# SKIP=PuppetLint git commit +# SKIP=RakeTask git push +# ``` +# +# Don't invoke overcommit at all: +# +# ``` +# OVERCOMMIT_DISABLE=1 git commit +# ``` +# +# Read more about overcommit: https://github.com/brigade/overcommit +# +# To manage this config yourself in your module add +# +# ``` +# .overcommit.yml: +# unmanaged: true +# ``` +# +# to your modules .sync.yml config +--- +PreCommit: + RuboCop: + enabled: true + description: 'Runs rubocop on modified files only' + command: ['bundle', 'exec', 'rubocop'] + RakeTarget: + enabled: true + description: 'Runs lint on modified files only' + targets: + - 'lint' + command: ['bundle', 'exec', 'rake'] + YamlSyntax: + enabled: true + JsonSyntax: + enabled: true + TrailingWhitespace: + enabled: true + +PrePush: + RakeTarget: + enabled: true + description: 'Run rake targets' + targets: + - 'validate' + - 'test' + - 'rubocop' + command: ['bundle', 'exec', 'rake'] diff --git a/.pmtignore b/.pmtignore new file mode 100644 index 00000000..a9d37aa0 --- /dev/null +++ b/.pmtignore @@ -0,0 +1,39 @@ +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +/docs/ +/pkg/ +/Gemfile +/Gemfile.lock +/Gemfile.local +/vendor/ +/.vendor/ +/spec/ +/Rakefile +/.vagrant/ +/.bundle/ +/.ruby-version +/coverage/ +/log/ +/.idea/ +/.dependencies/ +/.github/ +/.librarian/ +/Puppetfile.lock +/Puppetfile +*.iml +/.editorconfig +/.fixtures.yml +/.gitignore +/.msync.yml +/.overcommit.yml +/.pmtignore +/.rspec +/.rspec_parallel +/.rubocop.yml +/.sync.yml +.*.sw? +/.yardoc/ +/.yardopts +/Dockerfile +/HISTORY.md diff --git a/.puppet-lint.rc b/.puppet-lint.rc new file mode 100644 index 00000000..420e819f --- /dev/null +++ b/.puppet-lint.rc @@ -0,0 +1,5 @@ +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +--fail-on-warnings +--no-parameter_documentation-check diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..fded90cf --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,7 @@ +--- +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +inherit_from: .rubocop_todo.yml +inherit_gem: + voxpupuli-test: rubocop.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000..f8daacee --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,13 @@ +# This configuration was generated by +# `rubocop --auto-gen-config --no-auto-gen-timestamp` +# using RuboCop version 1.85.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 3 +RSpec/LeakyLocalVariable: + Exclude: + - 'spec/acceptance/class_spec.rb' + - 'spec/acceptance/facts_test_spec.rb' diff --git a/.sync.yml b/.sync.yml index ed97d539..4fa6b004 100644 --- a/.sync.yml +++ b/.sync.yml @@ -1 +1,6 @@ --- +spec/spec_helper_acceptance.rb: + unmanaged: false +.puppet-lint.rc: + enabled_lint_checks: + - parameter_types diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 749f64be..00000000 --- a/.travis.yml +++ /dev/null @@ -1,55 +0,0 @@ ---- -language: ruby - -bundler_args: --without system_tests - -rvm: - - 1.8.7 - - 1.9.3 - - 2.0.0 - - 2.1.0 - -env: - matrix: - - PUPPET_GEM_VERSION="~> 3.1.0" - - PUPPET_GEM_VERSION="~> 3.2.0" - - PUPPET_GEM_VERSION="~> 3.3.0" - - PUPPET_GEM_VERSION="~> 3.4.0" - - PUPPET_GEM_VERSION="~> 3.5.0" - - PUPPET_GEM_VERSION="~> 3.6.0" - - PUPPET_GEM_VERSION="~> 3.7.0" - - PUPPET_GEM_VERSION="~> 3.8.0" - - PUPPET_GEM_VERSION="~> 3" FUTURE_PARSER="yes" - - PUPPET_GEM_VERSION="~> 4.0.0" - - PUPPET_GEM_VERSION="~> 4.1.0" - - PUPPET_GEM_VERSION="~> 4.2.0" - - PUPPET_GEM_VERSION="~> 4" - -sudo: false - -script: 'bundle exec metadata-json-lint metadata.json && bundle exec rake validate && bundle exec rake lint && SPEC_OPTS="--format documentation" bundle exec rake spec' - -matrix: - fast_finish: true - exclude: - - rvm: 2.0.0 - env: PUPPET_GEM_VERSION="~> 3.1.0" - - rvm: 2.1.0 - env: PUPPET_GEM_VERSION="~> 3.1.0" - - rvm: 2.1.0 - env: PUPPET_GEM_VERSION="~> 3.2.0" - - rvm: 2.1.0 - env: PUPPET_GEM_VERSION="~> 3.3.0" - - rvm: 2.1.0 - env: PUPPET_GEM_VERSION="~> 3.4.0" - - rvm: 1.8.7 - env: PUPPET_GEM_VERSION="~> 4.0.0" - - rvm: 1.8.7 - env: PUPPET_GEM_VERSION="~> 4.1.0" - - rvm: 1.8.7 - env: PUPPET_GEM_VERSION="~> 4.2.0" - - rvm: 1.8.7 - env: PUPPET_GEM_VERSION="~> 4" - -notifications: - email: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..2c8c0db3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1030 @@ +# Changelog + +All notable changes to this project will be documented in this file. +Each new release typically also includes the latest modulesync defaults. +These should not affect the functionality of the module. + +## [v9.0.0](https://github.com/voxpupuli/puppet-python/tree/v9.0.0) (2026-05-05) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v8.0.0...v9.0.0) + +**Breaking changes:** + +- `init.pp`: remove useless `exec` with `default` title that was trying to set a default `umask` [\#735](https://github.com/voxpupuli/puppet-python/pull/735) ([kenyon](https://github.com/kenyon)) +- Drop puppet, update openvox minimum version to 8.19 [\#729](https://github.com/voxpupuli/puppet-python/pull/729) ([TheMeier](https://github.com/TheMeier)) + +**Implemented enhancements:** + +- puppet/epel: Allow 6.x [\#751](https://github.com/voxpupuli/puppet-python/pull/751) ([bastelfreak](https://github.com/bastelfreak)) +- Add support for Debian 13 [\#740](https://github.com/voxpupuli/puppet-python/pull/740) ([smortex](https://github.com/smortex)) +- Do not pass `--log` to `pip install` [\#736](https://github.com/voxpupuli/puppet-python/pull/736) ([smortex](https://github.com/smortex)) +- Allow a version to start with "v" in `python::pip` [\#727](https://github.com/voxpupuli/puppet-python/pull/727) ([techsk8](https://github.com/techsk8)) + +**Fixed bugs:** + +- Fix group ownership for `pip.conf` on FreeBSD [\#739](https://github.com/voxpupuli/puppet-python/pull/739) ([Szparki](https://github.com/Szparki)) +- Replace legacy fact in gunicorn config templating [\#724](https://github.com/voxpupuli/puppet-python/pull/724) ([notCalle](https://github.com/notCalle)) + +**Closed issues:** + +- "Error: Could not find group root" on FreeBSD [\#738](https://github.com/voxpupuli/puppet-python/issues/738) +- Define pip\_package in hieradata [\#731](https://github.com/voxpupuli/puppet-python/issues/731) + +**Merged pull requests:** + +- Add section in README that simulates `python3 -m pip install pandas --user` [\#725](https://github.com/voxpupuli/puppet-python/pull/725) ([bschonec](https://github.com/bschonec)) + +## [v8.0.0](https://github.com/voxpupuli/puppet-python/tree/v8.0.0) (2025-04-28) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v7.4.0...v8.0.0) + +**Breaking changes:** + +- Drop EoL Ubuntu 20.04 support [\#719](https://github.com/voxpupuli/puppet-python/pull/719) ([smortex](https://github.com/smortex)) +- Drop EoL FreeBSD 11/12 support [\#718](https://github.com/voxpupuli/puppet-python/pull/718) ([smortex](https://github.com/smortex)) +- Drop EoL Ubuntu 18.04 support [\#710](https://github.com/voxpupuli/puppet-python/pull/710) ([bastelfreak](https://github.com/bastelfreak)) +- Drop EoL CentOS 7/8 support [\#709](https://github.com/voxpupuli/puppet-python/pull/709) ([bastelfreak](https://github.com/bastelfreak)) +- Drop EoL Debian 10 support [\#708](https://github.com/voxpupuli/puppet-python/pull/708) ([bastelfreak](https://github.com/bastelfreak)) + +**Fixed bugs:** + +- Fix bootstrapping `python::pyvenv` when Python is not installed [\#716](https://github.com/voxpupuli/puppet-python/pull/716) ([smortex](https://github.com/smortex)) + +## [v7.4.0](https://github.com/voxpupuli/puppet-python/tree/v7.4.0) (2024-11-27) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v7.3.0...v7.4.0) + +**Implemented enhancements:** + +- Add Ubuntu 24.04 support [\#707](https://github.com/voxpupuli/puppet-python/pull/707) ([bastelfreak](https://github.com/bastelfreak)) +- pyvenv: Harden datatype for `$environment` [\#704](https://github.com/voxpupuli/puppet-python/pull/704) ([bastelfreak](https://github.com/bastelfreak)) +- simplify packages version detection [\#703](https://github.com/voxpupuli/puppet-python/pull/703) ([maxadamo](https://github.com/maxadamo)) +- Add support for FreeBSD 14 [\#694](https://github.com/voxpupuli/puppet-python/pull/694) ([smortex](https://github.com/smortex)) + +**Fixed bugs:** + +- `python::pip`'s `notreallyaversion` is not compatible with latest `pip` \(\>= 24.1\) due to changed output \(again\) [\#695](https://github.com/voxpupuli/puppet-python/issues/695) +- Fix `python::pip` - use valid, but highly unlikely package version [\#696](https://github.com/voxpupuli/puppet-python/pull/696) ([acojocariu1-godaddy](https://github.com/acojocariu1-godaddy)) + +**Merged pull requests:** + +- CI: Dont pin pip to an outdated version and don't force python3 package installation [\#706](https://github.com/voxpupuli/puppet-python/pull/706) ([bastelfreak](https://github.com/bastelfreak)) + +## [v7.3.0](https://github.com/voxpupuli/puppet-python/tree/v7.3.0) (2024-02-08) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v7.2.0...v7.3.0) + +**Implemented enhancements:** + +- Add python\_path to pyvenv class [\#686](https://github.com/voxpupuli/puppet-python/pull/686) ([wmellema](https://github.com/wmellema)) + +## [v7.2.0](https://github.com/voxpupuli/puppet-python/tree/v7.2.0) (2024-01-01) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v7.1.0...v7.2.0) + +**Implemented enhancements:** + +- Add Support for RedHat/CentOS 9 [\#676](https://github.com/voxpupuli/puppet-python/pull/676) ([ValdrinLushaj](https://github.com/ValdrinLushaj)) + +**Fixed bugs:** + +- Make latest version detection portable [\#682](https://github.com/voxpupuli/puppet-python/pull/682) ([smortex](https://github.com/smortex)) + +## [v7.1.0](https://github.com/voxpupuli/puppet-python/tree/v7.1.0) (2023-11-29) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v7.0.0...v7.1.0) + +**Implemented enhancements:** + +- Add Ubuntu 22.04 support [\#679](https://github.com/voxpupuli/puppet-python/pull/679) ([bastelfreak](https://github.com/bastelfreak)) +- Add Debian 12 support [\#678](https://github.com/voxpupuli/puppet-python/pull/678) ([bastelfreak](https://github.com/bastelfreak)) +- Add OracleLinux/AlmaLinux/Rocky support [\#677](https://github.com/voxpupuli/puppet-python/pull/677) ([JakeTRogers](https://github.com/JakeTRogers)) +- Bump puppet-epel to allow 5.x [\#674](https://github.com/voxpupuli/puppet-python/pull/674) ([dandunckelman](https://github.com/dandunckelman)) +- Allow to skip management of python dev package [\#669](https://github.com/voxpupuli/puppet-python/pull/669) ([smortex](https://github.com/smortex)) + +**Fixed bugs:** + +- Use the wheel group by default on FreeBSD [\#672](https://github.com/voxpupuli/puppet-python/pull/672) ([smortex](https://github.com/smortex)) +- If user declares their requirements.txt in Puppet, don't skip pip installation in python::requirements [\#619](https://github.com/voxpupuli/puppet-python/pull/619) ([acullenn](https://github.com/acullenn)) + +## [v7.0.0](https://github.com/voxpupuli/puppet-python/tree/v7.0.0) (2023-07-27) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v6.4.0...v7.0.0) + +**Breaking changes:** + +- Drop Ubuntu 16.04 \(EOL\) [\#659](https://github.com/voxpupuli/puppet-python/pull/659) ([smortex](https://github.com/smortex)) +- Drop Debian 9 \(EOL\) [\#658](https://github.com/voxpupuli/puppet-python/pull/658) ([smortex](https://github.com/smortex)) +- Drop Puppet 6 support [\#656](https://github.com/voxpupuli/puppet-python/pull/656) ([bastelfreak](https://github.com/bastelfreak)) + +**Implemented enhancements:** + +- Add Puppet 8 support [\#664](https://github.com/voxpupuli/puppet-python/pull/664) ([bastelfreak](https://github.com/bastelfreak)) +- puppetlabs/stdlib: Allow 9.x [\#663](https://github.com/voxpupuli/puppet-python/pull/663) ([bastelfreak](https://github.com/bastelfreak)) + +**Fixed bugs:** + +- Fix pip installation on Gentoo [\#651](https://github.com/voxpupuli/puppet-python/pull/651) ([puppetjoy](https://github.com/puppetjoy)) + +## [v6.4.0](https://github.com/voxpupuli/puppet-python/tree/v6.4.0) (2022-11-06) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v6.3.0...v6.4.0) + +**Implemented enhancements:** + +- move static data from params.pp to init.pp [\#648](https://github.com/voxpupuli/puppet-python/pull/648) ([bastelfreak](https://github.com/bastelfreak)) +- cleanup acceptance tests [\#647](https://github.com/voxpupuli/puppet-python/pull/647) ([bastelfreak](https://github.com/bastelfreak)) +- Implement Arch Linux support [\#642](https://github.com/voxpupuli/puppet-python/pull/642) ([bastelfreak](https://github.com/bastelfreak)) +- Add prompt parameter to python::pyvenv [\#641](https://github.com/voxpupuli/puppet-python/pull/641) ([ookisan](https://github.com/ookisan)) +- Add extra\_index parameter to python::pip [\#640](https://github.com/voxpupuli/puppet-python/pull/640) ([ookisan](https://github.com/ookisan)) + +**Fixed bugs:** + +- use legacy pip resolver for pip versions \< 21.1 \> 20.2.4 [\#639](https://github.com/voxpupuli/puppet-python/pull/639) ([saz](https://github.com/saz)) + +## [v6.3.0](https://github.com/voxpupuli/puppet-python/tree/v6.3.0) (2022-07-18) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v6.2.1...v6.3.0) + +**Implemented enhancements:** + +- allow puppet/epel v4 [\#634](https://github.com/voxpupuli/puppet-python/pull/634) ([vchepkov](https://github.com/vchepkov)) +- cleanup references to obsolete virtualenv parameter/command [\#633](https://github.com/voxpupuli/puppet-python/pull/633) ([vchepkov](https://github.com/vchepkov)) + +**Fixed bugs:** + +- Add python-venv installation [\#579](https://github.com/voxpupuli/puppet-python/pull/579) ([crazymind1337](https://github.com/crazymind1337)) + +## [v6.2.1](https://github.com/voxpupuli/puppet-python/tree/v6.2.1) (2021-12-10) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v6.2.0...v6.2.1) + +**Fixed bugs:** + +- Correct python::pip::environment parameter example [\#629](https://github.com/voxpupuli/puppet-python/pull/629) ([traylenator](https://github.com/traylenator)) +- gunicorn: RHEL8 has a different package name [\#628](https://github.com/voxpupuli/puppet-python/pull/628) ([yakatz](https://github.com/yakatz)) +- Remove duplicate arguments in the pip install command [\#627](https://github.com/voxpupuli/puppet-python/pull/627) ([zanyou](https://github.com/zanyou)) + +**Closed issues:** + +- Package with provider pip3 tries installing every run. [\#626](https://github.com/voxpupuli/puppet-python/issues/626) + +**Merged pull requests:** + +- modulesync 5.1.0 & puppet-lint: fix params\_empty\_string\_assignment [\#631](https://github.com/voxpupuli/puppet-python/pull/631) ([bastelfreak](https://github.com/bastelfreak)) + +## [v6.2.0](https://github.com/voxpupuli/puppet-python/tree/v6.2.0) (2021-08-26) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v6.1.0...v6.2.0) + +**Implemented enhancements:** + +- Add support for Debian 11 [\#621](https://github.com/voxpupuli/puppet-python/pull/621) ([smortex](https://github.com/smortex)) + +**Merged pull requests:** + +- Allow stdlib 8.0.0 [\#622](https://github.com/voxpupuli/puppet-python/pull/622) ([smortex](https://github.com/smortex)) + +## [v6.1.0](https://github.com/voxpupuli/puppet-python/tree/v6.1.0) (2021-06-05) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v6.0.1...v6.1.0) + +Due to a bug in the release pipeline, release v6.0.1 didn't make it to the forge. v6.0.2 just contains [modulesync 4.1.0](https://github.com/voxpupuli/modulesync_config/blob/master/CHANGELOG.md#410-2021-04-03) patch. + +**Implemented enhancements:** + +- Add support for FreeBSD [\#612](https://github.com/voxpupuli/puppet-python/pull/612) ([smortex](https://github.com/smortex)) + +**Closed issues:** + +- Can't uninstall pip package because of duplicate variable name [\#532](https://github.com/voxpupuli/puppet-python/issues/532) +- audit metaparameter is deprecated [\#375](https://github.com/voxpupuli/puppet-python/issues/375) + +## [v6.0.1](https://github.com/voxpupuli/puppet-python/tree/v6.0.1) (2021-04-29) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v6.0.0...v6.0.1) + +**Fixed bugs:** + +- Use $real\_pkgname for pip uninstall command [\#607](https://github.com/voxpupuli/puppet-python/pull/607) ([brabiega](https://github.com/brabiega)) + +**Closed issues:** + +- Pip uninstall does not support pkgname variable [\#606](https://github.com/voxpupuli/puppet-python/issues/606) + +## [v6.0.0](https://github.com/voxpupuli/puppet-python/tree/v6.0.0) (2021-04-03) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v5.0.0...v6.0.0) + +**Breaking changes:** + +- Drop Puppet 5 support [\#603](https://github.com/voxpupuli/puppet-python/pull/603) ([bastelfreak](https://github.com/bastelfreak)) +- Drop python::virtualenv defined resource [\#596](https://github.com/voxpupuli/puppet-python/pull/596) ([bastelfreak](https://github.com/bastelfreak)) +- Set default python to 3 [\#595](https://github.com/voxpupuli/puppet-python/pull/595) ([bastelfreak](https://github.com/bastelfreak)) + +**Implemented enhancements:** + +- Add the possibility to specify the pip version in virtual envs [\#599](https://github.com/voxpupuli/puppet-python/pull/599) ([SaschaDoering](https://github.com/SaschaDoering)) +- Add Support for RedHat/CentOS 8 [\#594](https://github.com/voxpupuli/puppet-python/pull/594) ([treydock](https://github.com/treydock)) +- enhance pyvenv tests [\#590](https://github.com/voxpupuli/puppet-python/pull/590) ([bastelfreak](https://github.com/bastelfreak)) +- Add Debian 10 support [\#573](https://github.com/voxpupuli/puppet-python/pull/573) ([bastelfreak](https://github.com/bastelfreak)) + +**Fixed bugs:** + +- Fix python::virtualenv to allow virtualenv to not require absolute path [\#592](https://github.com/voxpupuli/puppet-python/pull/592) ([treydock](https://github.com/treydock)) +- python::gunicorn: Fix typo in datatype [\#585](https://github.com/voxpupuli/puppet-python/pull/585) ([bastelfreak](https://github.com/bastelfreak)) + +**Closed issues:** + +- Setting Pip Version for Virtual Environments [\#559](https://github.com/voxpupuli/puppet-python/issues/559) +- Can't install this using librarian-puppet [\#406](https://github.com/voxpupuli/puppet-python/issues/406) + +**Merged pull requests:** + +- puppetlabs/stdlib: Allow 7.x [\#604](https://github.com/voxpupuli/puppet-python/pull/604) ([bastelfreak](https://github.com/bastelfreak)) +- Support Puppet 7 [\#602](https://github.com/voxpupuli/puppet-python/pull/602) ([mattock](https://github.com/mattock)) +- Support Ubuntu 20.04 [\#593](https://github.com/voxpupuli/puppet-python/pull/593) ([treydock](https://github.com/treydock)) +- Add pyvenv acceptance test [\#588](https://github.com/voxpupuli/puppet-python/pull/588) ([bastelfreak](https://github.com/bastelfreak)) +- General linting/indent/syntax [\#580](https://github.com/voxpupuli/puppet-python/pull/580) ([crazymind1337](https://github.com/crazymind1337)) +- Use Python 3 for virtualenv tests [\#575](https://github.com/voxpupuli/puppet-python/pull/575) ([waipeng](https://github.com/waipeng)) +- Add acceptance test for managing Python 3 [\#572](https://github.com/voxpupuli/puppet-python/pull/572) ([bastelfreak](https://github.com/bastelfreak)) + +## [v5.0.0](https://github.com/voxpupuli/puppet-python/tree/v5.0.0) (2020-09-23) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v4.1.1...v5.0.0) + +**Breaking changes:** + +- Drop CentOS 6 support [\#566](https://github.com/voxpupuli/puppet-python/pull/566) ([bastelfreak](https://github.com/bastelfreak)) +- Drop EOL Debian 8 [\#563](https://github.com/voxpupuli/puppet-python/pull/563) ([bastelfreak](https://github.com/bastelfreak)) + +**Fixed bugs:** + +- Fix undefined method error in facts [\#555](https://github.com/voxpupuli/puppet-python/pull/555) ([wiebe](https://github.com/wiebe)) + +**Closed issues:** + +- "warning: already initialized constant PIP\_VERSION" when upgrading to agent 6.15.0 [\#553](https://github.com/voxpupuli/puppet-python/issues/553) +- "undefined method \[\]" in pip\_version and virtualenv\_version facts [\#527](https://github.com/voxpupuli/puppet-python/issues/527) +- Deprecation Warnings - Mocha mock\_with [\#432](https://github.com/voxpupuli/puppet-python/issues/432) + +**Merged pull requests:** + +- delete legacy nodesets [\#562](https://github.com/voxpupuli/puppet-python/pull/562) ([bastelfreak](https://github.com/bastelfreak)) +- modulesync 3.0.0 & puppet-lint updates [\#556](https://github.com/voxpupuli/puppet-python/pull/556) ([bastelfreak](https://github.com/bastelfreak)) +- Convert unit tests to rspec rather than 'mocha' [\#554](https://github.com/voxpupuli/puppet-python/pull/554) ([KeithWard](https://github.com/KeithWard)) + +## [v4.1.1](https://github.com/voxpupuli/puppet-python/tree/v4.1.1) (2020-04-30) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v4.1.0...v4.1.1) + +**Fixed bugs:** + +- Fixes for virtualenv\_version fact when virtualenv \> 20.x [\#537](https://github.com/voxpupuli/puppet-python/pull/537) ([pjonesIDBS](https://github.com/pjonesIDBS)) + +## [v4.1.0](https://github.com/voxpupuli/puppet-python/tree/v4.1.0) (2020-04-26) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v4.0.0...v4.1.0) + +**Implemented enhancements:** + +- Add option for not managing python and virtualenv packages. [\#500](https://github.com/voxpupuli/puppet-python/pull/500) ([tdukaric](https://github.com/tdukaric)) + +**Fixed bugs:** + +- Wrong pip referenced inside virtualenv [\#505](https://github.com/voxpupuli/puppet-python/issues/505) +- CentOS: Fix ordering dependency [\#546](https://github.com/voxpupuli/puppet-python/pull/546) ([bastelfreak](https://github.com/bastelfreak)) +- switch from stahnma/epel to puppet/epel / Ubuntu 16.04: Execute tests on Python 3 [\#545](https://github.com/voxpupuli/puppet-python/pull/545) ([bastelfreak](https://github.com/bastelfreak)) +- Remove resource collector overriding pip\_provider [\#511](https://github.com/voxpupuli/puppet-python/pull/511) ([jplindquist](https://github.com/jplindquist)) + +**Closed issues:** + +- python3.6+ venv proper installation command [\#533](https://github.com/voxpupuli/puppet-python/issues/533) +- Virtualenv doesn't install with the right python [\#384](https://github.com/voxpupuli/puppet-python/issues/384) + +**Merged pull requests:** + +- Use voxpupuli-acceptance [\#543](https://github.com/voxpupuli/puppet-python/pull/543) ([ekohl](https://github.com/ekohl)) +- update repo links to https [\#531](https://github.com/voxpupuli/puppet-python/pull/531) ([bastelfreak](https://github.com/bastelfreak)) + +## [v4.0.0](https://github.com/voxpupuli/puppet-python/tree/v4.0.0) (2019-12-10) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v3.0.1...v4.0.0) + +**Breaking changes:** + +- Drop Ubuntu 14.04 support [\#515](https://github.com/voxpupuli/puppet-python/pull/515) ([bastelfreak](https://github.com/bastelfreak)) + +**Fixed bugs:** + +- Allow python::version to contain a point \(e.g. python3.7\) [\#523](https://github.com/voxpupuli/puppet-python/pull/523) ([baurmatt](https://github.com/baurmatt)) +- Fix duplicate declaration for python-venv package [\#518](https://github.com/voxpupuli/puppet-python/pull/518) ([baurmatt](https://github.com/baurmatt)) +- Use shell to exec pip commands by default [\#498](https://github.com/voxpupuli/puppet-python/pull/498) ([jamebus](https://github.com/jamebus)) +- Fix a reassigned variable [\#497](https://github.com/voxpupuli/puppet-python/pull/497) ([SaschaDoering](https://github.com/SaschaDoering)) + +**Closed issues:** + +- Duplicate declaration for python$version-venv [\#517](https://github.com/voxpupuli/puppet-python/issues/517) +- Python 3.6 on ubuntu 18.04 not working [\#508](https://github.com/voxpupuli/puppet-python/issues/508) +- Module does not recognize Debian python package name [\#506](https://github.com/voxpupuli/puppet-python/issues/506) +- Gunicorn via Hiera [\#499](https://github.com/voxpupuli/puppet-python/issues/499) +- Python::Pip fails if $ensure='absent' [\#496](https://github.com/voxpupuli/puppet-python/issues/496) + +**Merged pull requests:** + +- Clean up requirements\_spec.rb [\#521](https://github.com/voxpupuli/puppet-python/pull/521) ([ekohl](https://github.com/ekohl)) +- Switch to int\_date for acceptance test [\#519](https://github.com/voxpupuli/puppet-python/pull/519) ([baurmatt](https://github.com/baurmatt)) +- Upgrade pip and setuptools on venv creation [\#516](https://github.com/voxpupuli/puppet-python/pull/516) ([baurmatt](https://github.com/baurmatt)) +- Recognize Debian python package name, fixes: \#506 [\#514](https://github.com/voxpupuli/puppet-python/pull/514) ([lordievader](https://github.com/lordievader)) +- Clean up acceptance spec helper [\#512](https://github.com/voxpupuli/puppet-python/pull/512) ([ekohl](https://github.com/ekohl)) +- Add badges to README [\#495](https://github.com/voxpupuli/puppet-python/pull/495) ([alexjfisher](https://github.com/alexjfisher)) + +## [v3.0.1](https://github.com/voxpupuli/puppet-python/tree/v3.0.1) (2019-06-13) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v3.0.0...v3.0.1) + +**Merged pull requests:** + +- Fix travis secret [\#493](https://github.com/voxpupuli/puppet-python/pull/493) ([alexjfisher](https://github.com/alexjfisher)) + +## [v3.0.0](https://github.com/voxpupuli/puppet-python/tree/v3.0.0) (2019-06-13) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v2.2.2...v3.0.0) + +**Breaking changes:** + +- modulesync 2.5.1 and drop Puppet 4 [\#467](https://github.com/voxpupuli/puppet-python/pull/467) ([bastelfreak](https://github.com/bastelfreak)) + +**Implemented enhancements:** + +- Allow HTTP\_PROXY on bootstrap. [\#488](https://github.com/voxpupuli/puppet-python/pull/488) ([pillarsdotnet](https://github.com/pillarsdotnet)) +- Modern pip can install wheels without wheel installed [\#483](https://github.com/voxpupuli/puppet-python/pull/483) ([asottile](https://github.com/asottile)) +- Allow arbitrary pip providers [\#480](https://github.com/voxpupuli/puppet-python/pull/480) ([emmatyping](https://github.com/emmatyping)) +- Add manage\_scl boolean to control managing SCL [\#464](https://github.com/voxpupuli/puppet-python/pull/464) ([bodgit](https://github.com/bodgit)) +- Allow pip to work in AIX systems [\#461](https://github.com/voxpupuli/puppet-python/pull/461) ([feltra](https://github.com/feltra)) +- move pip bootstrap into a seperate class [\#460](https://github.com/voxpupuli/puppet-python/pull/460) ([feltra](https://github.com/feltra)) +- Allow custom python versions and environments [\#451](https://github.com/voxpupuli/puppet-python/pull/451) ([jradmacher](https://github.com/jradmacher)) + +**Fixed bugs:** + +- Installing from git repo runs install on every Puppet run [\#193](https://github.com/voxpupuli/puppet-python/issues/193) +- Fix python::pip installing $editable VCS packages every Puppet run [\#491](https://github.com/voxpupuli/puppet-python/pull/491) ([mlow](https://github.com/mlow)) +- Fix $subscribe overloading [\#490](https://github.com/voxpupuli/puppet-python/pull/490) ([nward](https://github.com/nward)) +- Fix version-check. [\#489](https://github.com/voxpupuli/puppet-python/pull/489) ([pillarsdotnet](https://github.com/pillarsdotnet)) +- Update version validation [\#472](https://github.com/voxpupuli/puppet-python/pull/472) ([bodgit](https://github.com/bodgit)) +- Normalize Python version in `python::pyvenv` [\#466](https://github.com/voxpupuli/puppet-python/pull/466) ([thaiphv](https://github.com/thaiphv)) +- Fix Ubuntu bionic package installation [\#450](https://github.com/voxpupuli/puppet-python/pull/450) ([ekohl](https://github.com/ekohl)) +- Fix $filename and $mode types in python::dotfile [\#446](https://github.com/voxpupuli/puppet-python/pull/446) ([gdubicki](https://github.com/gdubicki)) +- Stop using 'pip search' for ensure =\> latest [\#434](https://github.com/voxpupuli/puppet-python/pull/434) ([gdubicki](https://github.com/gdubicki)) + +**Closed issues:** + +- Should set permissive umask before exec. [\#486](https://github.com/voxpupuli/puppet-python/issues/486) +- When updating pip via puppet-python, an error occurs. [\#484](https://github.com/voxpupuli/puppet-python/issues/484) +- Not possible to install Python-3 with this module [\#482](https://github.com/voxpupuli/puppet-python/issues/482) +- Cannot install pre-commit pip. [\#481](https://github.com/voxpupuli/puppet-python/issues/481) +- Allow the use of pip3.4 and pip3.6 [\#476](https://github.com/voxpupuli/puppet-python/issues/476) +- python3\_version fact doesn't work on SCL [\#475](https://github.com/voxpupuli/puppet-python/issues/475) +- missing https\_proxy when using https pypi of other https indexes [\#473](https://github.com/voxpupuli/puppet-python/issues/473) +- Unable to use SCL version [\#471](https://github.com/voxpupuli/puppet-python/issues/471) +- Variable $subscribe shoud not be overwritten [\#470](https://github.com/voxpupuli/puppet-python/issues/470) +- Add switch to not manage SCL setup [\#463](https://github.com/voxpupuli/puppet-python/issues/463) +- update dependencies to stdlib \>= 4.19 [\#458](https://github.com/voxpupuli/puppet-python/issues/458) +- Impossible to use version number in Ubuntu 16.04 [\#448](https://github.com/voxpupuli/puppet-python/issues/448) +- Documentation still includes the deprecated stankevich-python module for installation [\#441](https://github.com/voxpupuli/puppet-python/issues/441) +- No puppet strings docs/class reference docs [\#439](https://github.com/voxpupuli/puppet-python/issues/439) +- python::pip ensure =\> latest triggers refresh on each puppet run for some packages [\#433](https://github.com/voxpupuli/puppet-python/issues/433) +- Support for Python3.6 executables [\#420](https://github.com/voxpupuli/puppet-python/issues/420) +- Python 3 + virtualenv + centos 7 not working [\#354](https://github.com/voxpupuli/puppet-python/issues/354) +- --no-use-wheel argument fails requirement installation [\#173](https://github.com/voxpupuli/puppet-python/issues/173) + +**Merged pull requests:** + +- 486 Set permissive umask. [\#487](https://github.com/voxpupuli/puppet-python/pull/487) ([pillarsdotnet](https://github.com/pillarsdotnet)) +- Update `puppetlabs/stdlib` dependency to allow 6.x and require at least 4.19.0 \(where the `fact()` function was introduced\) [\#485](https://github.com/voxpupuli/puppet-python/pull/485) ([pillarsdotnet](https://github.com/pillarsdotnet)) +- Update pip url regex to support 'git+git://\' [\#477](https://github.com/voxpupuli/puppet-python/pull/477) ([gharper](https://github.com/gharper)) +- README.md: remove obsolete and redundant sections [\#453](https://github.com/voxpupuli/puppet-python/pull/453) ([kenyon](https://github.com/kenyon)) +- remove .DS\_Store [\#452](https://github.com/voxpupuli/puppet-python/pull/452) ([kenyon](https://github.com/kenyon)) +- Change default indent to 2 Spaces in .editorconfig [\#449](https://github.com/voxpupuli/puppet-python/pull/449) ([jradmacher](https://github.com/jradmacher)) +- Replace deprecated validate\_\* functions [\#443](https://github.com/voxpupuli/puppet-python/pull/443) ([baurmatt](https://github.com/baurmatt)) +- Update modules with defined types for variables as described in docs/Add reference.md [\#440](https://github.com/voxpupuli/puppet-python/pull/440) ([danquack](https://github.com/danquack)) + +## [v2.2.2](https://github.com/voxpupuli/puppet-python/tree/v2.2.2) (2018-10-20) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v2.2.0...v2.2.2) + +**Closed issues:** + +- Make a new release to the forge [\#437](https://github.com/voxpupuli/puppet-python/issues/437) + +## [v2.2.0](https://github.com/voxpupuli/puppet-python/tree/v2.2.0) (2018-10-19) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v2.1.1...v2.2.0) + +**Implemented enhancements:** + +- Add Ubuntu 18.04 support [\#399](https://github.com/voxpupuli/puppet-python/issues/399) +- Add ubuntu 18.04 support [\#426](https://github.com/voxpupuli/puppet-python/pull/426) ([danquack](https://github.com/danquack)) + +**Fixed bugs:** + +- Pip: freeze all to be able to control setuptools, distribute, wheel, pip [\#418](https://github.com/voxpupuli/puppet-python/pull/418) ([Feandil](https://github.com/Feandil)) + +**Merged pull requests:** + +- modulesync 2.2.0 and allow puppet 6.x [\#435](https://github.com/voxpupuli/puppet-python/pull/435) ([bastelfreak](https://github.com/bastelfreak)) + +## [v2.1.1](https://github.com/voxpupuli/puppet-python/tree/v2.1.1) (2018-08-20) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v2.1.0...v2.1.1) + +**Fixed bugs:** + +- CentOS Acceptance testing, and more.... [\#424](https://github.com/voxpupuli/puppet-python/pull/424) ([danquack](https://github.com/danquack)) + +**Closed issues:** + +- enable acceptance tests for centos [\#423](https://github.com/voxpupuli/puppet-python/issues/423) +- Run acceptance tests on travis [\#402](https://github.com/voxpupuli/puppet-python/issues/402) +- \[CentOS7 + Python 2.7\]: python::virtualenv fails [\#365](https://github.com/voxpupuli/puppet-python/issues/365) + +**Merged pull requests:** + +- Updated README for python::virtualenv [\#421](https://github.com/voxpupuli/puppet-python/pull/421) ([tprestegard](https://github.com/tprestegard)) +- enable acceptance tests [\#419](https://github.com/voxpupuli/puppet-python/pull/419) ([bastelfreak](https://github.com/bastelfreak)) + +## [v2.1.0](https://github.com/voxpupuli/puppet-python/tree/v2.1.0) (2018-07-09) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/v2.0.0...v2.1.0) + +**Implemented enhancements:** + +- support for providing pip3 provider w/ tests. Modified readme 4 examples [\#414](https://github.com/voxpupuli/puppet-python/pull/414) ([danquack](https://github.com/danquack)) + +**Closed issues:** + +- How to deploy pip package to rhscl python34 [\#377](https://github.com/voxpupuli/puppet-python/issues/377) +- CentOS 7 with Python3 does not work [\#303](https://github.com/voxpupuli/puppet-python/issues/303) + +## [v2.0.0](https://github.com/voxpupuli/puppet-python/tree/v2.0.0) (2018-06-25) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.19.0...v2.0.0) + +**Breaking changes:** + +- Drop Puppet \<4.10 support [\#401](https://github.com/voxpupuli/puppet-python/issues/401) +- Drop Ruby 1.8 support [\#400](https://github.com/voxpupuli/puppet-python/issues/400) +- Drop EOL operatingsystem: Ubuntu 12.04 [\#397](https://github.com/voxpupuli/puppet-python/issues/397) +- Drop EOL operatingsystem: Ubuntu 10.04 [\#396](https://github.com/voxpupuli/puppet-python/issues/396) +- Drop EOL operatingsystem: Debian 7 [\#395](https://github.com/voxpupuli/puppet-python/issues/395) +- Drop EOL operatingsystem: Debian 6 [\#394](https://github.com/voxpupuli/puppet-python/issues/394) +- Drop EOL operatingsystem: CentOS 5 [\#393](https://github.com/voxpupuli/puppet-python/issues/393) + +**Implemented enhancements:** + +- Add Debian 9 Support [\#398](https://github.com/voxpupuli/puppet-python/issues/398) +- Add support for Anaconda [\#409](https://github.com/voxpupuli/puppet-python/pull/409) ([jb-abbadie](https://github.com/jb-abbadie)) +- Add umask parameter to pip execs [\#368](https://github.com/voxpupuli/puppet-python/pull/368) ([jstaph](https://github.com/jstaph)) + +**Closed issues:** + +- Activity on this project? [\#371](https://github.com/voxpupuli/puppet-python/issues/371) +- module is not compatible with setuptools v34 [\#361](https://github.com/voxpupuli/puppet-python/issues/361) +- So many Warnings [\#351](https://github.com/voxpupuli/puppet-python/issues/351) +- Spec tests time out with ruby 2.3.1 [\#336](https://github.com/voxpupuli/puppet-python/issues/336) +- how to used pip2.7 as provider in RHEL6 [\#290](https://github.com/voxpupuli/puppet-python/issues/290) +- Cannot determine if a package named in the form packagename\[subfeature\] is installed. [\#284](https://github.com/voxpupuli/puppet-python/issues/284) +- Pip install runs on every puppet run. [\#218](https://github.com/voxpupuli/puppet-python/issues/218) + +**Merged pull requests:** + +- Fix Python version regex in install.pp [\#410](https://github.com/voxpupuli/puppet-python/pull/410) ([fklajn](https://github.com/fklajn)) +- Remove docker nodesets [\#408](https://github.com/voxpupuli/puppet-python/pull/408) ([bastelfreak](https://github.com/bastelfreak)) +- Update README compatibility section [\#405](https://github.com/voxpupuli/puppet-python/pull/405) ([sandra-thieme](https://github.com/sandra-thieme)) +- add secret for forge deployment via travis [\#404](https://github.com/voxpupuli/puppet-python/pull/404) ([bastelfreak](https://github.com/bastelfreak)) +- Add deprecation notice for the old repository [\#403](https://github.com/voxpupuli/puppet-python/pull/403) ([stankevich](https://github.com/stankevich)) +- virtualenv.pp: make creation of $venv\_dir optional [\#391](https://github.com/voxpupuli/puppet-python/pull/391) ([daylicron](https://github.com/daylicron)) +- add pip support for setuptools extras [\#390](https://github.com/voxpupuli/puppet-python/pull/390) ([bryangwilliam](https://github.com/bryangwilliam)) +- Fix pip wheel checks [\#389](https://github.com/voxpupuli/puppet-python/pull/389) ([genebean](https://github.com/genebean)) + +## [1.19.0](https://github.com/voxpupuli/puppet-python/tree/1.19.0) (2018-04-28) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.18.2...1.19.0) + +**Closed issues:** + +- travis build failures since december? [\#372](https://github.com/voxpupuli/puppet-python/issues/372) +- python-pip has been renamed to python2-pip on el7 epel repo [\#348](https://github.com/voxpupuli/puppet-python/issues/348) +- --no-use-wheel renamed to --no-binary :all: in pip 7.0 and newer [\#309](https://github.com/voxpupuli/puppet-python/issues/309) +- duplicate resource [\#259](https://github.com/voxpupuli/puppet-python/issues/259) +- python::virtualenv does not accept the string 'pip' as per the documentation [\#205](https://github.com/voxpupuli/puppet-python/issues/205) + +**Merged pull requests:** + +- fix for latest versions of setuptools and pip [\#388](https://github.com/voxpupuli/puppet-python/pull/388) ([vchepkov](https://github.com/vchepkov)) +- Fix tests: Pin rake for ruby 1.9.3 [\#387](https://github.com/voxpupuli/puppet-python/pull/387) ([waipeng](https://github.com/waipeng)) +- Support virtualenv for Ubuntu 16.04 [\#386](https://github.com/voxpupuli/puppet-python/pull/386) ([waipeng](https://github.com/waipeng)) +- Set virtualenv package name for Debian stretch [\#383](https://github.com/voxpupuli/puppet-python/pull/383) ([sergiik](https://github.com/sergiik)) +- Update gunicorn.pp - Add manage\_config\_dir [\#382](https://github.com/voxpupuli/puppet-python/pull/382) ([epik0r](https://github.com/epik0r)) +- Support latest puppet versions [\#376](https://github.com/voxpupuli/puppet-python/pull/376) ([ghoneycutt](https://github.com/ghoneycutt)) +- Add python release as available facts [\#355](https://github.com/voxpupuli/puppet-python/pull/355) ([jcpunk](https://github.com/jcpunk)) +- Allow hiera config for dotfiles [\#344](https://github.com/voxpupuli/puppet-python/pull/344) ([puppetninja](https://github.com/puppetninja)) +- Ensure value is a string for =~ comparison [\#342](https://github.com/voxpupuli/puppet-python/pull/342) ([ghoneycutt](https://github.com/ghoneycutt)) +- add an alias to the python-dev package [\#334](https://github.com/voxpupuli/puppet-python/pull/334) ([dannygoulder](https://github.com/dannygoulder)) + +## [1.18.2](https://github.com/voxpupuli/puppet-python/tree/1.18.2) (2016-12-12) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.18.1...1.18.2) + +**Closed issues:** + +- EPEL7 python-pip package is called python2-pip; puppet-python won't recognize that it is installed [\#346](https://github.com/voxpupuli/puppet-python/issues/346) + +**Merged pull requests:** + +- Improve support for pip on CentOS7/EPEL [\#347](https://github.com/voxpupuli/puppet-python/pull/347) ([ju5t](https://github.com/ju5t)) + +## [1.18.1](https://github.com/voxpupuli/puppet-python/tree/1.18.1) (2016-12-08) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.18.0...1.18.1) + +**Closed issues:** + +- New release on the forge? [\#339](https://github.com/voxpupuli/puppet-python/issues/339) + +**Merged pull requests:** + +- Fix testing [\#345](https://github.com/voxpupuli/puppet-python/pull/345) ([ghoneycutt](https://github.com/ghoneycutt)) +- Add name of package to pip uninstall command [\#340](https://github.com/voxpupuli/puppet-python/pull/340) ([dontreboot](https://github.com/dontreboot)) +- EPEL only makes sense on RH systems but not Fedora [\#297](https://github.com/voxpupuli/puppet-python/pull/297) ([jcpunk](https://github.com/jcpunk)) + +## [1.18.0](https://github.com/voxpupuli/puppet-python/tree/1.18.0) (2016-10-12) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.17.0...1.18.0) + +**Merged pull requests:** + +- Allow failure for Ruby 2.3.1 [\#337](https://github.com/voxpupuli/puppet-python/pull/337) ([ghoneycutt](https://github.com/ghoneycutt)) +- Add support, tests and documentation for Gentoo [\#335](https://github.com/voxpupuli/puppet-python/pull/335) ([optiz0r](https://github.com/optiz0r)) + +## [1.17.0](https://github.com/voxpupuli/puppet-python/tree/1.17.0) (2016-09-16) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.16.0...1.17.0) + +**Closed issues:** + +- No tags [\#330](https://github.com/voxpupuli/puppet-python/issues/330) + +**Merged pull requests:** + +- Fix unescaped backslash in previous pip list addition [\#332](https://github.com/voxpupuli/puppet-python/pull/332) ([rikwasmus](https://github.com/rikwasmus)) +- Do not try to reinstall packages installed via the OS [\#331](https://github.com/voxpupuli/puppet-python/pull/331) ([rikwasmus](https://github.com/rikwasmus)) + +## [1.16.0](https://github.com/voxpupuli/puppet-python/tree/1.16.0) (2016-09-10) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.15.0...1.16.0) + +**Merged pull requests:** + +- RHSCL Repository installation made optional [\#328](https://github.com/voxpupuli/puppet-python/pull/328) ([diLLec](https://github.com/diLLec)) + +## [1.15.0](https://github.com/voxpupuli/puppet-python/tree/1.15.0) (2016-08-24) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.14.2...1.15.0) + +**Merged pull requests:** + +- Strict vars [\#299](https://github.com/voxpupuli/puppet-python/pull/299) ([ghoneycutt](https://github.com/ghoneycutt)) + +## [1.14.2](https://github.com/voxpupuli/puppet-python/tree/1.14.2) (2016-08-23) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.14.1...1.14.2) + +**Merged pull requests:** + +- Add support for Ruby 2.3.1 [\#326](https://github.com/voxpupuli/puppet-python/pull/326) ([ghoneycutt](https://github.com/ghoneycutt)) + +## [1.14.1](https://github.com/voxpupuli/puppet-python/tree/1.14.1) (2016-08-22) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.14.0...1.14.1) + +**Closed issues:** + +- Not using index when doing pip search for latest [\#321](https://github.com/voxpupuli/puppet-python/issues/321) +- regex for pip explicit version \( ensure =\> '1.0a1' \) broken [\#310](https://github.com/voxpupuli/puppet-python/issues/310) +- python::pip install args needs extra space to separate multiple args [\#162](https://github.com/voxpupuli/puppet-python/issues/162) + +**Merged pull requests:** + +- Fix travis [\#324](https://github.com/voxpupuli/puppet-python/pull/324) ([ghoneycutt](https://github.com/ghoneycutt)) +- Search index when staying at the latest version [\#322](https://github.com/voxpupuli/puppet-python/pull/322) ([mterzo](https://github.com/mterzo)) +- Use a single grep instead of a double pipe [\#320](https://github.com/voxpupuli/puppet-python/pull/320) ([rcalixte](https://github.com/rcalixte)) +- Add "args" option to gunicorn config [\#319](https://github.com/voxpupuli/puppet-python/pull/319) ([kronos-pbrideau](https://github.com/kronos-pbrideau)) +- Patch to support Centos 7 in bootstrap [\#318](https://github.com/voxpupuli/puppet-python/pull/318) ([asasfu](https://github.com/asasfu)) + +## [1.14.0](https://github.com/voxpupuli/puppet-python/tree/1.14.0) (2016-07-20) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.13.0...1.14.0) + +**Merged pull requests:** + +- Fix regex for pip package versions [\#317](https://github.com/voxpupuli/puppet-python/pull/317) ([mdean](https://github.com/mdean)) + +## [1.13.0](https://github.com/voxpupuli/puppet-python/tree/1.13.0) (2016-07-18) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.12.0...1.13.0) + +**Closed issues:** + +- SCL package installation returns an error [\#308](https://github.com/voxpupuli/puppet-python/issues/308) +- Patch Release [\#295](https://github.com/voxpupuli/puppet-python/issues/295) +- Can't install pip3 with Ubuntu [\#287](https://github.com/voxpupuli/puppet-python/issues/287) +- SCL python27: add a workaround for libpython2.7.so.1.0 issue \(LD\_LIBRARY\_PATH\) [\#234](https://github.com/voxpupuli/puppet-python/issues/234) + +**Merged pull requests:** + +- Set gunicorn package name on RedHat family [\#316](https://github.com/voxpupuli/puppet-python/pull/316) ([kronos-pbrideau](https://github.com/kronos-pbrideau)) +- Tweaks to get travis ci tests working again [\#315](https://github.com/voxpupuli/puppet-python/pull/315) ([mbmilligan](https://github.com/mbmilligan)) +- fix pip failing in virtualenv under SCL [\#314](https://github.com/voxpupuli/puppet-python/pull/314) ([mbmilligan](https://github.com/mbmilligan)) +- Ubuntu 16.04 has a + in python -V output at the end of version. [\#313](https://github.com/voxpupuli/puppet-python/pull/313) ([KlavsKlavsen](https://github.com/KlavsKlavsen)) +- use 'version' name specified directly [\#312](https://github.com/voxpupuli/puppet-python/pull/312) ([epleterte](https://github.com/epleterte)) +- Lowercase package name for centos-release-scl [\#304](https://github.com/voxpupuli/puppet-python/pull/304) ([prozach](https://github.com/prozach)) +- Fixed missing comma in \#301 [\#302](https://github.com/voxpupuli/puppet-python/pull/302) ([steverecio](https://github.com/steverecio)) +- Configure workers [\#301](https://github.com/voxpupuli/puppet-python/pull/301) ([steverecio](https://github.com/steverecio)) +- Fix support for Ruby 1.8.7 [\#298](https://github.com/voxpupuli/puppet-python/pull/298) ([ghoneycutt](https://github.com/ghoneycutt)) + +## [1.12.0](https://github.com/voxpupuli/puppet-python/tree/1.12.0) (2016-03-27) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.11.0...1.12.0) + +**Closed issues:** + +- Puppet-lint prints warnings [\#289](https://github.com/voxpupuli/puppet-python/issues/289) +- Release a new version? [\#285](https://github.com/voxpupuli/puppet-python/issues/285) +- pip is installed on every invocation when pip is installed from pip [\#256](https://github.com/voxpupuli/puppet-python/issues/256) + +**Merged pull requests:** + +- Correct use of version param as it relates to package installation [\#293](https://github.com/voxpupuli/puppet-python/pull/293) ([evidex](https://github.com/evidex)) +- Fix linting issues from \#289 [\#292](https://github.com/voxpupuli/puppet-python/pull/292) ([evidex](https://github.com/evidex)) +- bugfix: test if virtualenv\_version is defined [\#288](https://github.com/voxpupuli/puppet-python/pull/288) ([vicinus](https://github.com/vicinus)) +- Fixes \#256 [\#286](https://github.com/voxpupuli/puppet-python/pull/286) ([joshuaspence](https://github.com/joshuaspence)) + +## [1.11.0](https://github.com/voxpupuli/puppet-python/tree/1.11.0) (2016-01-31) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.10.0...1.11.0) + +**Closed issues:** + +- installing virtualenv broken in master [\#271](https://github.com/voxpupuli/puppet-python/issues/271) +- puppet not install the latest version of pip [\#268](https://github.com/voxpupuli/puppet-python/issues/268) +- Call, 'versioncmp' parameter 'a' expects a String value, got Undef [\#262](https://github.com/voxpupuli/puppet-python/issues/262) +- pip install runs every time for packages with underscores in the name [\#258](https://github.com/voxpupuli/puppet-python/issues/258) +- New version release? [\#257](https://github.com/voxpupuli/puppet-python/issues/257) + +**Merged pull requests:** + +- add SCL specific exec\_prefix [\#283](https://github.com/voxpupuli/puppet-python/pull/283) ([iakovgan](https://github.com/iakovgan)) +- python::pip expects \(un\)install-args to be strings [\#282](https://github.com/voxpupuli/puppet-python/pull/282) ([adamcstephens](https://github.com/adamcstephens)) +- Made virtualenv compatible with RHSCL/SCL [\#281](https://github.com/voxpupuli/puppet-python/pull/281) ([chrisfu](https://github.com/chrisfu)) +- Force virtualenv\_version to be a string. [\#280](https://github.com/voxpupuli/puppet-python/pull/280) ([dansajner](https://github.com/dansajner)) +- Update README to reflect actual defaults [\#279](https://github.com/voxpupuli/puppet-python/pull/279) ([ColinHebert](https://github.com/ColinHebert)) +- Add parameter path to pip manifest [\#277](https://github.com/voxpupuli/puppet-python/pull/277) ([BasLangenberg](https://github.com/BasLangenberg)) +- add configurable log level for gunicorn and unit tests [\#275](https://github.com/voxpupuli/puppet-python/pull/275) ([xaniasd](https://github.com/xaniasd)) +- new manage\_requirements argument to address issue 273 [\#274](https://github.com/voxpupuli/puppet-python/pull/274) ([rosenbergj](https://github.com/rosenbergj)) +- bugfix install pip on centos6 using scl [\#270](https://github.com/voxpupuli/puppet-python/pull/270) ([netors](https://github.com/netors)) +- fixed python dev install when using scl [\#269](https://github.com/voxpupuli/puppet-python/pull/269) ([netors](https://github.com/netors)) +- Revert "Update virtualenv\_version.rb" [\#267](https://github.com/voxpupuli/puppet-python/pull/267) ([shivapoudel](https://github.com/shivapoudel)) +- Update virtualenv\_version.rb [\#265](https://github.com/voxpupuli/puppet-python/pull/265) ([shivapoudel](https://github.com/shivapoudel)) +- Update params.pp [\#263](https://github.com/voxpupuli/puppet-python/pull/263) ([philippeback](https://github.com/philippeback)) +- Bug virtualenv instead of virtualenv-$version [\#261](https://github.com/voxpupuli/puppet-python/pull/261) ([ghost](https://github.com/ghost)) +- Addressing stankevich/puppet-python issue \#258. [\#260](https://github.com/voxpupuli/puppet-python/pull/260) ([rpocase](https://github.com/rpocase)) + +## [1.10.0](https://github.com/voxpupuli/puppet-python/tree/1.10.0) (2015-10-29) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.8...1.10.0) + +**Closed issues:** + +- known puppet bug on CentOS/RHEL 6/7 [\#225](https://github.com/voxpupuli/puppet-python/issues/225) + +**Merged pull requests:** + +- RedHat has different virtualenv packages for different pythons [\#255](https://github.com/voxpupuli/puppet-python/pull/255) ([adamcstephens](https://github.com/adamcstephens)) +- Create symlink for pip-python with pip provider [\#254](https://github.com/voxpupuli/puppet-python/pull/254) ([skpy](https://github.com/skpy)) +- use full path on commands [\#253](https://github.com/voxpupuli/puppet-python/pull/253) ([skpy](https://github.com/skpy)) +- Allow setting a custom index for `python::pip` [\#251](https://github.com/voxpupuli/puppet-python/pull/251) ([joshuaspence](https://github.com/joshuaspence)) + +## [1.9.8](https://github.com/voxpupuli/puppet-python/tree/1.9.8) (2015-09-19) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.7...1.9.8) + +**Closed issues:** + +- Unable to install pip using pip provider [\#243](https://github.com/voxpupuli/puppet-python/issues/243) +- Not possible to install latest version of Python [\#240](https://github.com/voxpupuli/puppet-python/issues/240) + +**Merged pull requests:** + +- Fix RSpec deprecated messages [\#250](https://github.com/voxpupuli/puppet-python/pull/250) ([tremblaysimon](https://github.com/tremblaysimon)) +- Minor improvement for bootstrapped pip installation [\#249](https://github.com/voxpupuli/puppet-python/pull/249) ([joshuaspence](https://github.com/joshuaspence)) +- Fix an issue with gunicorn [\#248](https://github.com/voxpupuli/puppet-python/pull/248) ([joshuaspence](https://github.com/joshuaspence)) +- Support group parameter for python::pip resource [\#247](https://github.com/voxpupuli/puppet-python/pull/247) ([tremblaysimon](https://github.com/tremblaysimon)) +- Various tidying up [\#246](https://github.com/voxpupuli/puppet-python/pull/246) ([joshuaspence](https://github.com/joshuaspence)) +- Bootstrap pip installation [\#244](https://github.com/voxpupuli/puppet-python/pull/244) ([joshuaspence](https://github.com/joshuaspence)) +- Various tidying up [\#242](https://github.com/voxpupuli/puppet-python/pull/242) ([joshuaspence](https://github.com/joshuaspence)) +- Allow custom versions to be installed [\#241](https://github.com/voxpupuli/puppet-python/pull/241) ([joshuaspence](https://github.com/joshuaspence)) +- Check that we have results before returning a value [\#238](https://github.com/voxpupuli/puppet-python/pull/238) ([zachfi](https://github.com/zachfi)) +- Adjust test code to pass syntax checker [\#237](https://github.com/voxpupuli/puppet-python/pull/237) ([fluential](https://github.com/fluential)) + +## [1.9.7](https://github.com/voxpupuli/puppet-python/tree/1.9.7) (2015-08-21) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.6...1.9.7) + +**Closed issues:** + +- Exec\<| tag == 'python-virtualenv' |\> changes and breaks the API [\#230](https://github.com/voxpupuli/puppet-python/issues/230) + +## [1.9.6](https://github.com/voxpupuli/puppet-python/tree/1.9.6) (2015-08-01) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.5...1.9.6) + +**Implemented enhancements:** + +- Manage compilers? [\#118](https://github.com/voxpupuli/puppet-python/issues/118) + +**Fixed bugs:** + +- Dupilicate declaration for requirement file [\#112](https://github.com/voxpupuli/puppet-python/issues/112) +- Resource order restrictions? [\#76](https://github.com/voxpupuli/puppet-python/issues/76) + +**Closed issues:** + +- May attempt to create virtualenvs before package install [\#215](https://github.com/voxpupuli/puppet-python/issues/215) +- virtualenv does not use SCL path from environment [\#212](https://github.com/voxpupuli/puppet-python/issues/212) +- Cut a new release [\#206](https://github.com/voxpupuli/puppet-python/issues/206) +- Doesn't work with python3 [\#204](https://github.com/voxpupuli/puppet-python/issues/204) +- Unable to use virtualenv in Debian Jessie [\#194](https://github.com/voxpupuli/puppet-python/issues/194) +- Support for SCL? [\#189](https://github.com/voxpupuli/puppet-python/issues/189) +- I am trying to install python version 2.7, It doesn't work. [\#185](https://github.com/voxpupuli/puppet-python/issues/185) +- facts broken on all systems [\#184](https://github.com/voxpupuli/puppet-python/issues/184) +- Documentation conflicts itself on whether or not pip must be explictly specified. [\#160](https://github.com/voxpupuli/puppet-python/issues/160) + +## [1.9.5](https://github.com/voxpupuli/puppet-python/tree/1.9.5) (2015-07-05) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.4...1.9.5) + +**Implemented enhancements:** + +- include epel for RedHat [\#115](https://github.com/voxpupuli/puppet-python/issues/115) + +**Closed issues:** + +- python-pip requires EPEL on Redhat/CentOs 6 and 7 [\#196](https://github.com/voxpupuli/puppet-python/issues/196) +- Is it possible to add a support for ipython? [\#195](https://github.com/voxpupuli/puppet-python/issues/195) +- New Feature: Pip installing specific version/tag out of VCS? [\#149](https://github.com/voxpupuli/puppet-python/issues/149) + +## [1.9.4](https://github.com/voxpupuli/puppet-python/tree/1.9.4) (2015-04-17) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.3...1.9.4) + +## [1.9.3](https://github.com/voxpupuli/puppet-python/tree/1.9.3) (2015-04-17) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.2...1.9.3) + +**Closed issues:** + +- Bump Version [\#190](https://github.com/voxpupuli/puppet-python/issues/190) + +## [1.9.2](https://github.com/voxpupuli/puppet-python/tree/1.9.2) (2015-04-17) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.1...1.9.2) + +## [1.9.1](https://github.com/voxpupuli/puppet-python/tree/1.9.1) (2015-03-27) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.9.0...1.9.1) + +**Closed issues:** + +- python::pip hangs with ensure "latest" behind a proxy server [\#170](https://github.com/voxpupuli/puppet-python/issues/170) +- Parameter cwd failed on Exec\[pip\_install\_rpyc\]: cwd must be a fully qualified path [\#165](https://github.com/voxpupuli/puppet-python/issues/165) +- 'require puppet' forces puppet to load pre-maturely [\#163](https://github.com/voxpupuli/puppet-python/issues/163) + +## [1.9.0](https://github.com/voxpupuli/puppet-python/tree/1.9.0) (2015-03-18) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.8.3...1.9.0) + +**Fixed bugs:** + +- virtualenv broken for python3 [\#24](https://github.com/voxpupuli/puppet-python/issues/24) + +**Closed issues:** + +- Missing Gunicorn Parameters [\#167](https://github.com/voxpupuli/puppet-python/issues/167) +- python::pip downgrade to older versions fails [\#150](https://github.com/voxpupuli/puppet-python/issues/150) + +## [1.8.3](https://github.com/voxpupuli/puppet-python/tree/1.8.3) (2015-02-04) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.8.2...1.8.3) + +**Implemented enhancements:** + +- Manage Python related .dotfiles \(for a user\) [\#87](https://github.com/voxpupuli/puppet-python/issues/87) + +## [1.8.2](https://github.com/voxpupuli/puppet-python/tree/1.8.2) (2014-12-03) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.8.1...1.8.2) + +## [1.8.1](https://github.com/voxpupuli/puppet-python/tree/1.8.1) (2014-12-02) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.8.0...1.8.1) + +## [1.8.0](https://github.com/voxpupuli/puppet-python/tree/1.8.0) (2014-11-30) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.16...1.8.0) + +**Closed issues:** + +- Facts cannot be loaded on the first puppet run due to missing rubygems gem [\#153](https://github.com/voxpupuli/puppet-python/issues/153) +- Please publish a new version in forge [\#152](https://github.com/voxpupuli/puppet-python/issues/152) +- Could not retrieve local facts: uninitialized constant Gem [\#151](https://github.com/voxpupuli/puppet-python/issues/151) + +## [1.7.16](https://github.com/voxpupuli/puppet-python/tree/1.7.16) (2014-11-20) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.15...1.7.16) + +**Implemented enhancements:** + +- Installing pip module via github [\#81](https://github.com/voxpupuli/puppet-python/issues/81) + +**Fixed bugs:** + +- python::pip Specifying a local path in url fails [\#91](https://github.com/voxpupuli/puppet-python/issues/91) + +**Closed issues:** + +- puppet-python fails to run on Ubuntu 12.04 [\#145](https://github.com/voxpupuli/puppet-python/issues/145) +- Specify package versions [\#144](https://github.com/voxpupuli/puppet-python/issues/144) +- Compatibility with puppet 2.7? [\#139](https://github.com/voxpupuli/puppet-python/issues/139) + +## [1.7.15](https://github.com/voxpupuli/puppet-python/tree/1.7.15) (2014-11-04) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.14...1.7.15) + +**Closed issues:** + +- Invalid parameter allow\_virtual [\#140](https://github.com/voxpupuli/puppet-python/issues/140) + +## [1.7.14](https://github.com/voxpupuli/puppet-python/tree/1.7.14) (2014-10-30) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.13...1.7.14) + +**Closed issues:** + +- Gunicorn timeout hardcoded in template [\#137](https://github.com/voxpupuli/puppet-python/issues/137) +- Problem with Package defaults warning [\#122](https://github.com/voxpupuli/puppet-python/issues/122) + +## [1.7.13](https://github.com/voxpupuli/puppet-python/tree/1.7.13) (2014-10-22) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.12...1.7.13) + +**Closed issues:** + +- Gunicorn does not allow passing in a list of environment variables [\#132](https://github.com/voxpupuli/puppet-python/issues/132) + +## [1.7.12](https://github.com/voxpupuli/puppet-python/tree/1.7.12) (2014-10-18) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.11...1.7.12) + +**Closed issues:** + +- 'system' or any other version of python doesn't work, doesn't get validated [\#129](https://github.com/voxpupuli/puppet-python/issues/129) +- Could not look up qualified variable `python::install::valid_versions` [\#126](https://github.com/voxpupuli/puppet-python/issues/126) + +## [1.7.11](https://github.com/voxpupuli/puppet-python/tree/1.7.11) (2014-10-11) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.10...1.7.11) + +**Closed issues:** + +- Unable to customize `APP_MODULE` variable in gunicorn template [\#127](https://github.com/voxpupuli/puppet-python/issues/127) +- New release on the Puppet forge [\#125](https://github.com/voxpupuli/puppet-python/issues/125) + +## [1.7.10](https://github.com/voxpupuli/puppet-python/tree/1.7.10) (2014-09-25) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.9...1.7.10) + +**Implemented enhancements:** + +- Add support to python-pyenv [\#113](https://github.com/voxpupuli/puppet-python/issues/113) +- Multiple python versions [\#79](https://github.com/voxpupuli/puppet-python/issues/79) +- use ensure\_packages [\#68](https://github.com/voxpupuli/puppet-python/issues/68) +- Allow extra flags when installing from requirement file [\#66](https://github.com/voxpupuli/puppet-python/issues/66) + +**Fixed bugs:** + +- There is listing of 2 similar depedency [\#111](https://github.com/voxpupuli/puppet-python/issues/111) + +**Closed issues:** + +- pip =\> true, but no python-pip installed on CentOS 6.5 [\#124](https://github.com/voxpupuli/puppet-python/issues/124) +- Could not match $\(ensure at pip.pp:104 [\#123](https://github.com/voxpupuli/puppet-python/issues/123) +- Add the forge module link in github project [\#110](https://github.com/voxpupuli/puppet-python/issues/110) +- Add support to travis-ci build status [\#106](https://github.com/voxpupuli/puppet-python/issues/106) +- Could not start Service\[gunicorn\] [\#83](https://github.com/voxpupuli/puppet-python/issues/83) +- python::pip - empty pkgname is silently ignored [\#67](https://github.com/voxpupuli/puppet-python/issues/67) +- python::requirements interferes with managing requirements.txt if not explicitly in a file resource [\#64](https://github.com/voxpupuli/puppet-python/issues/64) +- Manifests using the module aren't testable in windows [\#27](https://github.com/voxpupuli/puppet-python/issues/27) +- Can not install the same packages in different virtualenvs [\#18](https://github.com/voxpupuli/puppet-python/issues/18) + +## [1.7.9](https://github.com/voxpupuli/puppet-python/tree/1.7.9) (2014-08-10) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.8...1.7.9) + +## [1.7.8](https://github.com/voxpupuli/puppet-python/tree/1.7.8) (2014-07-31) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.7...1.7.8) + +**Closed issues:** + +- Need sudo to install pip [\#96](https://github.com/voxpupuli/puppet-python/issues/96) +- virtualenv / systempkgs and fact precedence [\#94](https://github.com/voxpupuli/puppet-python/issues/94) + +## [1.7.7](https://github.com/voxpupuli/puppet-python/tree/1.7.7) (2014-07-17) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.6...1.7.7) + +**Closed issues:** + +- Need a better way to deal with wheels when using pip [\#53](https://github.com/voxpupuli/puppet-python/issues/53) +- Wheel support? [\#10](https://github.com/voxpupuli/puppet-python/issues/10) + +## [1.7.6](https://github.com/voxpupuli/puppet-python/tree/1.7.6) (2014-07-07) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.5...1.7.6) + +## [1.7.5](https://github.com/voxpupuli/puppet-python/tree/1.7.5) (2014-05-07) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.4...1.7.5) + +## [1.7.4](https://github.com/voxpupuli/puppet-python/tree/1.7.4) (2014-04-24) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.3...1.7.4) + +## [1.7.3](https://github.com/voxpupuli/puppet-python/tree/1.7.3) (2014-04-24) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.2...1.7.3) + +## [1.7.2](https://github.com/voxpupuli/puppet-python/tree/1.7.2) (2014-04-08) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.1...1.7.2) + +**Closed issues:** + +- Remove redundant pkgname [\#74](https://github.com/voxpupuli/puppet-python/issues/74) +- Facter scripts raise warnings [\#69](https://github.com/voxpupuli/puppet-python/issues/69) + +## [1.7.1](https://github.com/voxpupuli/puppet-python/tree/1.7.1) (2014-03-25) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.7.0...1.7.1) + +## [1.7.0](https://github.com/voxpupuli/puppet-python/tree/1.7.0) (2014-03-18) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.6.6...1.7.0) + +**Closed issues:** + +- pip wheel error [\#70](https://github.com/voxpupuli/puppet-python/issues/70) +- Don't try to reinstall pip packages on every Puppet run [\#59](https://github.com/voxpupuli/puppet-python/issues/59) + +## [1.6.6](https://github.com/voxpupuli/puppet-python/tree/1.6.6) (2014-03-06) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.6.5...1.6.6) + +## [1.6.5](https://github.com/voxpupuli/puppet-python/tree/1.6.5) (2014-03-06) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/1.6.4...1.6.5) + +**Closed issues:** + +- Versions are not tagged in github as in puppet forge [\#63](https://github.com/voxpupuli/puppet-python/issues/63) + +## [1.6.4](https://github.com/voxpupuli/puppet-python/tree/1.6.4) (2014-03-06) + +[Full Changelog](https://github.com/voxpupuli/puppet-python/compare/ed137893babebabdfdb5adf44d1a52272093ce8b...1.6.4) + +**Closed issues:** + +- Could not retrieve pip\_version: undefined method [\#61](https://github.com/voxpupuli/puppet-python/issues/61) +- New release on the forge? [\#58](https://github.com/voxpupuli/puppet-python/issues/58) +- If virtualenv isn't installed, it isn't properly detected or installed. [\#50](https://github.com/voxpupuli/puppet-python/issues/50) +- IOError: \[Errno 26\] Text file busy [\#46](https://github.com/voxpupuli/puppet-python/issues/46) +- Add support for Scientific Linux [\#39](https://github.com/voxpupuli/puppet-python/issues/39) +- python::pip doesn't find pip command [\#31](https://github.com/voxpupuli/puppet-python/issues/31) +- Incorrect log file name option in pip command [\#28](https://github.com/voxpupuli/puppet-python/issues/28) +- Resource failed with ArgumentError [\#26](https://github.com/voxpupuli/puppet-python/issues/26) +- Upload to the forge [\#25](https://github.com/voxpupuli/puppet-python/issues/25) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/Gemfile b/Gemfile index ee04692b..12839821 100644 --- a/Gemfile +++ b/Gemfile @@ -1,36 +1,28 @@ -source ENV['GEM_SOURCE'] || "https://rubygems.org" - -group :development, :test do - gem 'metadata-json-lint', :require => false - gem 'rake', :require => false - gem 'rspec-puppet', :require => false - gem 'puppetlabs_spec_helper', :require => false - gem 'puppet-lint', :require => false - gem 'pry', :require => false - gem 'simplecov', :require => false -end +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ -# rspec must be v2 for ruby 1.8.7 -if RUBY_VERSION >= '1.8.7' and RUBY_VERSION < '1.9' - gem 'rspec', '~> 2.0' -end +source ENV['GEM_SOURCE'] || 'https://rubygems.org' -if facterversion = ENV['FACTER_GEM_VERSION'] - gem 'facter', facterversion, :require => false -else - gem 'facter', :require => false +group :test do + gem 'voxpupuli-test', '~> 14.0', :require => false + gem 'puppet_metadata', '~> 6.1', :require => false end -if puppetversion = ENV['PUPPET_GEM_VERSION'] - gem 'puppet', puppetversion, :require => false -else - gem 'puppet', :require => false +group :development do + gem 'guard-rake', :require => false + gem 'overcommit', '>= 0.39.1', :require => false end group :system_tests do - gem 'serverspec', :require => false - gem 'beaker', :require => false - gem 'beaker-rspec', :require => false + gem 'voxpupuli-acceptance', '~> 4.4', :require => false +end + +group :release do + gem 'voxpupuli-release', '~> 5.3', :require => false end -# vim:ft=ruby +gem 'rake', :require => false + +gem 'openvox', ENV.fetch('OPENVOX_GEM_VERSION', [">= 7", "< 9"]), :require => false, :groups => [:test] + +# vim: syntax=ruby diff --git a/README.md b/README.md index 3f64d93a..59d95fd3 100644 --- a/README.md +++ b/README.md @@ -1,285 +1,50 @@ -# puppet-python [![Build Status](https://travis-ci.org/stankevich/puppet-python.svg?branch=master)](https://travis-ci.org/stankevich/puppet-python) +# puppet-python -Puppet module for installing and managing python, pip, virtualenvs and Gunicorn virtual hosts. - -=== - -# Compatibility # - -* Puppet v3 (with and without the future parser) -* Puppet v4 - -## Ruby versions - -* 1.8.7 -* 1.9.3 -* 2.0.0 -* 2.1.0 - -## OS Distributions ## +[![Build Status](https://github.com/voxpupuli/puppet-python/workflows/CI/badge.svg)](https://github.com/voxpupuli/puppet-python/actions?query=workflow%3ACI) +[![Release](https://github.com/voxpupuli/puppet-python/actions/workflows/release.yml/badge.svg)](https://github.com/voxpupuli/puppet-python/actions/workflows/release.yml) +[![Puppet Forge](https://img.shields.io/puppetforge/v/puppet/python.svg)](https://forge.puppetlabs.com/puppet/python) +[![Puppet Forge - downloads](https://img.shields.io/puppetforge/dt/puppet/python.svg)](https://forge.puppetlabs.com/puppet/python) +[![Puppet Forge - endorsement](https://img.shields.io/puppetforge/e/puppet/python.svg)](https://forge.puppetlabs.com/puppet/python) +[![Puppet Forge - scores](https://img.shields.io/puppetforge/f/puppet/python.svg)](https://forge.puppetlabs.com/puppet/python) +[![puppetmodule.info docs](http://www.puppetmodule.info/images/badge.png)](http://www.puppetmodule.info/m/puppet-python) +[![License](https://img.shields.io/github/license/voxpupuli/puppet-python.svg)](https://github.com/voxpupuli/puppet-python/blob/master/LICENSE) -This module has been tested to work on the following systems. - -* Debian 6 -* Debian 7 -* Debian 8 -* EL 5 -* EL 6 -* EL 7 -* Suse 11 -* Ubuntu 10.04 -* Ubuntu 12.04 -* Ubuntu 14.04 - -=== - -## Installation - -```shell -git submodule add https://github.com/stankevich/puppet-python.git /path/to/python -``` -OR +Puppet module for installing and managing python, pip, virtualenvs and Gunicorn virtual hosts. -``` shell -puppet module install stankevich-python -``` +**Please note:** The module [stankevich/python](https://forge.puppet.com/stankevich/python) has been deprecated and is now available under Vox Pupuli: [puppet/python](https://forge.puppet.com/puppet/python). ## Usage - -### python - -Installs and manages python, python-pip, python-dev, python-virtualenv and Gunicorn. - -**ensure** - Desired installation state for the Python package. Options are absent, present and latest. Default: present - -**version** - Python version to install. Default: system - -**pip** - Desired installation state for the python-pip package. Options are absent, present and latest. Default: present - -**dev** - Desired installation state for the python-dev package. Options are absent, present and latest. Default: absent - -**virtualenv** - Desired installation state for the virtualenv package. Options are absent, present and latest. Default: absent - -**gunicorn** - Desired installation state for Gunicorn. Options are absent, present and latest. Default: absent - -**manage_gunicorn** - Allow Installation / Removal of Gunicorn. Default: true - -**use_epel** - Boolean to determine if the epel class is used. Default: true - -```puppet - class { 'python' : - version => 'system', - pip => 'present', - dev => 'absent', - virtualenv => 'absent', - gunicorn => 'absent', - } -``` - -### python::pip - -Installs and manages packages from pip. - -**pkgname** - the name of the package to install. Required. - -**ensure** - present/latest/absent. You can also specify the version. Default: present - -**virtualenv** - virtualenv to run pip in. Default: system (no virtualenv) - -**url** - URL to install from. Default: none - -**owner** - The owner of the virtualenv to ensure that packages are installed with the correct permissions (must be specified). Default: root - -**proxy** - Proxy server to use for outbound connections. Default: none - -**environment** - Additional environment variables required to install the packages. Default: none - -**egg** - The egg name to use. Default: `$name` of the class, e.g. cx_Oracle - -**install_args** - String of additional flags to pass to pip during installaton. Default: none - -**uninstall_args** - String of additional flags to pass to pip during uninstall. Default: none - -**timeout** - Timeout for the pip install command. Defaults to 1800. -```puppet - python::pip { 'cx_Oracle' : - pkgname => 'cx_Oracle', - ensure => '5.1.2', - virtualenv => '/var/www/project1', - owner => 'appuser', - proxy => 'http://proxy.domain.com:3128', - environment => 'ORACLE_HOME=/usr/lib/oracle/11.2/client64', - install_args => '-e', - timeout => 1800, - } -``` - -### python::requirements - -Installs and manages Python packages from requirements file. - -**virtualenv** - virtualenv to run pip in. Default: system-wide - -**proxy** - Proxy server to use for outbound connections. Default: none - -**owner** - The owner of the virtualenv to ensure that packages are installed with the correct permissions (must be specified). Default: root - -**src** - The `--src` parameter to `pip`, used to specify where to install `--editable` resources; by default no `--src` parameter is passed to `pip`. - -**group** - The group that was used to create the virtualenv. This is used to create the requirements file with correct permissions if it's not present already. - -**manage_requirements** - Create the requirements file if it doesn't exist. Default: true - -```puppet - python::requirements { '/var/www/project1/requirements.txt' : - virtualenv => '/var/www/project1', - proxy => 'http://proxy.domain.com:3128', - owner => 'appuser', - group => 'apps', - } -``` - -### python::virtualenv - -Creates Python virtualenv. - -**ensure** - present/absent. Default: present - -**version** - Python version to use. Default: system default - -**requirements** - Path to pip requirements.txt file. Default: none - -**proxy** - Proxy server to use for outbound connections. Default: none - -**systempkgs** - Copy system site-packages into virtualenv. Default: don't - -**distribute** - Include distribute in the virtualenv. Default: true - -**venv_dir** - The location of the virtualenv if resource path not specified. Must be absolute path. Default: resource name - -**owner** - Specify the owner of this virtualenv - -**group** - Specify the group for this virtualenv - -**index** - Base URL of Python package index. Default: none - -**cwd** - The directory from which to run the "pip install" command. Default: undef - -**timeout** - The maximum time in seconds the "pip install" command should take. Default: 1800 - -```puppet - python::virtualenv { '/var/www/project1' : - ensure => present, - version => 'system', - requirements => '/var/www/project1/requirements.txt', - proxy => 'http://proxy.domain.com:3128', - systempkgs => true, - distribute => false, - venv_dir => '/home/appuser/virtualenvs', - owner => 'appuser', - group => 'apps', - cwd => '/var/www/project1', - timeout => 0, - } +For class usage refer to the [Reference]("https://github.com/voxpupuli/puppet-python/blob/master/REFERENCE.md). If contributing, this is updated with +```shell +bundle exec rake strings:generate\[',,,,false,true'] ``` -### python::pyvenv - -Creates Python3 virtualenv. - -**ensure** - present/absent. Default: present - -**version** - Python version to use. Default: system default - -**systempkgs** - Copy system site-packages into virtualenv. Default: don't - -**venv_dir** - The location of the virtualenv if resource path not specified. Must be absolute path. Default: resource name - -**owner** - Specify the owner of this virtualenv +### Install Python package to a user's default install directory -**group** - Specify the group for this virtualenv +The following code simulates -**path** - Specifies the PATH variable that contains `pyvenv` executable. Default: [ '/bin', '/usr/bin', '/usr/sbin' ] - -**environment** - Specify any environment variables to use when creating pyvenv - -```puppet - python::pyvenv { '/var/www/project1' : - ensure => present, - version => 'system', - systempkgs => true, - venv_dir => '/home/appuser/virtualenvs', - owner => 'appuser', - group => 'apps', - } +```shell +python3 -m pip install pandas --user ``` - -### python::gunicorn - -Manages Gunicorn virtual hosts. - -**ensure** - present/absent. Default: present - -**virtualenv** - Run in virtualenv, specify directory. Default: disabled - -**mode** - Gunicorn mode. wsgi/django. Default: wsgi - -**dir** - Application directory. - -**bind** - Bind on: 'HOST', 'HOST:PORT', 'unix:PATH'. Default: `unix:/tmp/gunicorn-$name.socket` or `unix:${virtualenv}/${name}.socket` - -**environment** - Set ENVIRONMENT variable. Default: none - -**appmodule** - Set the application module name for gunicorn to load when not using Django. Default: `app:app` - -**osenv** - Allows setting environment variables for the gunicorn service. Accepts a hash of 'key': 'value' pairs. Default: false - -**timeout** - Allows setting the gunicorn idle worker process time before being killed. The unit of time is seconds. Default: 30 - -**template** - Which ERB template to use. Default: python/gunicorn.erb +where pip installs packages to a user's default install directory -- +typically `~/.local/` on Linux. ```puppet - python::gunicorn { 'vhost' : - ensure => present, - virtualenv => '/var/www/project1', - mode => 'wsgi', - dir => '/var/www/project1/current', - bind => 'unix:/tmp/gunicorn.socket', - environment => 'prod', - appmodule => 'app:app', - osenv => { 'DBHOST' => 'dbserver.example.com' }, - timeout => 30, - template => 'python/gunicorn.erb', - } -``` - -### python::dotfile - -Manages arbitrary python dotiles with a simple config hash. - -**ensure** - present/absent. Default: present - -**filename** - Default: $title - -**mode** - Default: 0644 - -**owner** - Default: root - -**group** - Default: root - -**config** Config hash. This will be expanded to an ini-file. Default: {} +# Somewhat hackishly, install Python PIP module PANDAS for Oracle Cloud API queries. +python::pyvenv { 'user_python_venv': + ensure => present, + version => 'system', + systempkgs => true, + venv_dir => '/home/example/.local', + owner => 'example', + group => 'example', + mode => '0750', +} -```puppet -python::dotfile { '/var/lib/jenkins/.pip/pip.conf': - ensure => present, - owner => 'jenkins', - group => 'jenkins', - config => { - 'global' => { - 'index-url => 'https://mypypi.acme.com/simple/' - 'extra-index-url => https://pypi.risedev.at/simple/ - } - } +python::pip { 'pandas': + virtualenv => '/home/example/.local', + owner => 'example', + group => 'example', } ``` @@ -300,34 +65,24 @@ python::python_pips: virtualenv: "/opt/env1" "coverage": virtualenv: "/opt/env2" +python::python_dotfiles: + "/var/lib/jenkins/.pip/pip.conf": + config: + global: + index-url: "https://mypypi.acme.com/simple/" + extra-index-url: "https://pypi.risedev.at/simple/" ``` ### Using SCL packages from RedHat or CentOS To use this module with Linux distributions in the Red Hat family and python distributions -from softwarecollections.org, set python::provider to 'rhscl' and python::version to the name +from softwarecollections.org, set python::provider to 'rhscl' and python::version to the name of the collection you want to use (e.g., 'python27', 'python33', or 'rh-python34'). ## Release Notes -**Version 1.9.8 Notes** -The `pip`, `virtualenv` and `gunicorn` parameters of `Class['python']` have changed. These parameters now accept `absent`, `present` and `latest` rather than `true` and `false`. The boolean values are still supported and are equivalent to `present` and `absent` respectively. Support for these boolean parameters is deprecated and will be removed in a later release. - -**Version 1.7.10 Notes** - -Installation of python-pip previously defaulted to `false` and was not installed. This default is now `true` and python-pip is installed. To prevent the installation of python-pip specify `pip => false` as a parameter when instantiating the `python` puppet class. - -**Version 1.1.x Notes** - -Version `1.1.x` makes several fundamental changes to the core of this module, adding some additional features, improving performance and making operations more robust in general. - -Please note that several changes have been made in `v1.1.x` which make manifests incompatible with the previous version. However, modifying your manifests to suit is trivial. Please see the notes below. - -Currently, the changes you need to make are as follows: - -* All pip definitions MUST include the owner field which specifies which user owns the virtualenv that packages will be installed in. Adding this greatly improves performance and efficiency of this module. -* You must explicitly specify pip => true in the python class if you want pip installed. As such, the pip package is now independent of the dev package and so one can exist without the other. +See [Changelog](https://github.com/voxpupuli/puppet-python/blob/master/CHANGELOG.md) -## Authors +## Contributors -[Sergey Stankevich](https://github.com/stankevich) | [Shiva Poudel](https://github.com/shivapoudel) | [Peter Souter](https://github.com/petems) | [Garrett Honeycutt](http://learnpuppet.com) +Check out [Github contributors](https://github.com/voxpupuli/puppet-python/graphs/contributors). diff --git a/REFERENCE.md b/REFERENCE.md new file mode 100644 index 00000000..9f19f1d8 --- /dev/null +++ b/REFERENCE.md @@ -0,0 +1,1214 @@ +# Reference + + + +## Table of Contents + +### Classes + +#### Public Classes + +* [`python`](#python): Installs and manages python, python-dev and gunicorn. +* [`python::pip::bootstrap`](#python--pip--bootstrap): allow to bootstrap pip when python is managed from other module + +#### Private Classes + +* `python::config`: Optionally installs the gunicorn service +* `python::install`: Installs core python packages +* `python::params`: The python Module default configuration settings. + +### Defined types + +* [`python::dotfile`](#python--dotfile): Manages any python dotfiles with a simple config hash. +* [`python::gunicorn`](#python--gunicorn): Manages Gunicorn virtual hosts. +* [`python::pip`](#python--pip): Installs and manages packages from pip. +* [`python::pyvenv`](#python--pyvenv): Create a Python3 virtualenv using pyvenv. +* [`python::requirements`](#python--requirements): Installs and manages Python packages from requirements file. + +### Data types + +* [`Python::Loglevel`](#Python--Loglevel): Match all valid loglevels for python +* [`Python::Package::Ensure`](#Python--Package--Ensure): Match all valid package ensures for python +* [`Python::Provider`](#Python--Provider): Match all valid provider for python +* [`Python::Umask`](#Python--Umask): Match valid umask for python +* [`Python::Venv::PipVersion`](#Python--Venv--PipVersion): A version type to ensure a specific Pip version in a virtual env. +* [`Python::Version`](#Python--Version): Match all valid versions for python + +## Classes + +### `python` + +Installs and manages python, python-dev and gunicorn. + +#### Examples + +##### install python from system python + +```puppet +class { 'python': + version => 'system', + pip => 'present', + dev => 'present', + gunicorn => 'present', +} +``` + +##### install python3 from scl repo + +```puppet +class { 'python' : + ensure => 'present', + version => 'rh-python36-python', + dev => 'present', +} +``` + +#### Parameters + +The following parameters are available in the `python` class: + +* [`default_system_version`](#-python--default_system_version) +* [`ensure`](#-python--ensure) +* [`version`](#-python--version) +* [`pip`](#-python--pip) +* [`dev`](#-python--dev) +* [`gunicorn`](#-python--gunicorn) +* [`manage_gunicorn`](#-python--manage_gunicorn) +* [`provider`](#-python--provider) +* [`use_epel`](#-python--use_epel) +* [`manage_scl`](#-python--manage_scl) +* [`umask`](#-python--umask) +* [`manage_gunicorn`](#-python--manage_gunicorn) +* [`manage_python_package`](#-python--manage_python_package) +* [`manage_dev_package`](#-python--manage_dev_package) +* [`manage_venv_package`](#-python--manage_venv_package) +* [`manage_pip_package`](#-python--manage_pip_package) +* [`venv`](#-python--venv) +* [`gunicorn_package_name`](#-python--gunicorn_package_name) +* [`python_pips`](#-python--python_pips) +* [`python_pyvenvs`](#-python--python_pyvenvs) +* [`python_requirements`](#-python--python_requirements) +* [`python_dotfiles`](#-python--python_dotfiles) +* [`rhscl_use_public_repository`](#-python--rhscl_use_public_repository) +* [`anaconda_installer_url`](#-python--anaconda_installer_url) +* [`anaconda_install_path`](#-python--anaconda_install_path) + +##### `default_system_version` + +Data type: `Python::Version` + +The default version of Python provided by the operating system. Only used as a fallback if Python is not installed yet to determine how to handle some actions that vary depending on the Python version used. + +Default value: `'3.11'` + +##### `ensure` + +Data type: `Python::Package::Ensure` + +Desired installation state for the Python package. + +Default value: `'present'` + +##### `version` + +Data type: `Python::Version` + +Python version to install. Beware that valid values for this differ a) by the provider you choose and b) by the osfamily/operatingsystem you are using. +Allowed values: + - provider == pip: everything pip allows as a version after the 'python==' + - else: 'system', 'pypy', 3/3.3/... + - Be aware that 'system' usually means python 2.X. + - 'pypy' actually lets us use pypy as python. + - 3/3.3/... means you are going to install the python3/python3.3/... + package, if available on your osfamily. + +Default value: `$facts['os']['family'] ? { 'Archlinux' => 'system', default => '3'` + +##### `pip` + +Data type: `Python::Package::Ensure` + +Desired installation state for the python-pip package. + +Default value: `'present'` + +##### `dev` + +Data type: `Python::Package::Ensure` + +Desired installation state for the python-dev package. + +Default value: `'absent'` + +##### `gunicorn` + +Data type: `Python::Package::Ensure` + +Desired installation state for Gunicorn. + +Default value: `'absent'` + +##### `manage_gunicorn` + +Data type: `Boolean` + +Allow Installation / Removal of Gunicorn. + +Default value: `true` + +##### `provider` + +Data type: `Optional[Python::Provider]` + +What provider to use for installation of the packages, except gunicorn and Python itself. + +Default value: `undef` + +##### `use_epel` + +Data type: `Boolean` + +to determine if the epel class is used. + +Default value: `$python::params::use_epel` + +##### `manage_scl` + +Data type: `Boolean` + +Whether to manage core SCL packages or not. + +Default value: `true` + +##### `umask` + +The default umask for invoked exec calls. + +##### `manage_gunicorn` + +manage the state for package gunicorn + +Default value: `true` + +##### `manage_python_package` + +Data type: `Boolean` + +manage the state for package python + +Default value: `true` + +##### `manage_dev_package` + +Data type: `Boolean` + +manage the state of the python development package + +Default value: `true` + +##### `manage_venv_package` + +Data type: `Boolean` + +manage the state for package venv + +Default value: `$python::params::manage_venv_package` + +##### `manage_pip_package` + +Data type: `Boolean` + +manage the state for package pip + +Default value: `$python::params::manage_pip_package` + +##### `venv` + +Data type: `Python::Package::Ensure` + + + +Default value: `'absent'` + +##### `gunicorn_package_name` + +Data type: `String[1]` + + + +Default value: `$python::params::gunicorn_package_name` + +##### `python_pips` + +Data type: `Hash` + + + +Default value: `{}` + +##### `python_pyvenvs` + +Data type: `Hash` + + + +Default value: `{}` + +##### `python_requirements` + +Data type: `Hash` + + + +Default value: `{}` + +##### `python_dotfiles` + +Data type: `Hash` + + + +Default value: `{}` + +##### `rhscl_use_public_repository` + +Data type: `Boolean` + + + +Default value: `true` + +##### `anaconda_installer_url` + +Data type: `Stdlib::Httpurl` + + + +Default value: `'https://repo.anaconda.com/archive/Anaconda3-5.2.0-Linux-x86_64.sh'` + +##### `anaconda_install_path` + +Data type: `Stdlib::Absolutepath` + + + +Default value: `'/opt/python'` + +### `python::pip::bootstrap` + +allow to bootstrap pip when python is managed from other module + +#### Examples + +##### + +```puppet +class { 'python::pip::bootstrap': + version => 'pip', +} +``` + +#### Parameters + +The following parameters are available in the `python::pip::bootstrap` class: + +* [`version`](#-python--pip--bootstrap--version) +* [`manage_python`](#-python--pip--bootstrap--manage_python) +* [`http_proxy`](#-python--pip--bootstrap--http_proxy) +* [`exec_provider`](#-python--pip--bootstrap--exec_provider) + +##### `version` + +Data type: `Enum['pip', 'pip3']` + +should be pip or pip3 + +Default value: `'pip'` + +##### `manage_python` + +Data type: `Variant[Boolean, String]` + +if python module will manage deps + +Default value: `false` + +##### `http_proxy` + +Data type: `Optional[Stdlib::HTTPUrl]` + +Proxy server to use for outbound connections. + +Default value: `undef` + +##### `exec_provider` + +Data type: `String[1]` + + + +Default value: `'shell'` + +## Defined types + +### `python::dotfile` + +Manages any python dotfiles with a simple config hash. + +#### Examples + +##### Create a pip config in /var/lib/jenkins/.pip/ + +```puppet +python::dotfile { '/var/lib/jenkins/.pip/pip.conf': + ensure => present, + owner => 'jenkins', + group => 'jenkins', + config => { + 'global' => { + 'index-url' => 'https://mypypi.acme.com/simple/' + 'extra-index-url' => 'https://pypi.risedev.at/simple/' + } + } +} +``` + +#### Parameters + +The following parameters are available in the `python::dotfile` defined type: + +* [`ensure`](#-python--dotfile--ensure) +* [`filename`](#-python--dotfile--filename) +* [`mode`](#-python--dotfile--mode) +* [`owner`](#-python--dotfile--owner) +* [`group`](#-python--dotfile--group) +* [`config`](#-python--dotfile--config) + +##### `ensure` + +Data type: `Enum['absent', 'present']` + + + +Default value: `'present'` + +##### `filename` + +Data type: `Stdlib::Absolutepath` + +Filename. + +Default value: `$title` + +##### `mode` + +Data type: `Stdlib::Filemode` + +File mode. + +Default value: `'0644'` + +##### `owner` + +Data type: `String[1]` + +user owner of dotfile + +Default value: `'root'` + +##### `group` + +Data type: `String[1]` + +group owner of dotfile + +Default value: `getvar('python::params::group')` + +##### `config` + +Data type: `Hash` + +Config hash. This will be expanded to an ini-file. + +Default value: `{}` + +### `python::gunicorn` + +Manages Gunicorn virtual hosts. + +#### Examples + +##### run gunicorn on vhost in virtualenv /var/www/project1 + +```puppet +python::gunicorn { 'vhost': + ensure => present, + virtualenv => '/var/www/project1', + mode => 'wsgi', + dir => '/var/www/project1/current', + bind => 'unix:/tmp/gunicorn.socket', + environment => 'prod', + owner => 'www-data', + group => 'www-data', + appmodule => 'app:app', + osenv => { 'DBHOST' => 'dbserver.example.com' }, + timeout => 30, + template => 'python/gunicorn.erb', +} +``` + +#### Parameters + +The following parameters are available in the `python::gunicorn` defined type: + +* [`ensure`](#-python--gunicorn--ensure) +* [`config_dir`](#-python--gunicorn--config_dir) +* [`manage_config_dir`](#-python--gunicorn--manage_config_dir) +* [`virtualenv`](#-python--gunicorn--virtualenv) +* [`mode`](#-python--gunicorn--mode) +* [`dir`](#-python--gunicorn--dir) +* [`bind`](#-python--gunicorn--bind) +* [`environment`](#-python--gunicorn--environment) +* [`appmodule`](#-python--gunicorn--appmodule) +* [`osenv`](#-python--gunicorn--osenv) +* [`timeout`](#-python--gunicorn--timeout) +* [`template`](#-python--gunicorn--template) +* [`args`](#-python--gunicorn--args) +* [`owner`](#-python--gunicorn--owner) +* [`group`](#-python--gunicorn--group) +* [`workers`](#-python--gunicorn--workers) +* [`access_log_format`](#-python--gunicorn--access_log_format) +* [`accesslog`](#-python--gunicorn--accesslog) +* [`errorlog`](#-python--gunicorn--errorlog) +* [`log_level`](#-python--gunicorn--log_level) + +##### `ensure` + +Data type: `Enum['present', 'absent']` + + + +Default value: `present` + +##### `config_dir` + +Data type: `Stdlib::Absolutepath` + +Configure the gunicorn config directory path. + +Default value: `'/etc/gunicorn.d'` + +##### `manage_config_dir` + +Data type: `Boolean` + +Set if the gunicorn config directory should be created. + +Default value: `false` + +##### `virtualenv` + +Data type: `Variant[Boolean,Stdlib::Absolutepath]` + +Run in virtualenv, specify directory. + +Default value: `false` + +##### `mode` + +Data type: `Enum['wsgi', 'django']` + +Gunicorn mode. + +Default value: `'wsgi'` + +##### `dir` + +Data type: `Stdlib::Absolutepath` + +Application directory. + +##### `bind` + +Data type: `Variant[String[1],Boolean]` + +Bind on: 'HOST', 'HOST:PORT', 'unix:PATH'. +Default: system-wide: unix:/tmp/gunicorn-$name.socket + virtualenv: unix:${virtualenv}/${name}.socket + +Default value: `false` + +##### `environment` + +Data type: `Variant[String[1],Boolean]` + +Set ENVIRONMENT variable. + +Default value: `false` + +##### `appmodule` + +Data type: `String[1]` + +Set the application module name for gunicorn to load when not using Django. + +Default value: `'app:app'` + +##### `osenv` + +Data type: `Variant[Boolean,Hash]` + +Allows setting environment variables for the gunicorn service. Accepts a hash of 'key': 'value' pairs. + +Default value: `false` + +##### `timeout` + +Data type: `Integer` + +Allows setting the gunicorn idle worker process time before being killed. The unit of time is seconds. + +Default value: `30` + +##### `template` + +Data type: `String[1]` + +Which ERB template to use. + +Default value: `'python/gunicorn.erb'` + +##### `args` + +Data type: `Array` + +Custom arguments to add in gunicorn config file. + +Default value: `[]` + +##### `owner` + +Data type: `String[1]` + + + +Default value: `'www-data'` + +##### `group` + +Data type: `String[1]` + + + +Default value: `'www-data'` + +##### `workers` + +Data type: `Variant[Boolean,Integer]` + + + +Default value: `false` + +##### `access_log_format` + +Data type: `Variant[Boolean,String[1]]` + + + +Default value: `false` + +##### `accesslog` + +Data type: `Variant[Boolean,Stdlib::Absolutepath]` + + + +Default value: `false` + +##### `errorlog` + +Data type: `Variant[Boolean,Stdlib::Absolutepath]` + + + +Default value: `false` + +##### `log_level` + +Data type: `Python::Loglevel` + + + +Default value: `'error'` + +### `python::pip` + +Installs and manages packages from pip. + +#### Examples + +##### Install Flask to /var/www/project1 using a proxy + +```puppet +python::pip { 'flask': + virtualenv => '/var/www/project1', + proxy => 'http://proxy.domain.com:3128', + index => 'http://www.example.com/simple/', +} +``` + +##### Install cx_Oracle with pip + +```puppet +python::pip { 'cx_Oracle' : + pkgname => 'cx_Oracle', + ensure => '5.1.2', + virtualenv => '/var/www/project1', + owner => 'appuser', + proxy => 'http://proxy.domain.com:3128', + environment => ['ORACLE_HOME=/usr/lib/oracle/11.2/client64'], + install_args => '-e', + timeout => 1800, +} +``` + +##### Install Requests with pip3 + +```puppet +python::pip { 'requests' : + ensure => 'present', + pkgname => 'requests', + pip_provider => 'pip3', + virtualenv => '/var/www/project1', + owner => 'root', + timeout => 1800 +} +``` + +#### Parameters + +The following parameters are available in the `python::pip` defined type: + +* [`name`](#-python--pip--name) +* [`pkgname`](#-python--pip--pkgname) +* [`ensure`](#-python--pip--ensure) +* [`virtualenv`](#-python--pip--virtualenv) +* [`pip_provider`](#-python--pip--pip_provider) +* [`url`](#-python--pip--url) +* [`owner`](#-python--pip--owner) +* [`group`](#-python--pip--group) +* [`index`](#-python--pip--index) +* [`extra_index`](#-python--pip--extra_index) +* [`proxy`](#-python--pip--proxy) +* [`editable`](#-python--pip--editable) +* [`environment`](#-python--pip--environment) +* [`extras`](#-python--pip--extras) +* [`timeout`](#-python--pip--timeout) +* [`install_args`](#-python--pip--install_args) +* [`uninstall_args`](#-python--pip--uninstall_args) +* [`log_dir`](#-python--pip--log_dir) +* [`egg`](#-python--pip--egg) +* [`umask`](#-python--pip--umask) +* [`path`](#-python--pip--path) +* [`exec_provider`](#-python--pip--exec_provider) + +##### `name` + +must be unique + +##### `pkgname` + +Data type: `String[1]` + +the name of the package. + +Default value: `$name` + +##### `ensure` + +Data type: `Variant[Enum[present, absent, latest], String[1]]` + +Require pip to be available. + +Default value: `present` + +##### `virtualenv` + +Data type: `Variant[Enum['system'], Stdlib::Absolutepath]` + +virtualenv to run pip in. + +Default value: `'system'` + +##### `pip_provider` + +Data type: `String[1]` + +version of pip you wish to use. + +Default value: `'pip'` + +##### `url` + +Data type: `Variant[Boolean, String]` + +URL to install from. + +Default value: `false` + +##### `owner` + +Data type: `String[1]` + +The owner of the virtualenv being manipulated. + +Default value: `'root'` + +##### `group` + +Data type: `Optional[String[1]]` + +The group of the virtualenv being manipulated. + +Default value: `getvar('python::params::group')` + +##### `index` + +Data type: `Variant[Boolean,String[1]]` + +Base URL of Python package index. + +Default value: `false` + +##### `extra_index` + +Data type: `Variant[Boolean,String[1]]` + +Base URL of extra Python package index. + +Default value: `false` + +##### `proxy` + +Data type: `Optional[Stdlib::HTTPUrl]` + +Proxy server to use for outbound connections. + +Default value: `undef` + +##### `editable` + +Data type: `Boolean` + +If true the package is installed as an editable resource. + +Default value: `false` + +##### `environment` + +Data type: `Array` + +Additional environment variables required to install the packages. + +Default value: `[]` + +##### `extras` + +Data type: `Array` + +Extra features provided by the package which should be installed. + +Default value: `[]` + +##### `timeout` + +Data type: `Numeric` + +The maximum time in seconds the "pip install" command should take. + +Default value: `1800` + +##### `install_args` + +Data type: `Optional[String[1]]` + +Any additional installation arguments that will be supplied when running pip install. + +Default value: `undef` + +##### `uninstall_args` + +Data type: `Optional[String[1]]` + +Any additional arguments that will be supplied when running pip uninstall. + +Default value: `undef` + +##### `log_dir` + +Data type: `String[1]` + +Log directory + +Default value: `'/tmp'` + +##### `egg` + +Data type: `Any` + +The egg name to use + +Default value: `false` + +##### `umask` + +Data type: `Optional[Python::Umask]` + + + +Default value: `undef` + +##### `path` + +Data type: `Array[String]` + + + +Default value: `['/usr/local/bin','/usr/bin','/bin', '/usr/sbin']` + +##### `exec_provider` + +Data type: `String[1]` + + + +Default value: `'shell'` + +### `python::pyvenv` + +Create a Python3 virtualenv using pyvenv. + +#### Examples + +##### + +```puppet +python::pyvenv { '/var/www/project1' : + ensure => present, + version => 'system', + systempkgs => true, + venv_dir => '/home/appuser/virtualenvs', + owner => 'appuser', + group => 'apps', +} +``` + +#### Parameters + +The following parameters are available in the `python::pyvenv` defined type: + +* [`ensure`](#-python--pyvenv--ensure) +* [`version`](#-python--pyvenv--version) +* [`systempkgs`](#-python--pyvenv--systempkgs) +* [`venv_dir`](#-python--pyvenv--venv_dir) +* [`owner`](#-python--pyvenv--owner) +* [`group`](#-python--pyvenv--group) +* [`mode`](#-python--pyvenv--mode) +* [`path`](#-python--pyvenv--path) +* [`environment`](#-python--pyvenv--environment) +* [`prompt`](#-python--pyvenv--prompt) +* [`python_path`](#-python--pyvenv--python_path) +* [`pip_version`](#-python--pyvenv--pip_version) + +##### `ensure` + +Data type: `Python::Package::Ensure` + + + +Default value: `present` + +##### `version` + +Data type: `Python::Version` + +Python version to use. + +Default value: `'system'` + +##### `systempkgs` + +Data type: `Boolean` + +Copy system site-packages into virtualenv + +Default value: `false` + +##### `venv_dir` + +Data type: `Stdlib::Absolutepath` + +Directory to install virtualenv to + +Default value: `$name` + +##### `owner` + +Data type: `String[1]` + +The owner of the virtualenv being manipulated + +Default value: `'root'` + +##### `group` + +Data type: `String[1]` + +The group relating to the virtualenv being manipulated + +Default value: `'root'` + +##### `mode` + +Data type: `Stdlib::Filemode` + +Optionally specify directory mode + +Default value: `'0755'` + +##### `path` + +Data type: `Array[Stdlib::Absolutepath]` + +Specifies the PATH variable. + +Default value: `['/bin', '/usr/bin', '/usr/sbin', '/usr/local/bin',]` + +##### `environment` + +Data type: `Array[String[1]]` + +Optionally specify environment variables for pyvenv + +Default value: `[]` + +##### `prompt` + +Data type: `Optional[String[1]]` + +Optionally specify the virtualenv prompt (python >= 3.6) + +Default value: `undef` + +##### `python_path` + +Data type: `Optional[Stdlib::Absolutepath]` + +Optionally specify python path for creation of virtualenv + +Default value: `undef` + +##### `pip_version` + +Data type: `Python::Venv::PipVersion` + + + +Default value: `'latest'` + +### `python::requirements` + +Installs and manages Python packages from requirements file. + +#### Examples + +##### install pip requirements from /var/www/project1/requirements.txt + +```puppet +python::requirements { '/var/www/project1/requirements.txt' : + virtualenv => '/var/www/project1', + proxy => 'http://proxy.domain.com:3128', + owner => 'appuser', + group => 'apps', +} +``` + +#### Parameters + +The following parameters are available in the `python::requirements` defined type: + +* [`requirements`](#-python--requirements--requirements) +* [`virtualenv`](#-python--requirements--virtualenv) +* [`pip_provider`](#-python--requirements--pip_provider) +* [`owner`](#-python--requirements--owner) +* [`group`](#-python--requirements--group) +* [`proxy`](#-python--requirements--proxy) +* [`src`](#-python--requirements--src) +* [`environment`](#-python--requirements--environment) +* [`forceupdate`](#-python--requirements--forceupdate) +* [`cwd`](#-python--requirements--cwd) +* [`extra_pip_args`](#-python--requirements--extra_pip_args) +* [`manage_requirements`](#-python--requirements--manage_requirements) +* [`fix_requirements_owner`](#-python--requirements--fix_requirements_owner) +* [`log_dir`](#-python--requirements--log_dir) +* [`timeout`](#-python--requirements--timeout) + +##### `requirements` + +Data type: `Stdlib::Absolutepath` + +Path to the requirements file. + +Default value: `$name` + +##### `virtualenv` + +Data type: `Variant[Enum['system'],Stdlib::Absolutepath]` + +virtualenv to run pip in. + +Default value: `'system'` + +##### `pip_provider` + +Data type: `Enum['pip', 'pip3']` + +version of pip you wish to use. + +Default value: `'pip'` + +##### `owner` + +Data type: `String[1]` + +The owner of the virtualenv being manipulated. + +Default value: `'root'` + +##### `group` + +Data type: `String[1]` + +The group relating to the virtualenv being manipulated. + +Default value: `'root'` + +##### `proxy` + +Data type: `Optional[Stdlib::HTTPUrl]` + +Proxy server to use for outbound connections. + +Default value: `undef` + +##### `src` + +Data type: `Any` + +Pip --src parameter to; if the requirements file contains --editable resources, this parameter specifies where they will be installed. See the pip documentation for more. + +Default value: `false` + +##### `environment` + +Data type: `Array` + +Additional environment variables required to install the packages. + +Default value: `[]` + +##### `forceupdate` + +Data type: `Boolean` + +Run a pip install requirements even if we don't receive an event from the requirements file - Useful for when the requirements file is written as part of a resource other than file (E.g vcsrepo) + +Default value: `false` + +##### `cwd` + +Data type: `Optional[Stdlib::Absolutepath]` + +The directory from which to run the "pip install" command. + +Default value: `undef` + +##### `extra_pip_args` + +Data type: `Optional[String[1]]` + +Extra arguments to pass to pip after the requirements file + +Default value: `undef` + +##### `manage_requirements` + +Data type: `Boolean` + +Create the requirements file if it doesn't exist. + +Default value: `true` + +##### `fix_requirements_owner` + +Data type: `Boolean` + +Change owner and group of requirements file. + +Default value: `true` + +##### `log_dir` + +Data type: `Stdlib::Absolutepath` + +Log directory. + +Default value: `'/tmp'` + +##### `timeout` + +Data type: `Integer` + +The maximum time in seconds the "pip install" command should take. + +Default value: `1800` + +## Data types + +### `Python::Loglevel` + +Match all valid loglevels for python + +Alias of `Enum['debug', 'info', 'warning', 'error', 'critical']` + +### `Python::Package::Ensure` + +Match all valid package ensures for python + +Alias of `Enum['absent', 'present', 'installed', 'latest']` + +### `Python::Provider` + +Match all valid provider for python + +Alias of `Enum['pip', 'scl', 'rhscl', 'anaconda', '']` + +### `Python::Umask` + +Match valid umask for python + +Alias of `Pattern[/[0-7]{1,4}/]` + +### `Python::Venv::PipVersion` + +A version type to ensure a specific Pip version in a virtual env. + +Alias of `Pattern[/^(<|>|<=|>=|==) [0-9]*(\.[0-9]+)*$/, /\Alatest\Z/]` + +### `Python::Version` + +Match all valid versions for python + +Alias of `Pattern[/\A(python)?[0-9](\.?[0-9])*/, /\Apypy\Z/, /\Asystem\Z/, /\Arh-python[0-9]{2}(?:-python)?\Z/]` + diff --git a/Rakefile b/Rakefile index 1f6c81f0..fab1b62a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,39 +1,36 @@ -require 'puppetlabs_spec_helper/rake_tasks' -require 'puppet-lint/tasks/puppet-lint' -require 'puppet-syntax/tasks/puppet-syntax' +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ -# These two gems aren't always present, for instance -# on Travis with --without development begin - require 'puppet_blacksmith/rake_tasks' + require 'voxpupuli/test/rake' rescue LoadError + # only available if gem group test is installed end -PuppetLint.configuration.fail_on_warnings -PuppetLint.configuration.send('relative') -PuppetLint.configuration.send('disable_80chars') -PuppetLint.configuration.send('disable_class_inherits_from_params_class') -PuppetLint.configuration.send('disable_class_parameter_defaults') -PuppetLint.configuration.send('disable_documentation') -PuppetLint.configuration.send('disable_single_quote_string_with_variables') -PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] +begin + require 'voxpupuli/acceptance/rake' +rescue LoadError + # only available if gem group acceptance is installed +end -exclude_paths = [ - "pkg/**/*", - "vendor/**/*", - "spec/**/*", -] -PuppetLint.configuration.ignore_paths = exclude_paths -PuppetSyntax.exclude_paths = exclude_paths +begin + require 'voxpupuli/release/rake_tasks' +rescue LoadError + # only available if gem group releases is installed +else + GCGConfig.user = 'voxpupuli' + GCGConfig.project = 'puppet-python' +end -desc "Run acceptance tests" -RSpec::Core::RakeTask.new(:acceptance) do |t| - t.pattern = 'spec/acceptance' +desc "Run main 'test' task and report merged results to coveralls" +task test_with_coveralls: [:test] do + if Dir.exist?(File.expand_path('../lib', __FILE__)) + require 'coveralls/rake/task' + Coveralls::RakeTask.new + Rake::Task['coveralls:push'].invoke + else + puts 'Skipping reporting to coveralls. Module has no lib dir' + end end -desc "Run syntax, lint, and spec tests." -task :test => [ - :syntax, - :lint, - :spec, -] \ No newline at end of file +# vim: syntax=ruby diff --git a/data/common.yaml b/data/common.yaml new file mode 100644 index 00000000..8d945465 --- /dev/null +++ b/data/common.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.11" diff --git a/data/os/Archlinux.yaml b/data/os/Archlinux.yaml new file mode 100644 index 00000000..3bd58ed5 --- /dev/null +++ b/data/os/Archlinux.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.13" diff --git a/data/os/Debian/11.yaml b/data/os/Debian/11.yaml new file mode 100644 index 00000000..c3b18ece --- /dev/null +++ b/data/os/Debian/11.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.9" diff --git a/data/os/Debian/12.yaml b/data/os/Debian/12.yaml new file mode 100644 index 00000000..8d945465 --- /dev/null +++ b/data/os/Debian/12.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.11" diff --git a/data/os/Debian/13.yaml b/data/os/Debian/13.yaml new file mode 100644 index 00000000..3bd58ed5 --- /dev/null +++ b/data/os/Debian/13.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.13" diff --git a/data/os/FreeBSD.yaml b/data/os/FreeBSD.yaml new file mode 100644 index 00000000..8d945465 --- /dev/null +++ b/data/os/FreeBSD.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.11" diff --git a/data/os/Gentoo.yaml b/data/os/Gentoo.yaml new file mode 100644 index 00000000..68b52939 --- /dev/null +++ b/data/os/Gentoo.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.12" diff --git a/data/os/RedHat/8.yaml b/data/os/RedHat/8.yaml new file mode 100644 index 00000000..5de86ac2 --- /dev/null +++ b/data/os/RedHat/8.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.6" diff --git a/data/os/RedHat/9.yaml b/data/os/RedHat/9.yaml new file mode 100644 index 00000000..c3b18ece --- /dev/null +++ b/data/os/RedHat/9.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.9" diff --git a/data/os/Ubuntu/22.04.yaml b/data/os/Ubuntu/22.04.yaml new file mode 100644 index 00000000..383744c0 --- /dev/null +++ b/data/os/Ubuntu/22.04.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.10" diff --git a/data/os/Ubuntu/24.04.yaml b/data/os/Ubuntu/24.04.yaml new file mode 100644 index 00000000..68b52939 --- /dev/null +++ b/data/os/Ubuntu/24.04.yaml @@ -0,0 +1 @@ +python::default_system_version: "3.12" diff --git a/tests/gunicorn.pp b/examples/gunicorn.pp similarity index 73% rename from tests/gunicorn.pp rename to examples/gunicorn.pp index 8880b54c..c83a8224 100644 --- a/tests/gunicorn.pp +++ b/examples/gunicorn.pp @@ -1,7 +1,6 @@ class { 'python': - version => 'system', - dev => true, - virtualenv => true, + version => 'system', + dev => true, } python::gunicorn { 'vhost': @@ -12,7 +11,9 @@ bind => 'unix:/tmp/gunicorn.socket', environment => 'prod', appmodule => 'app:app', - osenv => { 'DBHOST' => 'dbserver.example.com' }, + osenv => { + 'DBHOST' => 'dbserver.example.com', + }, timeout => 30, template => 'python/gunicorn.erb', } diff --git a/examples/init.pp b/examples/init.pp new file mode 100644 index 00000000..ab934fa9 --- /dev/null +++ b/examples/init.pp @@ -0,0 +1,4 @@ +class { 'python': + version => 'system', + dev => true, +} diff --git a/tests/pip.pp b/examples/pip.pp similarity index 65% rename from tests/pip.pp rename to examples/pip.pp index f0db1728..a65be945 100644 --- a/tests/pip.pp +++ b/examples/pip.pp @@ -1,7 +1,6 @@ class { 'python': - version => 'system', - dev => true, - virtualenv => true, + version => 'system', + dev => true, } python::pip { 'flask': diff --git a/examples/pyvenv.pp b/examples/pyvenv.pp new file mode 100644 index 00000000..b41b4306 --- /dev/null +++ b/examples/pyvenv.pp @@ -0,0 +1,12 @@ +class { 'python': + pip => false, + version => '3', +} + +python::pyvenv { '/opt/uwsgi': +} + +python::pip { 'uwsgi': + ensure => 'latest', + virtualenv => '/opt/uwsgi', +} diff --git a/tests/requirements.pp b/examples/requirements.pp similarity index 82% rename from tests/requirements.pp rename to examples/requirements.pp index 90dccd8b..4dcf289e 100644 --- a/tests/requirements.pp +++ b/examples/requirements.pp @@ -1,7 +1,6 @@ class { 'python': - version => 'system', - dev => true, - virtualenv => true, + version => 'system', + dev => true, } python::requirements { '/var/www/project1/requirements.txt': diff --git a/hiera.yaml b/hiera.yaml new file mode 100644 index 00000000..9cea3e4d --- /dev/null +++ b/hiera.yaml @@ -0,0 +1,23 @@ +--- +version: 5 + +defaults: # Used for any hierarchy level that omits these keys. + datadir: data # This path is relative to hiera.yaml's directory. + data_hash: yaml_data # Use the built-in YAML backend. + +hierarchy: + - name: "archicture" + paths: + - "architecture/%{facts.os.architecture}.yaml" + - "architecture/common.yaml" + - name: "osfamily/major release" + paths: + - "os/%{facts.os.name}/%{facts.os.release.major}.yaml" # Used to distinguish between Debian and Ubuntu + - "os/%{facts.os.family}/%{facts.os.release.major}.yaml" # + - "os/%{facts.os.family}/%{facts.kernelrelease}.yaml" # Used for Solaris + - name: "osfamily" + paths: + - "os/%{facts.os.name}.yaml" + - "os/%{facts.os.family}.yaml" + - name: 'common' + path: 'common.yaml' diff --git a/lib/facter/pip_version.rb b/lib/facter/pip_version.rb index 71f60aea..3303b237 100644 --- a/lib/facter/pip_version.rb +++ b/lib/facter/pip_version.rb @@ -1,9 +1,28 @@ +# frozen_string_literal: true + # Make pip version available as a fact -Facter.add("pip_version") do +def get_pip_version(executable) + if Facter::Util::Resolution.which(executable) # rubocop:disable Style/GuardClause + results = Facter::Util::Resolution.exec("#{executable} --version 2>&1").match(%r{^pip (\d+\.\d+\.?\d*).*$}) + results[1] if results + end +end + +Facter.add('pip_version') do + setcode do + get_pip_version 'pip' + end +end + +Facter.add('pip2_version') do + setcode do + get_pip_version 'pip2' + end +end + +Facter.add('pip3_version') do setcode do - if Facter::Util::Resolution.which('pip') - Facter::Util::Resolution.exec('pip --version 2>&1').match(/^pip (\d+\.\d+\.?\d*).*$/)[1] - end + get_pip_version 'pip3' end end diff --git a/lib/facter/python_release.rb b/lib/facter/python_release.rb new file mode 100644 index 00000000..fab88dbd --- /dev/null +++ b/lib/facter/python_release.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Make python release available as facts + +def get_python_release(executable) + if Facter::Util::Resolution.which(executable) # rubocop:disable Style/GuardClause + results = Facter::Util::Resolution.exec("#{executable} -V 2>&1").match(%r{^.*(\d+\.\d+)\.\d+\+?$}) + results[1] if results + end +end + +Facter.add('python_release') do + setcode do + get_python_release 'python' + end +end + +Facter.add('python2_release') do + setcode do + default_release = get_python_release 'python' + if default_release.nil? || !default_release.start_with?('2') + get_python_release 'python2' + else + default_release + end + end +end + +Facter.add('python3_release') do + setcode do + get_python_release 'python3' + end +end diff --git a/lib/facter/python_version.rb b/lib/facter/python_version.rb index 8fe91636..fe70684e 100644 --- a/lib/facter/python_version.rb +++ b/lib/facter/python_version.rb @@ -1,24 +1,24 @@ +# frozen_string_literal: true + # Make python versions available as facts def get_python_version(executable) - if Facter::Util::Resolution.which(executable) - results = Facter::Util::Resolution.exec("#{executable} -V 2>&1").match(/^.*(\d+\.\d+\.\d+)$/) - if results - results[1] - end + if Facter::Util::Resolution.which(executable) # rubocop:disable Style/GuardClause + results = Facter::Util::Resolution.exec("#{executable} -V 2>&1").match(%r{^.*(\d+\.\d+\.\d+\+?)$}) + results[1] if results end end -Facter.add("python_version") do +Facter.add('python_version') do setcode do get_python_version 'python' end end -Facter.add("python2_version") do +Facter.add('python2_version') do setcode do default_version = get_python_version 'python' - if default_version.nil? or !default_version.start_with?('2') + if default_version.nil? || !default_version.start_with?('2') get_python_version 'python2' else default_version @@ -26,7 +26,7 @@ def get_python_version(executable) end end -Facter.add("python3_version") do +Facter.add('python3_version') do setcode do get_python_version 'python3' end diff --git a/lib/facter/virtualenv_version.rb b/lib/facter/virtualenv_version.rb deleted file mode 100644 index a0917d09..00000000 --- a/lib/facter/virtualenv_version.rb +++ /dev/null @@ -1,9 +0,0 @@ -# Make virtualenv version available as a fact - -Facter.add("virtualenv_version") do - setcode do - if Facter::Util::Resolution.which('virtualenv') - Facter::Util::Resolution.exec('virtualenv --version 2>&1').match(/^(\d+\.\d+\.?\d*).*$/)[0] - end - end -end diff --git a/manifests/config.pp b/manifests/config.pp index 78a424b9..8c313313 100644 --- a/manifests/config.pp +++ b/manifests/config.pp @@ -1,28 +1,15 @@ -# == Define: python::config +# @api private +# @summary Optionally installs the gunicorn service # -# Optionally installs the gunicorn service +# @example +# include python::config # -# === Examples -# -# include python::config -# -# === Authors -# -# Sergey Stankevich -# Ashley Penney -# Fotis Gimian -# - class python::config { - Class['python::install'] -> Python::Pip <| |> Class['python::install'] -> Python::Requirements <| |> - Class['python::install'] -> Python::Virtualenv <| |> - - Python::Virtualenv <| |> -> Python::Pip <| |> if $python::manage_gunicorn { - if $python::gunicorn != 'absent' { + unless $python::gunicorn == 'absent' { Class['python::install'] -> Python::Gunicorn <| |> Python::Gunicorn <| |> ~> Service['gunicorn'] @@ -36,5 +23,4 @@ } } } - } diff --git a/manifests/dotfile.pp b/manifests/dotfile.pp index 5bc70ae9..66d59b1e 100644 --- a/manifests/dotfile.pp +++ b/manifests/dotfile.pp @@ -1,53 +1,40 @@ -# == Define: python::dotfile # -# Manages any python dotfiles with a simple config hash. -# -# === Parameters -# -# [*ensure*] -# present|absent. Default: present -# -# [*filename*] -# Filename. Default: $title -# -# [*mode*] -# File mode. Default: 0644 -# -# [*owner*] -# [*group*] -# Owner/group. Default: `root`/`root` -# -# [*config*] -# Config hash. This will be expanded to an ini-file. Default: {} -# -# === Examples -# -# python::dotfile { '/var/lib/jenkins/.pip/pip.conf': -# ensure => present, -# owner => 'jenkins', -# group => 'jenkins', -# config => { -# 'global' => { -# 'index-url => 'https://mypypi.acme.com/simple/' -# 'extra-index-url => https://pypi.risedev.at/simple/ +# @summary Manages any python dotfiles with a simple config hash. +# +# @param ensure +# @param filename Filename. +# @param mode File mode. +# @param owner user owner of dotfile +# @param group group owner of dotfile +# @param config Config hash. This will be expanded to an ini-file. +# +# @example Create a pip config in /var/lib/jenkins/.pip/ +# python::dotfile { '/var/lib/jenkins/.pip/pip.conf': +# ensure => present, +# owner => 'jenkins', +# group => 'jenkins', +# config => { +# 'global' => { +# 'index-url' => 'https://mypypi.acme.com/simple/' +# 'extra-index-url' => 'https://pypi.risedev.at/simple/' +# } # } # } -# } # # define python::dotfile ( - $ensure = 'present', - $filename = $title, - $owner = 'root', - $group = 'root', - $mode = '0644', - $config = {}, + Enum['absent', 'present'] $ensure = 'present', + Stdlib::Absolutepath $filename = $title, + String[1] $owner = 'root', + String[1] $group = getvar('python::params::group'), + Stdlib::Filemode $mode = '0644', + Hash $config = {}, ) { $parent_dir = dirname($filename) exec { "create ${title}'s parent dir": command => "install -o ${owner} -g ${group} -d ${parent_dir}", - path => [ '/usr/bin', '/bin', '/usr/local/bin', ], + path => ['/usr/bin', '/bin', '/usr/local/bin',], creates => $parent_dir, } diff --git a/manifests/gunicorn.pp b/manifests/gunicorn.pp index 37739875..5393ac33 100644 --- a/manifests/gunicorn.pp +++ b/manifests/gunicorn.pp @@ -1,102 +1,85 @@ -# == Define: python::gunicorn # -# Manages Gunicorn virtual hosts. +# @summary Manages Gunicorn virtual hosts. # -# === Parameters -# -# [*ensure*] -# present|absent. Default: present -# -# [*virtualenv*] -# Run in virtualenv, specify directory. Default: disabled -# -# [*mode*] -# Gunicorn mode. -# wsgi|django. Default: wsgi -# -# [*dir*] -# Application directory. -# -# [*bind*] -# Bind on: 'HOST', 'HOST:PORT', 'unix:PATH'. +# @param ensure +# @param config_dir Configure the gunicorn config directory path. +# @param manage_config_dir Set if the gunicorn config directory should be created. +# @param virtualenv Run in virtualenv, specify directory. +# @param mode Gunicorn mode. +# @param dir Application directory. +# @param bind Bind on: 'HOST', 'HOST:PORT', 'unix:PATH'. # Default: system-wide: unix:/tmp/gunicorn-$name.socket # virtualenv: unix:${virtualenv}/${name}.socket -# -# [*environment*] -# Set ENVIRONMENT variable. Default: none -# -# [*appmodule*] -# Set the application module name for gunicorn to load when not using Django. -# Default: app:app -# -# [*osenv*] -# Allows setting environment variables for the gunicorn service. Accepts a -# hash of 'key': 'value' pairs. -# Default: false -# -# [*timeout*] -# Allows setting the gunicorn idle worker process time before being killed. -# The unit of time is seconds. -# Default: 30 -# -# [*template*] -# Which ERB template to use. Default: python/gunicorn.erb -# -# === Examples -# -# python::gunicorn { 'vhost': -# ensure => present, -# virtualenv => '/var/www/project1', -# mode => 'wsgi', -# dir => '/var/www/project1/current', -# bind => 'unix:/tmp/gunicorn.socket', -# environment => 'prod', -# owner => 'www-data', -# group => 'www-data', -# appmodule => 'app:app', -# osenv => { 'DBHOST' => 'dbserver.example.com' }, -# timeout => 30, -# template => 'python/gunicorn.erb', -# } -# -# === Authors -# -# Sergey Stankevich -# Ashley Penney -# Marc Fournier +# @param environment Set ENVIRONMENT variable. +# @param appmodule Set the application module name for gunicorn to load when not using Django. +# @param osenv Allows setting environment variables for the gunicorn service. Accepts a hash of 'key': 'value' pairs. +# @param timeout Allows setting the gunicorn idle worker process time before being killed. The unit of time is seconds. +# @param template Which ERB template to use. +# @param args Custom arguments to add in gunicorn config file. +# +# @example run gunicorn on vhost in virtualenv /var/www/project1 +# python::gunicorn { 'vhost': +# ensure => present, +# virtualenv => '/var/www/project1', +# mode => 'wsgi', +# dir => '/var/www/project1/current', +# bind => 'unix:/tmp/gunicorn.socket', +# environment => 'prod', +# owner => 'www-data', +# group => 'www-data', +# appmodule => 'app:app', +# osenv => { 'DBHOST' => 'dbserver.example.com' }, +# timeout => 30, +# template => 'python/gunicorn.erb', +# } # define python::gunicorn ( - $ensure = present, - $virtualenv = false, - $mode = 'wsgi', - $dir = false, - $bind = false, - $environment = false, - $owner = 'www-data', - $group = 'www-data', - $appmodule = 'app:app', - $osenv = false, - $timeout = 30, - $access_log_format = false, - $accesslog = false, - $errorlog = false, - $log_level = 'error', - $template = 'python/gunicorn.erb', + Stdlib::Absolutepath $dir, + Enum['present', 'absent'] $ensure = present, + Stdlib::Absolutepath $config_dir = '/etc/gunicorn.d', + Boolean $manage_config_dir = false, + Variant[Boolean,Stdlib::Absolutepath] $virtualenv = false, + Enum['wsgi', 'django'] $mode = 'wsgi', + Variant[String[1],Boolean] $bind = false, + Variant[String[1],Boolean] $environment = false, + String[1] $owner = 'www-data', + String[1] $group = 'www-data', + String[1] $appmodule = 'app:app', + Variant[Boolean,Hash] $osenv = false, + Integer $timeout = 30, + Variant[Boolean,Integer] $workers = false, + Variant[Boolean,String[1]] $access_log_format = false, + Variant[Boolean,Stdlib::Absolutepath] $accesslog = false, + Variant[Boolean,Stdlib::Absolutepath] $errorlog = false, + Python::Loglevel $log_level = 'error', + String[1] $template = 'python/gunicorn.erb', + Array $args = [], ) { + $processor_count = fact('processors.count') - # Parameter validation - if ! $dir { - fail('python::gunicorn: dir parameter must not be empty') - } - - validate_re($log_level, 'debug|info|warning|error|critical', "Invalid \$log_level value ${log_level}") + if $manage_config_dir { + file { $config_dir: + ensure => directory, + mode => '0755', + owner => 'root', + group => 'root', + } - file { "/etc/gunicorn.d/${name}": - ensure => $ensure, - mode => '0644', - owner => 'root', - group => 'root', - content => template($template), + file { "${config_dir}/${name}": + ensure => $ensure, + mode => '0644', + owner => 'root', + group => 'root', + content => template($template), + require => File[$config_dir], + } + } else { + file { "${config_dir}/${name}": + ensure => $ensure, + mode => '0644', + owner => 'root', + group => 'root', + content => template($template), + } } - } diff --git a/manifests/init.pp b/manifests/init.pp index 5072523a..31061daf 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,17 +1,8 @@ -# == Class: python +# @summary Installs and manages python, python-dev and gunicorn. # -# Installs and manages python, python-dev, python-virtualenv and Gunicorn. -# -# === Parameters -# -# [*ensure*] -# Desired installation state for the Python package. Valid options are absent, -# present and latest. Default: present -# -# [*version*] -# Python version to install. Beware that valid values for this differ a) by -# the provider you choose and b) by the osfamily/operatingsystem you are using. -# Default: system default +# @param default_system_version The default version of Python provided by the operating system. Only used as a fallback if Python is not installed yet to determine how to handle some actions that vary depending on the Python version used. +# @param ensure Desired installation state for the Python package. +# @param version Python version to install. Beware that valid values for this differ a) by the provider you choose and b) by the osfamily/operatingsystem you are using. # Allowed values: # - provider == pip: everything pip allows as a version after the 'python==' # - else: 'system', 'pypy', 3/3.3/... @@ -19,127 +10,74 @@ # - 'pypy' actually lets us use pypy as python. # - 3/3.3/... means you are going to install the python3/python3.3/... # package, if available on your osfamily. -# -# [*pip*] -# Desired installation state for python-pip. Boolean values are deprecated. -# Default: present -# Allowed values: 'absent', 'present', 'latest' -# -# [*dev*] -# Desired installation state for python-dev. Boolean values are deprecated. -# Default: absent -# Allowed values: 'absent', 'present', 'latest' -# -# [*virtualenv*] -# Desired installation state for python-virtualenv. Boolean values are -# deprecated. Default: absent -# Allowed values: 'absent', 'present', 'latest -# -# [*gunicorn*] -# Desired installation state for Gunicorn. Boolean values are deprecated. -# Default: absent -# Allowed values: 'absent', 'present', 'latest' -# -# [*manage_gunicorn*] -# Allow Installation / Removal of Gunicorn. Default: true -# -# [*provider*] -# What provider to use for installation of the packages, except gunicorn and -# Python itself. Default: system default provider -# Allowed values: 'pip' -# -# [*use_epel*] -# Boolean to determine if the epel class is used. Default: true -# -# === Examples -# -# class { 'python': -# version => 'system', -# pip => 'present', -# dev => 'present', -# virtualenv => 'present', -# gunicorn => 'present', -# } -# -# === Authors -# -# Sergey Stankevich -# Garrett Honeycutt +# @param pip Desired installation state for the python-pip package. +# @param dev Desired installation state for the python-dev package. +# @param gunicorn Desired installation state for Gunicorn. +# @param manage_gunicorn Allow Installation / Removal of Gunicorn. +# @param provider What provider to use for installation of the packages, except gunicorn and Python itself. +# @param use_epel to determine if the epel class is used. +# @param manage_scl Whether to manage core SCL packages or not. +# @param umask The default umask for invoked exec calls. +# @param manage_gunicorn manage the state for package gunicorn +# @param manage_python_package manage the state for package python +# @param manage_dev_package manage the state of the python development package +# @param manage_venv_package manage the state for package venv +# @param manage_pip_package manage the state for package pip +# +# @example install python from system python +# class { 'python': +# version => 'system', +# pip => 'present', +# dev => 'present', +# gunicorn => 'present', +# } +# @example install python3 from scl repo +# class { 'python' : +# ensure => 'present', +# version => 'rh-python36-python', +# dev => 'present', +# } # class python ( - $ensure = $python::params::ensure, - $version = $python::params::version, - $pip = $python::params::pip, - $dev = $python::params::dev, - $virtualenv = $python::params::virtualenv, - $gunicorn = $python::params::gunicorn, - $manage_gunicorn = $python::params::manage_gunicorn, - $provider = $python::params::provider, - $valid_versions = $python::params::valid_versions, - $python_pips = { }, - $python_virtualenvs = { }, - $python_pyvenvs = { }, - $python_requirements = { }, - $use_epel = $python::params::use_epel, -) inherits python::params{ - - if $provider != undef and $provider != '' { - validate_re($provider, ['^(pip|scl|rhscl)$'], - "Only 'pip', 'rhscl' and 'scl' are valid providers besides the system default. Detected provider is <${provider}>.") - } - + Python::Version $default_system_version, + Python::Package::Ensure $ensure = 'present', + Python::Version $version = $facts['os']['family'] ? { 'Archlinux' => 'system', default => '3' }, + Python::Package::Ensure $pip = 'present', + Python::Package::Ensure $dev = 'absent', + Python::Package::Ensure $venv = 'absent', + Python::Package::Ensure $gunicorn = 'absent', + Boolean $manage_gunicorn = true, + Boolean $manage_python_package = true, + Boolean $manage_dev_package = true, + Boolean $manage_venv_package = $python::params::manage_venv_package, + Boolean $manage_pip_package = $python::params::manage_pip_package, + String[1] $gunicorn_package_name = $python::params::gunicorn_package_name, + Optional[Python::Provider] $provider = undef, + Hash $python_pips = {}, + Hash $python_pyvenvs = {}, + Hash $python_requirements = {}, + Hash $python_dotfiles = {}, + Boolean $use_epel = $python::params::use_epel, + Boolean $rhscl_use_public_repository = true, + Stdlib::Httpurl $anaconda_installer_url = 'https://repo.anaconda.com/archive/Anaconda3-5.2.0-Linux-x86_64.sh', + Stdlib::Absolutepath $anaconda_install_path = '/opt/python', + Boolean $manage_scl = true, +) inherits python::params { $exec_prefix = $provider ? { - 'scl' => "scl enable ${version} -- ", - 'rhscl' => "scl enable ${version} -- ", + 'scl' => "/usr/bin/scl enable ${version} -- ", + 'rhscl' => "/usr/bin/scl enable ${version} -- ", default => '', } - validate_re($ensure, ['^(absent|present|latest)$']) - validate_re($version, concat(['system', 'pypy'], $valid_versions)) - - if $pip == false or $pip == true { - warning('Use of boolean values for the $pip parameter is deprecated') - } else { - validate_re($pip, ['^(absent|present|latest)$']) - } - - if $virtualenv == false or $virtualenv == true { - warning('Use of boolean values for the $virtualenv parameter is deprecated') - } else { - validate_re($virtualenv, ['^(absent|present|latest)$']) - } - - if $virtualenv == false or $virtualenv == true { - warning('Use of boolean values for the $virtualenv parameter is deprecated') - } else { - validate_re($virtualenv, ['^(absent|present|latest)$']) - } - - if $gunicorn == false or $gunicorn == true { - warning('Use of boolean values for the $gunicorn parameter is deprecated') - } else { - validate_re($gunicorn, ['^(absent|present|latest)$']) - } + contain python::install + contain python::config - validate_bool($manage_gunicorn) - validate_bool($use_epel) - - # Module compatibility check - $compatible = [ 'Debian', 'RedHat', 'Suse' ] - if ! ($::osfamily in $compatible) { - fail("Module is not compatible with ${::operatingsystem}") - } - - # Anchor pattern to contain dependencies - anchor { 'python::begin': } -> - class { 'python::install': } -> - class { 'python::config': } -> - anchor { 'python::end': } + Class['python::install'] + -> Class['python::config'] # Allow hiera configuration of python resources create_resources('python::pip', $python_pips) create_resources('python::pyvenv', $python_pyvenvs) - create_resources('python::virtualenv', $python_virtualenvs) create_resources('python::requirements', $python_requirements) - + create_resources('python::dotfile', $python_dotfiles) } diff --git a/manifests/install.pp b/manifests/install.pp index 049a3e31..60adfc05 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -1,142 +1,127 @@ -# == Class: python::install +# @api private +# @summary Installs core python packages # -# Installs core python packages, -# -# === Examples -# -# include python::install -# -# === Authors -# -# Sergey Stankevich -# Ashley Penney -# Fotis Gimian -# Garrett Honeycutt +# @example +# include python::install # class python::install { - - $python = $::python::version ? { - 'system' => 'python', - 'pypy' => 'pypy', - default => $python::version, - } - - $pythondev = $::osfamily ? { - 'RedHat' => "${python}-devel", - 'Debian' => "${python}-dev", - 'Suse' => "${python}-devel", + $python = $python::version ? { + 'system' => 'python', + 'pypy' => 'pypy', + /\A(python[23]\.[0-9]+)/ => $1, + /\A(python)?([0-9]+)/ => "python${2}", + /\Arh-python[0-9]{2}/ => $python::version, + default => "python${python::version}", } - $dev_ensure = $python::dev ? { - true => 'present', - false => 'absent', - default => $python::dev, + $pythondev = $facts['os']['family'] ? { + 'AIX' => "${python}-devel", + 'Debian' => "${python}-dev", + 'FreeBSD' => undef, + 'Gentoo' => undef, + 'Archlinux' => undef, + 'RedHat' => "${python}-devel", + 'Suse' => "${python}-devel", } - $pip_ensure = $python::pip ? { - true => 'present', - false => 'absent', - default => $python::pip, - } - - $venv_ensure = $python::virtualenv ? { - true => 'present', - false => 'absent', - default => $python::virtualenv, - } - - package { 'python': - ensure => $python::ensure, - name => $python, - } - - package { 'virtualenv': - ensure => $venv_ensure, - require => Package['python'], + if $python::manage_python_package { + package { 'python': + ensure => $python::ensure, + name => $python, + } } - case $python::provider { - pip: { - - package { 'pip': - ensure => $pip_ensure, + if $python::manage_venv_package { + ## + ## CentOS has no extra package for venv + ## + unless $facts['os']['family'] == 'RedHat' { + package { 'python-venv': + ensure => $python::venv, + name => "${python}-venv", require => Package['python'], } + } + } - package { 'python-dev': - ensure => $dev_ensure, - name => $pythondev, + case $python::provider { + 'pip': { + if $python::manage_pip_package { + package { 'pip': + ensure => $python::pip, + require => Package['python'], + } } - # Install pip without pip, see https://pip.pypa.io/en/stable/installing/. - exec { 'bootstrap pip': - command => '/usr/bin/curl https://bootstrap.pypa.io/get-pip.py | python', - creates => '/usr/local/bin/pip', - require => Package['python'], + if $python::manage_dev_package and $pythondev { + package { 'python-dev': + ensure => $python::dev, + name => $pythondev, + } } - # Puppet is opinionated about the pip command name - file { 'pip-python': - ensure => link, - path => '/usr/bin/pip-python', - target => '/usr/bin/pip', - require => Exec['bootstrap pip'], - } + # Respect the $python::pip setting + unless $python::pip == 'absent' { + # Install pip without pip, see https://pip.pypa.io/en/stable/installing/. + include python::pip::bootstrap - Exec['bootstrap pip'] -> File['pip-python'] -> Package <| provider == pip |> + Exec['bootstrap pip'] -> File['pip-python'] -> Package <| provider == pip |> - Package <| title == 'pip' |> { - name => 'pip', - provider => 'pip', - } - Package <| title == 'virtualenv' |> { - name => 'virtualenv', - provider => 'pip', + Package <| title == 'pip' |> { + name => 'pip', + provider => 'pip', + } } } - scl: { + 'scl': { # SCL is only valid in the RedHat family. If RHEL, package must be # enabled using the subscription manager outside of puppet. If CentOS, # the centos-release-SCL will install the repository. - $install_scl_repo_package = $::operatingsystem ? { - 'CentOS' => 'present', - default => 'absent', - } + if $python::manage_scl { + $install_scl_repo_package = $facts['os']['name'] ? { + 'CentOS' => 'present', + default => 'absent', + } - package { 'centos-release-SCL': - ensure => $install_scl_repo_package, - before => Package['scl-utils'], - } - package { 'scl-utils': - ensure => 'latest', - before => Package['python'], + package { 'centos-release-scl': + ensure => $install_scl_repo_package, + before => Package['scl-utils'], + } + + package { 'scl-utils': + ensure => 'present', + before => Package['python'], + } + + Package['scl-utils'] -> Package["${python}-scldevel"] + + if $python::pip != 'absent' { + Package['scl-utils'] -> Exec['python-scl-pip-install'] + } } - # This gets installed as a dependency anyway - # package { "${python::version}-python-virtualenv": - # ensure => $venv_ensure, - # require => Package['scl-utils'], - # } package { "${python}-scldevel": - ensure => $dev_ensure, - require => Package['scl-utils'], + ensure => $python::dev, } - if $pip_ensure != 'absent' { + + if $python::pip != 'absent' { exec { 'python-scl-pip-install': command => "${python::exec_prefix}easy_install pip", path => ['/usr/bin', '/bin'], creates => "/opt/rh/${python::version}/root/usr/bin/pip", - require => Package['scl-utils'], } } } - rhscl: { + 'rhscl': { # rhscl is RedHat SCLs from softwarecollections.org - $scl_package = "rhscl-${::python::version}-epel-${::operatingsystemmajrelease}-${::architecture}" - package { $scl_package: - source => "https://www.softwarecollections.org/en/scls/rhscl/${::python::version}/epel-${::operatingsystemmajrelease}-${::architecture}/download/${scl_package}.noarch.rpm", - provider => 'rpm', - tag => 'python-scl-repo', + if $python::rhscl_use_public_repository { + $scl_package = "rhscl-${python::version}-epel-${facts['os']['release']['major']}-${facts['os']['architecture']}" + + package { $scl_package: + source => "https://www.softwarecollections.org/en/scls/rhscl/${python::version}/epel-${facts['os']['release']['major']}-${facts['os']['architecture']}/download/${scl_package}.noarch.rpm", + provider => 'rpm', + tag => 'python-scl-repo', + } } Package <| title == 'python' |> { @@ -144,70 +129,115 @@ } package { "${python}-scldevel": - ensure => $dev_ensure, + ensure => $python::dev, tag => 'python-scl-package', } - if $pip_ensure != 'absent' { - exec { 'python-scl-pip-install': - command => "${python::exec_prefix}easy_install pip", - path => ['/usr/bin', '/bin'], - creates => "/opt/rh/${python::version}/root/usr/bin/pip", - } + package { "${python}-python-pip": + ensure => $python::pip, + tag => 'python-pip-package', } - Package <| tag == 'python-scl-repo' |> -> - Package <| tag == 'python-scl-package' |> -> - Exec['python-scl-pip-install'] - } + if $python::rhscl_use_public_repository { + Package <| tag == 'python-scl-repo' |> + -> Package <| tag == 'python-scl-package' |> + } - default: { + Package <| tag == 'python-scl-package' |> -> Package <| tag == 'python-pip-package' |> + } + 'anaconda': { + $installer_path = '/var/tmp/anaconda_installer.sh' - package { 'pip': - ensure => $pip_ensure, - require => Package['python'], + file { $installer_path: + source => $python::anaconda_installer_url, + mode => '0700', } - - package { 'python-dev': - ensure => $dev_ensure, - name => $pythondev, + -> exec { 'install_anaconda_python': + command => "${installer_path} -b -p ${python::anaconda_install_path}", + creates => $python::anaconda_install_path, + logoutput => true, } + -> exec { 'install_anaconda_virtualenv': + command => "${python::anaconda_install_path}/bin/pip install virtualenv", + creates => "${python::anaconda_install_path}/bin/virtualenv", + } + } + default: { + case $facts['os']['family'] { + 'AIX': { + if String($python::version) =~ /^python3/ { + class { 'python::pip::bootstrap': + version => 'pip3', + } + } else { + if $python::manage_pip_package { + package { 'python-pip': + ensure => $python::pip, + require => Package['python'], + provider => 'yum', + } + } + } - if $::osfamily == 'RedHat' { - if $pip_ensure != 'absent' { - if $python::use_epel == true { - include 'epel' - Class['epel'] -> Package['pip'] + if $python::manage_dev_package and $pythondev { + package { 'python-dev': + ensure => $python::dev, + name => $pythondev, + alias => $pythondev, + provider => 'yum', + } } } - if ($venv_ensure != 'absent') and ($::operatingsystemrelease =~ /^6/) { - if $python::use_epel == true { - include 'epel' - Class['epel'] -> Package['virtualenv'] + default: { + if $python::manage_pip_package { + package { 'pip': + ensure => $python::pip, + require => Package['python'], + } + } + + if $python::manage_dev_package and $pythondev { + package { 'python-dev': + ensure => $python::dev, + name => $pythondev, + alias => $pythondev, + } } } + } - $virtualenv_package = "${python}-virtualenv" - } else { - $virtualenv_package = $::lsbdistcodename ? { - 'jessie' => 'virtualenv', - default => 'python-virtualenv', + if $facts['os']['family'] == 'RedHat' { + if $python::pip != 'absent' and $python::use_epel and ($python::manage_pip_package or $python::manage_python_package) { + require epel } } - if $::python::version =~ /^3/ { - $pip_package = 'python3-pip' + if String($python::version) =~ /^python3/ { + $pip_package = "${python}-pip" + $pip_provider = $python.regsubst(/^.*python3\.?/,'pip3.').regsubst(/\.$/,'') + } elsif $facts['os']['family'] == 'RedHat' { + $pip_package = 'python3-pip' + $pip_provider = pip3 + } elsif $facts['os']['family'] == 'FreeBSD' { + $pip_package = "py${python::version}-pip" + $pip_provider = 'pip' + } elsif $facts['os']['family'] == 'Gentoo' { + $pip_package = 'dev-python/pip' + $pip_provider = 'pip' + } elsif $facts['os']['name'] == 'Ubuntu' { + $pip_package = 'python3-pip' + $pip_provider = 'pip3' + } elsif $facts['os']['name'] == 'Debian' { + $pip_package = 'python3-pip' + $pip_provider = 'pip3' } else { - $pip_package = 'python-pip' + $pip_package = 'python-pip' + $pip_provider = 'pip' } Package <| title == 'pip' |> { name => $pip_package, } - - Package <| title == 'virtualenv' |> { - name => $virtualenv_package, - } } } @@ -220,6 +250,7 @@ package { 'gunicorn': ensure => $gunicorn_ensure, + name => $python::gunicorn_package_name, } } } diff --git a/manifests/params.pp b/manifests/params.pp index c19ea65a..49bfdeac 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -1,20 +1,45 @@ -# == Class: python::params +# @api private +# @summary The python Module default configuration settings. # # The python Module default configuration settings. # class python::params { - $ensure = 'present' - $version = 'system' - $pip = 'present' - $dev = 'absent' - $virtualenv = 'absent' - $gunicorn = 'absent' - $manage_gunicorn = true - $provider = undef - $valid_versions = $::osfamily ? { - 'RedHat' => ['3','27','33'], - 'Debian' => ['3', '3.3', '2.7'], - 'Suse' => [], + # Module compatibility check + unless $facts['os']['family'] in ['AIX', 'Debian', 'FreeBSD', 'Gentoo', 'RedHat', 'Suse', 'Archlinux'] { + fail("Module is not compatible with ${facts['os']['name']}") + } + + if $facts['os']['family'] == 'RedHat' and $facts['os']['name'] != 'Fedora' { + $use_epel = true + } else { + $use_epel = false + } + + $group = $facts['os']['family'] ? { + 'AIX' => 'system', + 'FreeBSD' => 'wheel', + default => 'root' + } + + $pip_lookup_path = $facts['os']['family'] ? { + 'AIX' => ['/bin', '/usr/bin', '/usr/local/bin', '/opt/freeware/bin/',], + default => ['/bin', '/usr/bin', '/usr/local/bin',] + } + + $gunicorn_package_name = $facts['os']['family'] ? { + 'RedHat' => $facts['os']['release']['major'] ? { + '7' => 'python-gunicorn', + default => 'python3-gunicorn', + }, + default => 'gunicorn', + } + + $manage_pip_package = $facts['os']['family'] ? { + 'Archlinux' => false, + default => true, + } + $manage_venv_package = $facts['os']['family'] ? { + 'Archlinux' => false, + default => true, } - $use_epel = true } diff --git a/manifests/pip.pp b/manifests/pip.pp index b50d7efb..9d568207 100644 --- a/manifests/pip.pp +++ b/manifests/pip.pp @@ -1,91 +1,83 @@ -# == Define: python::pip +# @summary Installs and manages packages from pip. # -# Installs and manages packages from pip. +# @param name must be unique +# @param pkgname the name of the package. +# @param ensure Require pip to be available. +# @param virtualenv virtualenv to run pip in. +# @param pip_provider version of pip you wish to use. +# @param url URL to install from. +# @param owner The owner of the virtualenv being manipulated. +# @param group The group of the virtualenv being manipulated. +# @param index Base URL of Python package index. +# @param extra_index Base URL of extra Python package index. +# @param proxy Proxy server to use for outbound connections. +# @param editable If true the package is installed as an editable resource. +# @param environment Additional environment variables required to install the packages. +# @param extras Extra features provided by the package which should be installed. +# @param timeout The maximum time in seconds the "pip install" command should take. +# @param install_args Any additional installation arguments that will be supplied when running pip install. +# @param uninstall_args Any additional arguments that will be supplied when running pip uninstall. +# @param log_dir Log directory +# @param egg The egg name to use +# @param umask # -# === Parameters -# -# [*name] -# must be unique -# -# [*pkgname] -# name of the package. If pkgname is not specified, use name (title) instead. -# -# [*ensure*] -# present|absent. Default: present -# -# [*virtualenv*] -# virtualenv to run pip in. -# -# [*url*] -# URL to install from. Default: none -# -# [*owner*] -# The owner of the virtualenv being manipulated. Default: root -# -# [*group*] -# The group of the virtualenv being manipulated. Default: root -# -# [*index*] -# Base URL of Python package index. Default: none (http://pypi.python.org/simple/) -# -# [*proxy*] -# Proxy server to use for outbound connections. Default: none -# -# [*editable*] -# Boolean. If true the package is installed as an editable resource. -# -# [*environment*] -# Additional environment variables required to install the packages. Default: none -# -# [*timeout*] -# The maximum time in seconds the "pip install" command should take. Default: 1800 -# -# [*install_args*] -# String. Any additional installation arguments that will be supplied -# when running pip install. -# -# [*uninstall_args*] -# String. Any additional arguments that will be supplied when running -# pip uninstall. -# -# [*log_dir*] -# String. Log directory. -# -# === Examples -# -# python::pip { 'flask': -# virtualenv => '/var/www/project1', -# proxy => 'http://proxy.domain.com:3128', -# index => 'http://www.example.com/simple/', -# } -# -# === Authors -# -# Sergey Stankevich -# Fotis Gimian +# @example Install Flask to /var/www/project1 using a proxy +# python::pip { 'flask': +# virtualenv => '/var/www/project1', +# proxy => 'http://proxy.domain.com:3128', +# index => 'http://www.example.com/simple/', +# } +# @example Install cx_Oracle with pip +# python::pip { 'cx_Oracle' : +# pkgname => 'cx_Oracle', +# ensure => '5.1.2', +# virtualenv => '/var/www/project1', +# owner => 'appuser', +# proxy => 'http://proxy.domain.com:3128', +# environment => ['ORACLE_HOME=/usr/lib/oracle/11.2/client64'], +# install_args => '-e', +# timeout => 1800, +# } +# @example Install Requests with pip3 +# python::pip { 'requests' : +# ensure => 'present', +# pkgname => 'requests', +# pip_provider => 'pip3', +# virtualenv => '/var/www/project1', +# owner => 'root', +# timeout => 1800 +# } # define python::pip ( - $pkgname = $name, - $ensure = present, - $virtualenv = 'system', - $url = false, - $owner = 'root', - $group = 'root', - $index = false, - $proxy = false, - $egg = false, - $editable = false, - $environment = [], - $install_args = '', - $uninstall_args = '', - $timeout = 1800, - $log_dir = '/tmp', - $path = ['/usr/local/bin','/usr/bin','/bin', '/usr/sbin'], + String[1] $pkgname = $name, + Variant[Enum[present, absent, latest], String[1]] $ensure = present, + Variant[Enum['system'], Stdlib::Absolutepath] $virtualenv = 'system', + String[1] $pip_provider = 'pip', + Variant[Boolean, String] $url = false, + String[1] $owner = 'root', + Optional[String[1]] $group = getvar('python::params::group'), + Optional[Python::Umask] $umask = undef, + Variant[Boolean,String[1]] $index = false, + Variant[Boolean,String[1]] $extra_index = false, + Optional[Stdlib::HTTPUrl] $proxy = undef, + Any $egg = false, + Boolean $editable = false, + Array $environment = [], + Array $extras = [], + Optional[String[1]] $install_args = undef, + Optional[String[1]] $uninstall_args = undef, + Numeric $timeout = 1800, + String[1] $log_dir = '/tmp', + Array[String] $path = ['/usr/local/bin','/usr/bin','/bin', '/usr/sbin'], + String[1] $exec_provider = 'shell', ) { - $python_provider = getparam(Class['python'], 'provider') $python_version = getparam(Class['python'], 'version') - + + if $virtualenv != 'system' { + Python::Pyvenv <| |> -> Python::Pip[$name] + } + # Get SCL exec prefix # NB: this will not work if you are running puppet from scl enabled shell $exec_prefix = $python_provider ? { @@ -93,12 +85,13 @@ 'rhscl' => "scl enable ${python_version} -- ", default => '', } - - # Parameter validation - if ! $virtualenv { - fail('python::pip: virtualenv parameter must not be empty') + + $_path = $python_provider ? { + 'anaconda' => concat(["${python::anaconda_install_path}/bin"], $path), + default => $path, } + # Parameter validation if $virtualenv == 'system' and $owner != 'root' { fail('python::pip: root user must be used when virtualenv is system') } @@ -108,25 +101,28 @@ default => $virtualenv, } - validate_absolute_path($cwd) - $log = $virtualenv ? { 'system' => $log_dir, default => $virtualenv, } $pip_env = $virtualenv ? { - 'system' => "${exec_prefix}pip", - default => "${virtualenv}/bin/pip", + 'system' => "${exec_prefix}${pip_provider}", + default => "${exec_prefix}${virtualenv}/bin/${pip_provider}", } $pypi_index = $index ? { - false => '', - default => "--index-url=${index}", - } + false => '', + default => "--index-url=${index}", + } + + $pypi_extra_index = $extra_index ? { + false => '', + default => "--extra-index-url=${extra_index}", + } $proxy_flag = $proxy ? { - false => '', + undef => '', default => "--proxy=${proxy}", } @@ -137,134 +133,132 @@ $install_editable = '' } - #TODO: Do more robust argument checking, but below is a start - if ($ensure == absent) and ($install_args != '') { + # TODO: Do more robust argument checking, but below is a start + if ($ensure == absent) and $install_args { fail('python::pip cannot provide install_args with ensure => absent') } - if ($ensure == present) and ($uninstall_args != '') { + if ($ensure == present) and $uninstall_args { fail('python::pip cannot provide uninstall_args with ensure => present') } - # Check if searching by explicit version. - if $ensure =~ /^((19|20)[0-9][0-9]-(0[1-9]|1[1-2])-([0-2][1-9]|3[0-1])|[0-9]+\.[0-9]+(\.[0-9]+)?)$/ { - $grep_regex = "^${pkgname}==${ensure}\$" - } else { - $grep_regex = $pkgname ? { - /==/ => "^${pkgname}\$", - default => "^${pkgname}==", + if $pkgname =~ /==/ { + $parts = split($pkgname, '==') + $real_pkgname = $parts[0] + + $_ensure = $ensure ? { + 'absent' => 'absent', + default => $parts[1], } + } else { + $real_pkgname = $pkgname + $_ensure = $ensure + } + + # We do not try to mimic a version scheme validation which is already implemented by the package manager. + # If it starts with a number it is probably a version. + # If it wasn't or if there is any error, the package manager will trigger a failure. + $grep_regex = $_ensure ? { + /^(present|absent|latest)$/ => "^${real_pkgname}[[:space:]].*$", + /^v?[0-9].*$/ => "^${real_pkgname}[[:space:]]\\+(\\?${_ensure}\\()$\\|$\\|, \\|[[:space:]]\\)", + default => fail('ensure can be a version number (e.g. 1.7.0 or v1.7.0) or one of: present, absent, latest') + } + + $extras_string = empty($extras) ? { + true => '', + default => sprintf('[%s]',join($extras,',')), } $egg_name = $egg ? { - false => $pkgname, + false => "${real_pkgname}${extras_string}", default => $egg } $source = $url ? { - false => $pkgname, - /^(\/|[a-zA-Z]\:)/ => $url, - /^(git\+|hg\+|bzr\+|svn\+)(http|https|ssh|svn|sftp|ftp|lp)(:\/\/).+$/ => $url, - default => "${url}#egg=${egg_name}", + false => "${real_pkgname}${extras_string}", + /^(\/|[a-zA-Z]\:)/ => "'${url}'", + /^(git\+|hg\+|bzr\+|svn\+)(http|https|ssh|svn|sftp|ftp|lp|git)(:\/\/).+$/ => "'${url}'", + default => "'${url}#egg=${egg_name}'", } - # We need to jump through hoops to make sure we issue the correct pip command - # depending on wheel support and versions. - # - # Pip does not support wheels prior to version 1.4.0 - # Pip wheels require setuptools/distribute > 0.8 - # Python 2.6 and older does not support setuptools/distribute > 0.8 - # Pip >= 1.5 tries to use wheels by default, even if wheel package is not - # installed, in this case the --no-use-wheel flag needs to be passed - # Versions prior to 1.5 don't support the --no-use-wheel flag - # - # To check for this we test for wheel parameter using help and then using - # version, this makes sure we only use wheels if they are supported and - # installed + $pip_install = "${pip_env} install" + $pip_common_args = "${pypi_index} ${pypi_extra_index} ${proxy_flag} ${install_editable} ${source}" # Explicit version out of VCS when PIP supported URL is provided - if $source =~ /^(git\+|hg\+|bzr\+|svn\+)(http|https|ssh|svn|sftp|ftp|lp)(:\/\/).+$/ { - if $ensure != present and $ensure != latest { - exec { "pip_install_${name}": - command => "${pip_env} wheel --help > /dev/null 2>&1 && { ${pip_env} wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { ${pip_env} --log ${log}/pip.log install ${install_args} \$wheel_support_flag ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source}@${ensure}#egg=${egg_name} || ${pip_env} --log ${log}/pip.log install ${install_args} ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source}@${ensure}#egg=${egg_name} ;}", - unless => "${pip_env} freeze | grep -i -e ${grep_regex}", - user => $owner, - group => $group, - cwd => $cwd, - environment => $environment, - timeout => $timeout, - path => $path, - } + if $source =~ /^'(git\+|hg\+|bzr\+|svn\+)(http|https|ssh|svn|sftp|ftp|lp|git)(:\/\/).+'$/ { + if $_ensure != present and $_ensure != latest { + $command = "${pip_install} ${install_args} ${pip_common_args}@${_ensure}#egg=${egg_name}" + $unless_command = "${pip_env} list | grep -i -e '${grep_regex}'" } else { - exec { "pip_install_${name}": - command => "${pip_env} wheel --help > /dev/null 2>&1 && { ${pip_env} wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { ${pip_env} --log ${log}/pip.log install ${install_args} \$wheel_support_flag ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source} || ${pip_env} --log ${log}/pip.log install ${install_args} ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source} ;}", - unless => "${pip_env} freeze | grep -i -e ${grep_regex}", - user => $owner, - group => $group, - cwd => $cwd, - environment => $environment, - timeout => $timeout, - path => $path, - } + $command = "${pip_install} ${install_args} ${pip_common_args}" + $unless_command = "${pip_env} list | grep -i -e '${grep_regex}'" } } else { - case $ensure { - /^((19|20)[0-9][0-9]-(0[1-9]|1[1-2])-([0-2][1-9]|3[0-1])|[0-9]+\.[0-9]+(\.[0-9]+)?)$/: { - # Version formats as per http://guide.python-distribute.org/specification.html#standard-versioning-schemes - # Explicit version. - exec { "pip_install_${name}": - command => "${pip_env} wheel --help > /dev/null 2>&1 && { ${pip_env} wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { ${pip_env} --log ${log}/pip.log install ${install_args} \$wheel_support_flag ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source}==${ensure} || ${pip_env} --log ${log}/pip.log install ${install_args} ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source}==${ensure} ;}", - unless => "${pip_env} freeze | grep -i -e ${grep_regex}", - user => $owner, - group => $group, - cwd => $cwd, - environment => $environment, - timeout => $timeout, - path => $path, - } + case $_ensure { + /^v?[0-9].*$/: { + # Specific version + $command = "${pip_install} ${install_args} ${pip_common_args}==${_ensure}" + $unless_command = "${pip_env} list | grep -i -e '${grep_regex}'" } - present: { + 'present': { # Whatever version is available. - exec { "pip_install_${name}": - command => "${pip_env} wheel --help > /dev/null 2>&1 && { ${pip_env} wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { ${pip_env} --log ${log}/pip.log install \$wheel_support_flag ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source} || ${pip_env} --log ${log}/pip.log install ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source} ;}", - unless => "${pip_env} freeze | grep -i -e ${grep_regex}", - user => $owner, - group => $group, - cwd => $cwd, - environment => $environment, - timeout => $timeout, - path => $path, - } + $command = "${pip_install} ${install_args} ${pip_common_args}" + $unless_command = "${pip_env} list | grep -i -e '${grep_regex}'" } - latest: { - # Latest version. - exec { "pip_install_${name}": - command => "${pip_env} wheel --help > /dev/null 2>&1 && { ${pip_env} wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { ${pip_env} --log ${log}/pip.log install --upgrade \$wheel_support_flag ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source} || ${pip_env} --log ${log}/pip.log install --upgrade ${pypi_index} ${proxy_flag} ${install_args} ${install_editable} ${source} ;}", - unless => "${pip_env} search ${proxy_flag} ${source} | grep -i INSTALLED | grep -i latest", - user => $owner, - group => $group, - cwd => $cwd, - environment => $environment, - timeout => $timeout, - path => $path, + 'latest': { + $pip_version = $facts['pip_version'] + if $pip_version and versioncmp($pip_version, '21.1') == -1 and versioncmp($pip_version, '20.2.4') == 1 { + $legacy_resolver = '--use-deprecated=legacy-resolver' + } else { + $legacy_resolver = '' } + + # Unfortunately this is the smartest way of getting the latest available package version with pip as of now + # Note: we DO need to repeat ourselves with "from version" in both grep and sed as on some systems pip returns + # more than one line with paretheses. + $latest_version = join( + [ + "${pip_install} ${legacy_resolver} ${pypi_index} ${pypi_extra_index} ${proxy_flag}", + " ${install_args} ${install_editable} '${real_pkgname}==9!0dev0+x' 2>&1", + " | sed -nE 's/.*\\(from versions: (.*, )*(.*)\\)/\\2/p'", + ' | tr -d "[:space:]"', + ], + ) + + # Packages with underscores in their names are listed with dashes in their place in `pip freeze` output + $pkgname_with_dashes = regsubst($real_pkgname, '_', '-', 'G') + $grep_regex_pkgname_with_dashes = "^${pkgname_with_dashes}==" + $installed_version = join(["${pip_env} freeze --all", " | grep -i -e ${grep_regex_pkgname_with_dashes} | cut -d= -f3", " | tr -d '[:space:]'",]) + + $command = "${pip_install} --upgrade ${install_args} ${pip_common_args}" + $unless_command = "[ \$(${latest_version}) = \$(${installed_version}) ]" } default: { # Anti-action, uninstall. - exec { "pip_uninstall_${name}": - command => "echo y | ${pip_env} uninstall ${uninstall_args} ${proxy_flag}", - onlyif => "${pip_env} freeze | grep -i -e ${grep_regex}", - user => $owner, - group => $group, - cwd => $cwd, - environment => $environment, - timeout => $timeout, - path => $path, - } + $command = "echo y | ${pip_env} uninstall ${uninstall_args} ${proxy_flag} ${real_pkgname}" + $unless_command = "! ${pip_env} list | grep -i -e '${grep_regex}'" } } } + + $pip_installer = ($ensure == 'absent') ? { + true => "pip_uninstall_${name}", + false => "pip_install_${name}", + } + + exec { $pip_installer: + command => $command, + unless => $unless_command, + user => $owner, + group => $group, + umask => $umask, + cwd => $cwd, + environment => $environment, + timeout => $timeout, + path => $_path, + provider => $exec_provider, + } } diff --git a/manifests/pip/bootstrap.pp b/manifests/pip/bootstrap.pp new file mode 100644 index 00000000..b060a6d0 --- /dev/null +++ b/manifests/pip/bootstrap.pp @@ -0,0 +1,70 @@ +# @summary allow to bootstrap pip when python is managed from other module +# +# @param version should be pip or pip3 +# @param manage_python if python module will manage deps +# @param http_proxy Proxy server to use for outbound connections. +# +# @example +# class { 'python::pip::bootstrap': +# version => 'pip', +# } +# +class python::pip::bootstrap ( + Enum['pip', 'pip3'] $version = 'pip', + Variant[Boolean, String] $manage_python = false, + Optional[Stdlib::HTTPUrl] $http_proxy = undef, + String[1] $exec_provider = 'shell', +) inherits python::params { + if $manage_python { + include python + } else { + $target_src_pip_path = $facts['os']['family'] ? { + 'AIX' => '/opt/freeware/bin', + default => '/usr/bin' + } + + $environ = $http_proxy ? { + undef => [], + default => $facts['os']['family'] ? { + 'AIX' => ["http_proxy=${http_proxy}", "https_proxy=${http_proxy}"], + default => ["HTTP_PROXY=${http_proxy}", "HTTPS_PROXY=${http_proxy}"], + } + } + + if $version == 'pip3' { + exec { 'bootstrap pip3': + command => '/usr/bin/curl https://bootstrap.pypa.io/get-pip.py | python3', + environment => $environ, + unless => 'which pip3', + path => $python::params::pip_lookup_path, + require => Package['python3'], + provider => $exec_provider, + } + + # puppet is opinionated about the pip command name + file { 'pip3-python': + ensure => link, + path => '/usr/bin/pip3', + target => "${target_src_pip_path}/pip${facts['python3_release']}", + require => Exec['bootstrap pip3'], + } + } else { + exec { 'bootstrap pip': + command => '/usr/bin/curl https://bootstrap.pypa.io/get-pip.py | python', + environment => $environ, + unless => 'which pip', + path => $python::params::pip_lookup_path, + require => Package['python'], + provider => $exec_provider, + } + + # puppet is opinionated about the pip command name + file { 'pip-python': + ensure => link, + path => '/usr/bin/pip', + target => "${target_src_pip_path}/pip${facts['python2_release']}", + require => Exec['bootstrap pip'], + } + } + } +} diff --git a/manifests/pyvenv.pp b/manifests/pyvenv.pp index 216d66d5..7aa7202f 100644 --- a/manifests/pyvenv.pp +++ b/manifests/pyvenv.pp @@ -1,91 +1,106 @@ -# == Define: python::pyvenv # -# Create a Python3 virtualenv using pyvenv. +# @summary Create a Python3 virtualenv using pyvenv. # -# === Parameters +# @param ensure +# @param version Python version to use. +# @param systempkgs Copy system site-packages into virtualenv +# @param venv_dir Directory to install virtualenv to +# @param owner The owner of the virtualenv being manipulated +# @param group The group relating to the virtualenv being manipulated +# @param mode Optionally specify directory mode +# @param path Specifies the PATH variable. +# @param environment Optionally specify environment variables for pyvenv +# @param prompt Optionally specify the virtualenv prompt (python >= 3.6) +# @param python_path Optionally specify python path for creation of virtualenv # -# [*ensure*] -# present|absent. Default: present -# -# [*version*] -# Python version to use. Default: system default -# -# [*systempkgs*] -# Copy system site-packages into virtualenv. Default: don't -# -# [*venv_dir*] -# Directory to install virtualenv to. Default: $name -# -# [*owner*] -# The owner of the virtualenv being manipulated. Default: root -# -# [*group*] -# The group relating to the virtualenv being manipulated. Default: root -# -# [*mode*] -# Optionally specify directory mode. Default: 0755 -# -# [*path*] -# Specifies the PATH variable. Default: [ '/bin', '/usr/bin', '/usr/sbin' ] - -# [*environment*] -# Optionally specify environment variables for pyvenv -# -# === Examples -# -# python::venv { '/var/www/project1': -# ensure => present, -# version => 'system', -# systempkgs => true, -# } -# -# === Authors -# -# Sergey Stankevich -# Ashley Penney -# Marc Fournier -# Fotis Gimian -# Seth Cleveland +# @example +# python::pyvenv { '/var/www/project1' : +# ensure => present, +# version => 'system', +# systempkgs => true, +# venv_dir => '/home/appuser/virtualenvs', +# owner => 'appuser', +# group => 'apps', +# } # define python::pyvenv ( - $ensure = present, - $version = 'system', - $systempkgs = false, - $venv_dir = $name, - $owner = 'root', - $group = 'root', - $mode = '0755', - $path = [ '/bin', '/usr/bin', '/usr/sbin', '/usr/local/bin' ], - $environment = [], + Python::Package::Ensure $ensure = present, + Python::Version $version = 'system', + Boolean $systempkgs = false, + Stdlib::Absolutepath $venv_dir = $name, + String[1] $owner = 'root', + String[1] $group = 'root', + Stdlib::Filemode $mode = '0755', + Array[Stdlib::Absolutepath] $path = ['/bin', '/usr/bin', '/usr/sbin', '/usr/local/bin',], + Array[String[1]] $environment = [], + Optional[String[1]] $prompt = undef, + Python::Venv::PipVersion $pip_version = 'latest', + Optional[Stdlib::Absolutepath] $python_path = undef, ) { + include python if $ensure == 'present' { + $python_version = $version ? { + 'system' => $facts['python3_version'].lest || { $python::default_system_version }, + default => $version, + } + + $python_version_parts = split($python_version, '[.]') + $normalized_python_version = sprintf('%s.%s', $python_version_parts[0], $python_version_parts[1]) + + $local_exec_prefix = $python_path ? { + undef => $python::exec_prefix, + default => $python_path, + } + # pyvenv is deprecated since 3.6 and will be removed in 3.8 + if $python_path != undef { + $virtualenv_cmd = "${python_path} -m venv" + } elsif versioncmp($normalized_python_version, '3.6') >=0 { + $virtualenv_cmd = "${local_exec_prefix}python${normalized_python_version} -m venv" + } else { + $virtualenv_cmd = "${local_exec_prefix}pyvenv-${normalized_python_version}" + } - $virtualenv_cmd = $version ? { - 'system' => "${python::exec_prefix}pyvenv", - default => "${python::exec_prefix}pyvenv-${version}", + $_path = $python::provider ? { + 'anaconda' => concat(["${python::anaconda_install_path}/bin"], $path), + default => $path, } - if ( $systempkgs == true ) { + if $systempkgs == true { $system_pkgs_flag = '--system-site-packages' } else { $system_pkgs_flag = '' } + if versioncmp($normalized_python_version, '3.6') >=0 and $prompt { + $prompt_arg = "--prompt ${shell_escape($prompt)}" + } else { + $prompt_arg = '' + } + file { $venv_dir: - ensure => directory, - owner => $owner, - group => $group, - mode => $mode + ensure => directory, + owner => $owner, + group => $group, + mode => $mode, + require => Class['python::install'], + } + + $pip_cmd = "${python::exec_prefix}${venv_dir}/bin/pip" + + $pip_upgrade = ($pip_version != 'latest') ? { + true => "--upgrade 'pip ${pip_version}'", + false => '--upgrade pip', } exec { "python_virtualenv_${venv_dir}": - command => "${virtualenv_cmd} --clear ${system_pkgs_flag} ${venv_dir}", + command => "${virtualenv_cmd} --clear ${system_pkgs_flag} ${prompt_arg} ${venv_dir} && ${pip_cmd} install ${pip_upgrade} && ${pip_cmd} install --upgrade setuptools", user => $owner, creates => "${venv_dir}/bin/activate", - path => $path, + path => $_path, cwd => '/tmp', environment => $environment, + timeout => 600, unless => "grep '^[\\t ]*VIRTUAL_ENV=[\\\\'\\\"]*${venv_dir}[\\\"\\\\'][\\t ]*$' ${venv_dir}/bin/activate", #Unless activate exists and VIRTUAL_ENV is correct we re-create the virtualenv require => File[$venv_dir], } diff --git a/manifests/requirements.pp b/manifests/requirements.pp index 9f773f3c..374a70ae 100644 --- a/manifests/requirements.pp +++ b/manifests/requirements.pp @@ -1,84 +1,48 @@ -# == Define: python::requirements # -# Installs and manages Python packages from requirements file. -# -# === Parameters -# -# [*requirements*] -# Path to the requirements file. Defaults to the resource name -# -# [*virtualenv*] -# virtualenv to run pip in. Default: system-wide -# -# [*owner*] -# The owner of the virtualenv being manipulated. Default: root -# -# [*group*] -# The group relating to the virtualenv being manipulated. Default: root -# -# [*proxy*] -# Proxy server to use for outbound connections. Default: none -# -# [*src*] -# Pip --src parameter; if the requirements file contains --editable resources, -# this parameter specifies where they will be installed. See the pip -# documentation for more. Default: none (i.e. use the pip default). -# -# [*environment*] -# Additional environment variables required to install the packages. Default: none -# -# [*forceupdate*] -# Run a pip install requirements even if we don't receive an event from the -# requirements file - Useful for when the requirements file is written as part of a -# resource other than file (E.g vcsrepo) -# -# [*cwd*] -# The directory from which to run the "pip install" command. Default: undef -# -# [*extra_pip_args*] -# Extra arguments to pass to pip after the requirements file -# -# [*manage_requirements*] -# Create the requirements file if it doesn't exist. Default: true -# -# [*fix_requirements_owner*] -# Change owner and group of requirements file. Default: true -# -# [*log_dir*] -# String. Log directory. -# -# [*timeout*] -# The maximum time in seconds the "pip install" command should take. Default: 1800 -# -# === Examples -# -# python::requirements { '/var/www/project1/requirements.txt': -# virtualenv => '/var/www/project1', -# proxy => 'http://proxy.domain.com:3128', -# } -# -# === Authors -# -# Sergey Stankevich -# Ashley Penney -# Fotis Gimian +# @summary Installs and manages Python packages from requirements file. +# +# @param requirements Path to the requirements file. +# @param virtualenv virtualenv to run pip in. +# @param pip_provider version of pip you wish to use. +# @param owner The owner of the virtualenv being manipulated. +# @param group The group relating to the virtualenv being manipulated. +# @param proxy Proxy server to use for outbound connections. +# @param src Pip --src parameter to; if the requirements file contains --editable resources, this parameter specifies where they will be installed. See the pip documentation for more. +# @param environment Additional environment variables required to install the packages. +# @param forceupdate Run a pip install requirements even if we don't receive an event from the requirements file - Useful for when the requirements file is written as part of a resource other than file (E.g vcsrepo) +# @param cwd The directory from which to run the "pip install" command. +# @param extra_pip_args Extra arguments to pass to pip after the requirements file +# @param manage_requirements Create the requirements file if it doesn't exist. +# @param fix_requirements_owner Change owner and group of requirements file. +# @param log_dir Log directory. +# @param timeout The maximum time in seconds the "pip install" command should take. +# +# @example install pip requirements from /var/www/project1/requirements.txt +# python::requirements { '/var/www/project1/requirements.txt' : +# virtualenv => '/var/www/project1', +# proxy => 'http://proxy.domain.com:3128', +# owner => 'appuser', +# group => 'apps', +# } # define python::requirements ( - $requirements = $name, - $virtualenv = 'system', - $owner = 'root', - $group = 'root', - $proxy = false, - $src = false, - $environment = [], - $forceupdate = false, - $cwd = undef, - $extra_pip_args = '', - $manage_requirements = true, - $fix_requirements_owner = true, - $log_dir = '/tmp', - $timeout = 1800, + Stdlib::Absolutepath $requirements = $name, + Variant[Enum['system'],Stdlib::Absolutepath] $virtualenv = 'system', + Enum['pip', 'pip3'] $pip_provider = 'pip', + String[1] $owner = 'root', + String[1] $group = 'root', + Optional[Stdlib::HTTPUrl] $proxy = undef, + Any $src = false, + Array $environment = [], + Boolean $forceupdate = false, + Optional[Stdlib::Absolutepath] $cwd = undef, + Optional[String[1]] $extra_pip_args = undef, + Boolean $manage_requirements = true, + Boolean $fix_requirements_owner = true, + Stdlib::Absolutepath $log_dir = '/tmp', + Integer $timeout = 1800, ) { + include python if $virtualenv == 'system' and ($owner != 'root' or $group != 'root') { fail('python::pip: root user must be used when virtualenv is system') @@ -98,13 +62,13 @@ } $pip_env = $virtualenv ? { - 'system' => "${python::exec_prefix} pip", - default => "${python::exec_prefix} ${virtualenv}/bin/pip", + 'system' => "${python::exec_prefix} ${pip_provider}", + default => "${python::exec_prefix} ${virtualenv}/bin/${pip_provider}", } $proxy_flag = $proxy ? { - false => '', - default => "--proxy=${proxy}", + undef => '', + default => "--proxy=${proxy}", } $src_flag = $src ? { @@ -116,7 +80,7 @@ # the same requirements file. if !defined(File[$requirements]) and $manage_requirements == true { file { $requirements: - ensure => present, + ensure => file, mode => '0644', owner => $owner_real, group => $group_real, @@ -124,17 +88,22 @@ replace => false, content => '# Puppet will install and/or update pip packages listed here', } + + $local_subscribe = File[$requirements] + } elsif File[$requirements] and $manage_requirements == true { + $local_subscribe = File[$requirements] + } else { + $local_subscribe = undef } exec { "python_requirements${name}": provider => shell, - command => "${pip_env} --log ${log}/pip.log install ${proxy_flag} ${src_flag} -r ${requirements} ${extra_pip_args}", + command => "${pip_env} install ${proxy_flag} ${src_flag} -r ${requirements} ${extra_pip_args}", refreshonly => !$forceupdate, timeout => $timeout, cwd => $cwd, user => $owner, - subscribe => File[$requirements], + subscribe => $local_subscribe, environment => $environment, } - } diff --git a/manifests/virtualenv.pp b/manifests/virtualenv.pp deleted file mode 100644 index 459b3399..00000000 --- a/manifests/virtualenv.pp +++ /dev/null @@ -1,202 +0,0 @@ -# == Define: python::virtualenv -# -# Creates Python virtualenv. -# -# === Parameters -# -# [*ensure*] -# present|absent. Default: present -# -# [*version*] -# Python version to use. Default: system default -# -# [*requirements*] -# Path to pip requirements.txt file. Default: none -# -# [*systempkgs*] -# Copy system site-packages into virtualenv. Default: don't -# If virtualenv version < 1.7 this flag has no effect since -# [*venv_dir*] -# Directory to install virtualenv to. Default: $name -# -# [*distribute*] -# Include distribute in the virtualenv. Default: true -# -# [*index*] -# Base URL of Python package index. Default: none (http://pypi.python.org/simple/) -# -# [*owner*] -# The owner of the virtualenv being manipulated. Default: root -# -# [*group*] -# The group relating to the virtualenv being manipulated. Default: root -# -# [*mode*] -# Optionally specify directory mode. Default: 0755 -# -# [*proxy*] -# Proxy server to use for outbound connections. Default: none -# -# [*environment*] -# Additional environment variables required to install the packages. Default: none -# -# [*path*] -# Specifies the PATH variable. Default: [ '/bin', '/usr/bin', '/usr/sbin' ] -# -# [*cwd*] -# The directory from which to run the "pip install" command. Default: undef -# -# [*timeout*] -# The maximum time in seconds the "pip install" command should take. Default: 1800 -# -# [*extra_pip_args*] -# Extra arguments to pass to pip after requirements file. Default: blank -# -# === Examples -# -# python::virtualenv { '/var/www/project1': -# ensure => present, -# version => 'system', -# requirements => '/var/www/project1/requirements.txt', -# proxy => 'http://proxy.domain.com:3128', -# systempkgs => true, -# index => 'http://www.example.com/simple/', -# } -# -# === Authors -# -# Sergey Stankevich -# Shiva Poudel -# -define python::virtualenv ( - $ensure = present, - $version = 'system', - $requirements = false, - $systempkgs = false, - $venv_dir = $name, - $distribute = true, - $index = false, - $owner = 'root', - $group = 'root', - $mode = '0755', - $proxy = false, - $environment = [], - $path = [ '/bin', '/usr/bin', '/usr/sbin', '/usr/local/bin' ], - $cwd = undef, - $timeout = 1800, - $extra_pip_args = '', - $virtualenv = undef -) { - include ::python - - if $ensure == 'present' { - $python = $version ? { - 'system' => 'python', - 'pypy' => 'pypy', - default => "python${version}", - } - - if $virtualenv == undef { - $used_virtualenv = 'virtualenv' - } else { - $used_virtualenv = $virtualenv - } - - $proxy_flag = $proxy ? { - false => '', - default => "--proxy=${proxy}", - } - - $proxy_command = $proxy ? { - false => '', - default => "&& export http_proxy=${proxy}", - } - - # Virtualenv versions prior to 1.7 do not support the - # --system-site-packages flag, default off for prior versions - # Prior to version 1.7 the default was equal to --system-site-packages - # and the flag --no-site-packages had to be passed to do the opposite - $_virtualenv_version = getvar('virtualenv_version') ? { - /.*/ => getvar('virtualenv_version'), - default => '', - } - if (( versioncmp($_virtualenv_version,'1.7') > 0 ) and ( $systempkgs == true )) { - $system_pkgs_flag = '--system-site-packages' - } elsif (( versioncmp($_virtualenv_version,'1.7') < 0 ) and ( $systempkgs == false )) { - $system_pkgs_flag = '--no-site-packages' - } else { - $system_pkgs_flag = $systempkgs ? { - true => '--system-site-packages', - false => '--no-site-packages', - default => fail('Invalid value for systempkgs. Boolean value is expected') - } - } - - $distribute_pkg = $distribute ? { - true => 'distribute', - default => 'setuptools', - } - $pypi_index = $index ? { - false => '', - default => "-i ${index}", - } - - # Python 2.6 and older does not support setuptools/distribute > 0.8 which - # is required for pip wheel support, pip therefor requires --no-use-wheel flag - # if the # pip version is more recent than 1.4.1 but using an old python or - # setuputils/distribute version - # To check for this we test for wheel parameter using help and then using - # version, this makes sure we only use wheels if they are supported - - file { $venv_dir: - ensure => directory, - owner => $owner, - group => $group, - mode => $mode - } - - $virtualenv_cmd = "${python::exec_prefix}${used_virtualenv}" - $pip_cmd = "${python::exec_prefix}${venv_dir}/bin/pip" - - exec { "python_virtualenv_${venv_dir}": - command => "true ${proxy_command} && ${virtualenv_cmd} ${system_pkgs_flag} -p ${python} ${venv_dir} && ${pip_cmd} wheel --help > /dev/null 2>&1 && { ${pip_cmd} wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { ${pip_cmd} --log ${venv_dir}/pip.log install ${pypi_index} ${proxy_flag} \$wheel_support_flag --upgrade pip ${distribute_pkg} || ${pip_cmd} --log ${venv_dir}/pip.log install ${pypi_index} ${proxy_flag} --upgrade pip ${distribute_pkg} ;}", - user => $owner, - creates => "${venv_dir}/bin/activate", - path => $path, - cwd => '/tmp', - environment => $environment, - unless => "grep '^[\\t ]*VIRTUAL_ENV=[\\\\'\\\"]*${venv_dir}[\\\"\\\\'][\\t ]*$' ${venv_dir}/bin/activate", #Unless activate exists and VIRTUAL_ENV is correct we re-create the virtualenv - require => File[$venv_dir], - } - - if $requirements { - exec { "python_requirements_initial_install_${requirements}_${venv_dir}": - command => "${pip_cmd} wheel --help > /dev/null 2>&1 && { ${pip_cmd} wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; ${pip_cmd} --log ${venv_dir}/pip.log install ${pypi_index} ${proxy_flag} \$wheel_support_flag -r ${requirements} ${extra_pip_args}", - refreshonly => true, - timeout => $timeout, - user => $owner, - subscribe => Exec["python_virtualenv_${venv_dir}"], - environment => $environment, - cwd => $cwd - } - - python::requirements { "${requirements}_${venv_dir}": - requirements => $requirements, - virtualenv => $venv_dir, - proxy => $proxy, - owner => $owner, - group => $group, - cwd => $cwd, - require => Exec["python_virtualenv_${venv_dir}"], - extra_pip_args => $extra_pip_args, - } - } - } elsif $ensure == 'absent' { - file { $venv_dir: - ensure => absent, - force => true, - recurse => true, - purge => true, - } - } -} diff --git a/metadata.json b/metadata.json index b7342020..9cd44130 100644 --- a/metadata.json +++ b/metadata.json @@ -1,42 +1,82 @@ { - "name": "stankevich-python", - "version": "1.11.0", - "author": "stankevich", - "summary": "Python Module", + "name": "puppet-python", + "version": "9.0.0", + "author": "Vox Pupuli", + "summary": "Puppet module for Python", "license": "Apache-2.0", - "source": "git://github.com/stankevich/puppet-python.git", - "project_page": "https://github.com/stankevich/puppet-python", - "issues_url": "https://github.com/stankevich/puppet-python/issues", - "description": "Puppet module for Python", + "source": "https://github.com/voxpupuli/puppet-python.git", + "project_page": "https://github.com/voxpupuli/puppet-python", + "issues_url": "https://github.com/voxpupuli/puppet-python/issues", "tags": [ "python", "pip", - "virtualenv", "gunicorn" ], "operatingsystem_support": [ + { + "operatingsystem": "AIX", + "operatingsystemrelease": [ + "6100-09-11-1810", + "7100-05-03-1837" + ] + }, + { + "operatingsystem": "AlmaLinux", + "operatingsystemrelease": [ + "8", + "9" + ] + }, { "operatingsystem": "CentOS", "operatingsystemrelease": [ - "5", - "6", - "7" + "9" ] }, { "operatingsystem": "Debian", "operatingsystemrelease": [ - "6", - "7", - "8" + "11", + "12", + "13" ] }, + { + "operatingsystem": "FreeBSD", + "operatingsystemrelease": [ + "13", + "14" + ] + }, + { + "operatingsystem": "Gentoo" + }, { "operatingsystem": "Ubuntu", "operatingsystemrelease": [ - "10.04", - "12.04", - "14.04" + "22.04", + "24.04" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "8", + "9" + ] + }, + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "8", + "9" + ] + }, + { + "operatingsystem": "Rocky", + "operatingsystemrelease": [ + "8", + "9" ] }, { @@ -44,16 +84,25 @@ "operatingsystemrelease": [ "11.3" ] + }, + { + "operatingsystem": "Archlinux" } ], "requirements": [ { - "name": "puppet", - "version_requirement": ">=3.0.0 < 5.0.0" + "name": "openvox", + "version_requirement": ">= 8.19.0 < 9.0.0" } ], "dependencies": [ - {"name":"puppetlabs/stdlib","version_requirement":">= 4.6.0 < 6.0.0"}, - {"name":"stahnma/epel","version_requirement":">= 1.0.1 < 2.0.0"} + { + "name": "puppetlabs/stdlib", + "version_requirement": ">= 4.19.0 < 10.0.0" + }, + { + "name": "puppet/epel", + "version_requirement": ">= 3.0.0 < 7.0.0" + } ] } diff --git a/spec/acceptance/class_spec.rb b/spec/acceptance/class_spec.rb index 0b65659c..70c573b0 100644 --- a/spec/acceptance/class_spec.rb +++ b/spec/acceptance/class_spec.rb @@ -1,17 +1,45 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'python class' do - context 'default parameters' do # Using puppet_apply as a helper - it 'should work with no errors' do + it 'works with no errors' do + pp = 'include python' + + # Run it twice and test for idempotency + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + end + + context 'installing python 3' do + # Using puppet_apply as a helper + it 'works with no errors' do pp = <<-EOS - class { 'python': } + class { 'python': + ensure => 'present', + pip => 'present', + dev => 'present', + venv => 'present', + } EOS # Run it twice and test for idempotency - apply_manifest(pp, :catch_failures => true) - apply_manifest(pp, :catch_changes => true) + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + fact_notices = <<-EOS + notify{"pip_version: ${facts['pip3_version']}":} + notify{"python_version: ${facts['python3_version']}":} + EOS + it 'outputs python facts when not installed' do + apply_manifest(fact_notices, catch_failures: true) do |r| + expect(r.stdout).to match(%r{python_version: 3\.\S+}) + expect(r.stdout).to match(%r{pip_version: \S+}) + end end end end diff --git a/spec/acceptance/declared_requirements_install_spec.rb b/spec/acceptance/declared_requirements_install_spec.rb new file mode 100644 index 00000000..bc9c07d4 --- /dev/null +++ b/spec/acceptance/declared_requirements_install_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'requirements' do + it 'checks declared requirements file is installed to venv' do + pp = <<-EOS + file { '/tmp/requirements.txt': + ensure => 'present', + content => 'requests', + } + + python::pyvenv { '/tmp/pyvenv': + ensure => 'present', + } + + python::requirements { '/tmp/requirements.txt': + virtualenv => '/tmp/pyvenv' + } + EOS + + apply_manifest(pp, catch_failures: true) + + expect(shell('/tmp/pyvenv/bin/pip3 list --no-index | grep requests').stdout).to match(%r{requests +\d+.\d+.\d+}) + end +end diff --git a/spec/acceptance/facts_test_spec.rb b/spec/acceptance/facts_test_spec.rb index 6393c01f..b6393a79 100644 --- a/spec/acceptance/facts_test_spec.rb +++ b/spec/acceptance/facts_test_spec.rb @@ -1,43 +1,41 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'python class' do - context 'facts' do install_python = <<-EOS class { 'python' : - version => 'system', pip => 'present', - virtualenv => 'present', } - EOS + EOS - fact_notices = <<-EOS - notify{"pip_version: ${::pip_version}":} - notify{"system_python_version: ${::system_python_version}":} - notify{"python_version: ${::python_version}":} - notify{"virtualenv_version: ${::virtualenv_version}":} - EOS + fact_notices = <<-EOS + notify{"pip_version: ${facts['pip_version']}":} + notify{"system_python_version: ${facts['system_python_version']}":} + notify{"python_version: ${facts['python_version']}":} + EOS - it 'should output python facts when not installed' do - apply_manifest(fact_notices, :catch_failures => true) do |r| - expect(r.stdout).to match(/python_version: \S+/) - expect(r.stdout).to match(/pip_version: \S+/) - expect(r.stdout).to match(/virtualenv_version: \S+/) - expect(r.stdout).to match(/system_python_version: \S+/) + # rubocop:disable RSpec/RepeatedExample + it 'outputs python facts when not installed' do + apply_manifest(fact_notices, catch_failures: true) do |r| + expect(r.stdout).to match(%r{python_version: \S+}) + expect(r.stdout).to match(%r{pip_version: \S+}) + expect(r.stdout).to match(%r{system_python_version: \S+}) end end it 'sets up python' do - apply_manifest(install_python, :catch_failures => true) + apply_manifest(install_python, catch_failures: true) end - it 'should output python facts when installed' do - apply_manifest(fact_notices, :catch_failures => true) do |r| - expect(r.stdout).to match(/python_version: \S+/) - expect(r.stdout).to match(/pip_version: \S+/) - expect(r.stdout).to match(/virtualenv_version: \S+/) - expect(r.stdout).to match(/system_python_version: \S+/) + it 'outputs python facts when installed' do + apply_manifest(fact_notices, catch_failures: true) do |r| + expect(r.stdout).to match(%r{python_version: \S+}) + expect(r.stdout).to match(%r{pip_version: \S+}) + expect(r.stdout).to match(%r{system_python_version: \S+}) end end + # rubocop:enable RSpec/RepeatedExample end end diff --git a/spec/acceptance/nodesets/centos-59-x64.yml b/spec/acceptance/nodesets/centos-59-x64.yml deleted file mode 100644 index 2ad90b86..00000000 --- a/spec/acceptance/nodesets/centos-59-x64.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - centos-59-x64: - roles: - - master - platform: el-5-x86_64 - box : centos-59-x64-vbox4210-nocm - box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box - hypervisor : vagrant -CONFIG: - type: git diff --git a/spec/acceptance/nodesets/centos-64-x64-pe.yml b/spec/acceptance/nodesets/centos-64-x64-pe.yml deleted file mode 100644 index 7d9242f1..00000000 --- a/spec/acceptance/nodesets/centos-64-x64-pe.yml +++ /dev/null @@ -1,12 +0,0 @@ -HOSTS: - centos-64-x64: - roles: - - master - - database - - dashboard - platform: el-6-x86_64 - box : centos-64-x64-vbox4210-nocm - box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box - hypervisor : vagrant -CONFIG: - type: pe diff --git a/spec/acceptance/nodesets/centos-65-x64.yml b/spec/acceptance/nodesets/centos-65-x64.yml deleted file mode 100644 index 4e2cb809..00000000 --- a/spec/acceptance/nodesets/centos-65-x64.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - centos-65-x64: - roles: - - master - platform: el-6-x86_64 - box : centos-65-x64-vbox436-nocm - box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box - hypervisor : vagrant -CONFIG: - type: foss diff --git a/spec/acceptance/nodesets/debian-70rc1-x64.yml b/spec/acceptance/nodesets/debian-70rc1-x64.yml deleted file mode 100644 index 4b55677f..00000000 --- a/spec/acceptance/nodesets/debian-70rc1-x64.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - debian-70rc1-x64: - roles: - - master - platform: debian-70rc1-x64 - box : debian-70rc1-x64-vbox4210-nocm - box_url : http://puppet-vagrant-boxes.puppetlabs.com/debian-70rc1-x64-vbox4210-nocm.box - hypervisor : vagrant -CONFIG: - type: foss diff --git a/spec/acceptance/nodesets/debian-73-x64.yml b/spec/acceptance/nodesets/debian-73-x64.yml deleted file mode 100644 index d7143455..00000000 --- a/spec/acceptance/nodesets/debian-73-x64.yml +++ /dev/null @@ -1,11 +0,0 @@ -HOSTS: - debian-73-x64: - roles: - - master - platform: debian-7-amd64 - box : debian-73-x64-virtualbox-nocm - box_url : http://puppet-vagrant-boxes.puppetlabs.com/debian-73-x64-virtualbox-nocm.box - hypervisor : vagrant -CONFIG: - log_level: debug - type: git \ No newline at end of file diff --git a/spec/acceptance/nodesets/default.yml b/spec/acceptance/nodesets/default.yml deleted file mode 100644 index 45af9893..00000000 --- a/spec/acceptance/nodesets/default.yml +++ /dev/null @@ -1,11 +0,0 @@ -HOSTS: - ubuntu-server-12042-x64: - roles: - - master - platform: ubuntu-server-12.04-amd64 - box : ubuntu-server-12042-x64-vbox4210-nocm - box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box - hypervisor : vagrant -CONFIG: - type: foss - vagrant_ssh_port_random: true diff --git a/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml b/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml deleted file mode 100644 index 5ca1514e..00000000 --- a/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - ubuntu-server-10044-x64: - roles: - - master - platform: ubuntu-10.04-amd64 - box : ubuntu-server-10044-x64-vbox4210-nocm - box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box - hypervisor : vagrant -CONFIG: - type: foss diff --git a/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml b/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml deleted file mode 100644 index d065b304..00000000 --- a/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - ubuntu-server-12042-x64: - roles: - - master - platform: ubuntu-12.04-amd64 - box : ubuntu-server-12042-x64-vbox4210-nocm - box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box - hypervisor : vagrant -CONFIG: - type: foss diff --git a/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml deleted file mode 100644 index cba1cd04..00000000 --- a/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml +++ /dev/null @@ -1,11 +0,0 @@ -HOSTS: - ubuntu-server-1404-x64: - roles: - - master - platform: ubuntu-14.04-amd64 - box : puppetlabs/ubuntu-14.04-64-nocm - box_url : https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm - hypervisor : vagrant -CONFIG: - log_level : debug - type: git diff --git a/spec/acceptance/pip_spec.rb b/spec/acceptance/pip_spec.rb new file mode 100644 index 00000000..83a639fa --- /dev/null +++ b/spec/acceptance/pip_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'python::pip defined resource' do + context 'install package with custom name' do + it 'works with no errors' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + } + + python::pyvenv { '/opt/test-venv': + ensure => 'present', + systempkgs => false, + mode => '0755', + } + + python::pip { 'agent package': + virtualenv => '/opt/test-venv', + pkgname => 'agent', + ensure => '0.1.2', + } + PUPPET + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + end + + # rubocop:disable RSpec/RepeatedExampleGroupDescription + # rubocop:disable RSpec/RepeatedExampleGroupBody + describe command('/opt/test-venv/bin/pip list') do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match %r{agent.* 0\.1\.2} } + end + + context 'uninstall package with custom name' do + it 'works with no errors' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + } + + python::pyvenv { '/opt/test-venv': + ensure => 'present', + systempkgs => false, + mode => '0755', + } + + python::pip { 'agent package install': + ensure => '0.1.2', + pkgname => 'agent', + virtualenv => '/opt/test-venv', + } + + python::pip { 'agent package uninstall custom pkgname': + ensure => 'absent', + pkgname => 'agent', + virtualenv => '/opt/test-venv', + require => Python::Pip['agent package install'], + } + + PUPPET + + apply_manifest(pp, catch_failures: true) + end + end + + describe command('/opt/test-venv/bin/pip list') do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.not_to match %r{agent.* 0\.1\.2} } + end + + context 'fails to install package with wrong version' do + it 'throws an error' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + } + + python::pyvenv { '/opt/test-venv': + ensure => 'present', + systempkgs => false, + mode => '0755', + } + + python::pip { 'agent package': + virtualenv => '/opt/test-venv', + pkgname => 'agent', + ensure => '0.1.33+2020-this_is_something-fun', + } + PUPPET + + result = apply_manifest(pp, expect_failures: true) + expect(result.stderr).to contain(%r{returned 1 instead of one of}) + end + end + + describe command('/opt/test-venv/bin/pip show agent') do + its(:exit_status) { is_expected.to eq 1 } + end + + context 'install package via extra_index' do + it 'works with no errors' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + } + + python::pyvenv { '/opt/test-venv': + ensure => 'present', + systempkgs => false, + mode => '0755', + } + + python::pip { 'agent package via extra_index': + virtualenv => '/opt/test-venv', + pkgname => 'agent', + index => 'invalid', + extra_index => 'https://pypi.org/simple', + ensure => '0.1.2', + } + PUPPET + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + end + + describe command('/opt/test-venv/bin/pip list') do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match %r{agent.* 0\.1\.2} } + end + # rubocop:enable RSpec/RepeatedExampleGroupBody + # rubocop:enable RSpec/RepeatedExampleGroupDescription +end diff --git a/spec/acceptance/pyvenv_spec.rb b/spec/acceptance/pyvenv_spec.rb new file mode 100644 index 00000000..263e4a40 --- /dev/null +++ b/spec/acceptance/pyvenv_spec.rb @@ -0,0 +1,238 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'python::pyvenv defined resource with python 3' do + context 'minimal parameters' do + # Using puppet_apply as a helper + it 'works with no errors' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + venv => 'present', + } + user { 'agent': + ensure => 'present', + managehome => true, + home => '/opt/agent', + } + group { 'agent': + ensure => 'present', + system => true, + } + python::pyvenv { '/opt/agent/venv': + ensure => 'present', + systempkgs => true, + owner => 'agent', + group => 'agent', + mode => '0755', + } + PUPPET + + # Run it twice and test for idempotency + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + end + + context 'with python::pip' do + it 'works with no errors' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + venv => 'present', + } + user { 'agent': + ensure => 'present', + managehome => true, + home => '/opt/agent', + } + group { 'agent': + ensure => 'present', + system => true, + } + python::pyvenv { '/opt/agent/venv': + ensure => 'present', + systempkgs => true, + owner => 'agent', + group => 'agent', + mode => '0755', + } + python::pip { 'agent' : + ensure => 'latest', + pkgname => 'agent', + pip_provider => 'pip', + virtualenv => '/opt/agent/venv', + owner => 'agent', + group => 'agent', + } + PUPPET + + # Run it twice and test for idempotency + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + end + + context 'with minimal python::pip' do + it 'works with no errors' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + venv => 'present', + } + user { 'agent': + ensure => 'present', + managehome => true, + home => '/opt/agent', + } + group { 'agent': + ensure => 'present', + system => true, + } + python::pyvenv { '/opt/agent/venv': + ensure => 'present', + systempkgs => true, + owner => 'agent', + group => 'agent', + mode => '0755', + } + python::pip { 'agent' : + virtualenv => '/opt/agent/venv', + owner => 'agent', + group => 'agent', + } + PUPPET + + # Run it twice and test for idempotency + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + end + + context 'with minimal python::pip and without systempkgs' do + it 'works with no errors' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + venv => 'present', + } + user { 'agent': + ensure => 'present', + managehome => true, + home => '/opt/agent', + } + group { 'agent': + ensure => 'present', + system => true, + } + python::pyvenv { '/opt/agent/venv': + ensure => 'present', + systempkgs => false, + owner => 'agent', + group => 'agent', + mode => '0755', + } + python::pip { 'agent' : + virtualenv => '/opt/agent/venv', + owner => 'agent', + group => 'agent', + } + PUPPET + + # Run it twice and test for idempotency + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + end + + context 'with versioned minimal python::pip and without systempkgs' do + it 'works with no errors' do + pp = <<-PUPPET + class { 'python': + dev => 'present', + venv => 'present', + } + user { 'agent': + ensure => 'present', + managehome => true, + home => '/opt/agent', + } + group { 'agent': + ensure => 'present', + system => true, + } + python::pyvenv { '/opt/agent/venv': + ensure => 'present', + systempkgs => false, + owner => 'agent', + group => 'agent', + mode => '0755', + } + python::pip { 'agent' : + ensure => '0.1.2', + virtualenv => '/opt/agent/venv', + owner => 'agent', + group => 'agent', + } + PUPPET + + # Run it twice and test for idempotency + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe command('/opt/agent/venv/bin/pip list') do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match %r{agent.* 0\.1\.2} } + end + end + + context 'with versioned minimal python::pip and without systempkgs using custom python path' do + it 'works with no errors' do + pp = <<-PUPPET + + class { 'python': + dev => 'present', + venv => 'present', + } + file { '/usr/bin/mycustompython': + ensure => link, + target => '/usr/bin/python', + } + user { 'agent': + ensure => 'present', + managehome => true, + home => '/opt/agent', + } + group { 'agent': + ensure => 'present', + system => true, + } + python::pyvenv { '/opt/agent/venv': + ensure => 'present', + systempkgs => false, + owner => 'agent', + group => 'agent', + mode => '0755', + python_path => '/usr/bin/mycustompython', + } + python::pip { 'agent' : + ensure => '0.1.2', + virtualenv => '/opt/agent/venv', + owner => 'agent', + group => 'agent', + } + PUPPET + + # Run it twice and test for idempotency + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe command('/opt/agent/venv/bin/pip list') do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match %r{agent.* 0\.1\.2} } + end + end +end diff --git a/spec/acceptance/virtualenv_spec.rb b/spec/acceptance/virtualenv_spec.rb deleted file mode 100644 index 2dfbdc3d..00000000 --- a/spec/acceptance/virtualenv_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper_acceptance' - -describe 'python class' do - - context 'default parameters' do - # Using puppet_apply as a helper - it 'should work with no errors' do - pp = <<-EOS - class { 'python' : - version => 'system', - pip => 'present', - virtualenv => 'present', - } - -> - python::virtualenv { 'venv' : - ensure => 'present', - systempkgs => false, - venv_dir => '/opt/venv', - owner => 'root', - group => 'root', - } - -> - python::pip { 'rpyc' : - ensure => '3.2.3', - virtualenv => '/opt/venv', - } - EOS - - # Run it twice and test for idempotency - apply_manifest(pp, :catch_failures => true) - apply_manifest(pp, :catch_changes => true) - end - end -end diff --git a/spec/classes/python_spec.rb b/spec/classes/python_spec.rb index bcdbb003..f1c2d9a1 100644 --- a/spec/classes/python_spec.rb +++ b/spec/classes/python_spec.rb @@ -1,274 +1,459 @@ -require 'spec_helper' +# frozen_string_literal: true -describe 'python', :type => :class do - context "on Debian OS" do - let :facts do - { - :id => 'root', - :kernel => 'Linux', - :lsbdistcodename => 'squeeze', - :osfamily => 'Debian', - :operatingsystem => 'Debian', - :operatingsystemrelease => '6', - :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', - :concat_basedir => '/dne', - } - end +require 'spec_helper' +describe 'python' do + on_supported_os.each do |os, facts| + next if os == 'gentoo-3-x86_64' - it { is_expected.to contain_class("python::install") } - # Base debian packages. - it { is_expected.to contain_package("python") } - it { is_expected.to contain_package("python-dev") } - it { is_expected.to contain_package("pip") } - # Basic python packages (from pip) - it { is_expected.to contain_package("virtualenv")} - - describe "with python::dev" do - context "true" do - let (:params) {{ :dev => 'present' }} - it { is_expected.to contain_package("python-dev").with_ensure('present') } - end - context "empty/default" do - it { is_expected.to contain_package("python-dev").with_ensure('absent') } + context "on #{os}" do + let :facts do + facts end - end - describe "with manage_gunicorn" do - context "true" do - let (:params) {{ :manage_gunicorn => true }} - it { is_expected.to contain_package("gunicorn") } - end - context "empty args" do - #let (:params) {{ :manage_gunicorn => '' }} - it { is_expected.to contain_package("gunicorn") } + context 'with defaults' do + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_class('python::install') } + it { is_expected.to contain_class('python::params') } + it { is_expected.to contain_class('python::config') } + it { is_expected.to contain_package('python') } + + if facts[:os]['family'] == 'Archlinux' + it { is_expected.not_to contain_package('pip') } + else + it { is_expected.to contain_package('pip') } + end + + if %w[Archlinux RedHat].include?(facts[:os]['family']) + it { is_expected.not_to contain_package('python-venv') } + else + it { is_expected.to contain_package('python-venv') } + end end - context "false" do - let (:params) {{ :manage_gunicorn => false }} - it {is_expected.not_to contain_package("gunicorn")} + + context 'without managing things' do + let :params do + { + manage_python_package: false, + manage_dev_package: false, + manage_pip_package: false, + manage_venv_package: false, + } + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.not_to contain_package('python') } + it { is_expected.not_to contain_package('python-dev') } + it { is_expected.not_to contain_package('pip') } + it { is_expected.not_to contain_package('python-venv') } end - end - describe "with python::provider" do - context "pip" do - let (:params) {{ :provider => 'pip' }} - it { is_expected.to contain_package("virtualenv").with( - 'provider' => 'pip' - )} - it { is_expected.to contain_package("pip").with( - 'provider' => 'pip' - )} + context 'with packages present' do + let :params do + { + manage_pip_package: true, + manage_venv_package: true, + pip: 'present', + venv: 'present', + } + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_package('pip').with(ensure: 'present') } + + it { is_expected.to contain_package('python-venv').with(ensure: 'present') } unless facts[:os]['family'] == 'RedHat' end - # python::provider - context "default" do - let (:params) {{ :provider => '' }} - it { is_expected.to contain_package("virtualenv")} - it { is_expected.to contain_package("pip")} + case facts[:os]['family'] + when 'Debian' + + # tests were written for Debian 6 + context 'on Debian OS' do + it { is_expected.to contain_class('python::install') } + # Base debian packages. + it { is_expected.to contain_package('python') } + it { is_expected.to contain_package('python-dev') } + it { is_expected.to contain_package('pip') } + + describe 'with python::version' do + context 'python3.7' do + let(:params) { { version: 'python3.7' } } - describe "with python::virtualenv" do - context "true" do - let (:params) {{ :provider => '', :virtualenv => true }} - it { is_expected.to contain_package("virtualenv").with_ensure('present') } + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_package('pip').with_name('python3.7-pip') } + it { is_expected.to contain_package('python').with_name('python3.7') } + it { is_expected.to contain_package('python-dev').with_name('python3.7-dev') } + end end - end - describe "without python::virtualenv" do - context "default/empty" do - let (:params) {{ :provider => '' }} - it { is_expected.to contain_package("virtualenv").with_ensure('absent') } + # rubocop:disable RSpec/RepeatedExampleGroupDescription + describe 'with python::dev' do + context 'true' do + let(:params) { { dev: 'present' } } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_package('python-dev').with_ensure('present') } + end + + context 'empty/default' do + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_package('python-dev').with_ensure('absent') } + end end - end - end - end - describe "with python::dev" do - context "true" do - let (:params) {{ :dev => 'present' }} - it { is_expected.to contain_package("python-dev").with_ensure('present') } - end - context "default/empty" do - it { is_expected.to contain_package("python-dev").with_ensure('absent') } - end - end - end + describe 'without python::dev' do + context 'empty/default' do + it { is_expected.to contain_package('python-dev').with_ensure('absent') } + end + end - context "on a Redhat 5 OS" do - let :facts do - { - :id => 'root', - :kernel => 'Linux', - :osfamily => 'RedHat', - :operatingsystem => 'RedHat', - :operatingsystemrelease => '5', - :concat_basedir => '/dne', - :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', - } - end - it { is_expected.to contain_class("python::install") } - # Base debian packages. - it { is_expected.to contain_package("python") } - it { is_expected.to contain_package("python-dev").with_name("python-devel") } - it { is_expected.to contain_package("pip") } - # Basic python packages (from pip) - it { is_expected.to contain_package("virtualenv")} - - describe "with python::dev" do - context "true" do - let (:params) {{ :dev => 'present' }} - it { is_expected.to contain_package("python-dev").with_ensure('present') } - end - context "empty/default" do - it { is_expected.to contain_package("python-dev").with_ensure('absent') } - end - end + describe 'with python::python_pyvenvs' do + context 'with two pyenvs' do + let(:params) do + { + python_pyvenvs: { + '/opt/env1' => { + version: '3.8', + }, + '/opt/env2' => { + version: '3.8', + }, + }, + } + end - describe "with manage_gunicorn" do - context "true" do - let (:params) {{ :manage_gunicorn => true }} - it { is_expected.to contain_package("gunicorn") } - end - context "empty args" do - #let (:params) {{ :manage_gunicorn => '' }} - it { is_expected.to contain_package("gunicorn") } - end - context "false" do - let (:params) {{ :manage_gunicorn => false }} - it {is_expected.not_to contain_package("gunicorn")} - end - end + it { is_expected.to compile } - describe "with python::provider" do - context "pip" do - let (:params) {{ :provider => 'pip' }} + it { is_expected.to contain_python__pyvenv('/opt/env1').with_ensure('present') } + it { is_expected.to contain_python__pyvenv('/opt/env2').with_ensure('present') } + it { is_expected.to contain_exec('python_virtualenv_/opt/env1') } + it { is_expected.to contain_exec('python_virtualenv_/opt/env2') } + it { is_expected.to contain_file('/opt/env1') } + it { is_expected.to contain_file('/opt/env2') } + end + end - it { is_expected.to contain_package("virtualenv").with( - 'provider' => 'pip' - )} - it { is_expected.to contain_package("pip").with( - 'provider' => 'pip' - )} - end + describe 'with python::python_pyvenvs and pip version defined' do + context 'with two pyenvs' do + let(:params) do + { + python_pyvenvs: { + '/opt/env1' => { + version: '3.8', + pip_version: 'latest', + }, + '/opt/env2' => { + version: '3.8', + pip_version: '<= 20.3.4', + }, + }, + } + end + + it { is_expected.to compile } + + it { is_expected.to contain_python__pyvenv('/opt/env1').with_ensure('present') } + it { is_expected.to contain_python__pyvenv('/opt/env2').with_ensure('present') } - # python::provider - context "default" do - let (:params) {{ :provider => '' }} - it { is_expected.to contain_package("virtualenv")} - it { is_expected.to contain_package("pip")} + it { + expect(subject).to contain_exec('python_virtualenv_/opt/env1') + .with( + command: 'python3.8 -m venv --clear /opt/env1 && /opt/env1/bin/pip install --upgrade pip && /opt/env1/bin/pip install --upgrade setuptools', + user: 'root', + creates: '/opt/env1/bin/activate', + path: [ + '/bin', + '/usr/bin', + '/usr/sbin', + '/usr/local/bin', + ], + cwd: '/tmp', + environment: [], + timeout: 600, + unless: %r{^grep '\^\[\\t \]\*VIRTUAL_ENV=\[\\\\'\\"\]\*/opt/env1\[\\"\\\\'\]\[\\t \]\*\$' /opt/env1/bin/activate$}, + ) + .that_requires('File[/opt/env1]') + } - describe "with python::virtualenv" do - context "true" do - let (:params) {{ :provider => '', :virtualenv => 'present' }} - it { is_expected.to contain_package("virtualenv").with_ensure('present') } + it { + expect(subject).to contain_exec('python_virtualenv_/opt/env2') + .with( + command: 'python3.8 -m venv --clear /opt/env2 && /opt/env2/bin/pip install --upgrade \'pip <= 20.3.4\' && /opt/env2/bin/pip install --upgrade setuptools', + user: 'root', + creates: '/opt/env2/bin/activate', + path: [ + '/bin', + '/usr/bin', + '/usr/sbin', + '/usr/local/bin', + ], + cwd: '/tmp', + environment: [], + timeout: 600, + unless: %r{^grep '\^\[\\t \]\*VIRTUAL_ENV=\[\\\\'\\"\]\*/opt/env2\[\\"\\\\'\]\[\\t \]\*\$' /opt/env2/bin/activate$}, + ) + .that_requires('File[/opt/env2]') + } + + it { is_expected.to contain_file('/opt/env1') } + it { is_expected.to contain_file('/opt/env2') } + end + end + + describe 'with manage_gunicorn' do + context 'true' do + let(:params) { { manage_gunicorn: true } } + + it { is_expected.to contain_package('gunicorn') } + end + + context 'empty args' do + # let(:params) {{ :manage_gunicorn => '' }} + it { is_expected.to contain_package('gunicorn') } + end + + context 'false' do + let(:params) { { manage_gunicorn: false } } + + it { is_expected.not_to contain_package('gunicorn') } + end + end + + describe 'with python::provider' do + context 'pip' do + let(:params) { { pip: 'present', provider: 'pip' } } + + it { is_expected.to contain_package('pip').with('provider' => 'pip') } + end + + # python::provider + context 'default' do + let(:params) { { provider: '' } } + + it { is_expected.to contain_package('pip') } + end + end + + describe 'with python::dev' do + context 'true' do + let(:params) { { dev: 'present' } } + + it { is_expected.to contain_package('python-dev').with_ensure('present') } + end + + context 'default/empty' do + it { is_expected.to contain_package('python-dev').with_ensure('absent') } + end end - end - describe "with python::virtualenv" do - context "default/empty" do - let (:params) {{ :provider => '' }} - it { is_expected.to contain_package("virtualenv").with_ensure('absent') } + describe 'EPEL does not exist for Debian' do + context 'default/empty' do + it { is_expected.not_to contain_class('epel') } + end end end - end - end + when 'RedHat' + case facts[:os]['name'] + when 'Fedora' - describe "with python::dev" do - context "true" do - let (:params) {{ :dev => 'present' }} - it { is_expected.to contain_package("python-dev").with_ensure('present') } - end - context "default/empty" do - it { is_expected.to contain_package("python-dev").with_ensure('absent') } - end - end - end + # written for Fedora 22 + context 'on a Fedora OS' do + describe 'EPEL does not exist for Fedora' do + context 'default/empty' do + it { is_expected.not_to contain_class('epel') } + end + end + end + when 'RedHat', 'CentOS' + case facts[:os]['release']['major'] + when '8' + context 'on a Redhat 8 OS' do + it { is_expected.to contain_class('python::install') } + it { is_expected.to contain_package('pip').with_name('python3-pip') } - context "on a SLES 11 SP3" do - let :facts do - { - :id => 'root', - :kernel => 'Linux', - :osfamily => 'Suse', - :operatingsystem => 'SLES', - :operatingsystemrelease => '11.3', - :concat_basedir => '/dne', - :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', - } - end - it { is_expected.to contain_class("python::install") } - # Base Suse packages. - it { is_expected.to contain_package("python") } - it { is_expected.to contain_package("python-dev").with_name("python-devel") } - it { is_expected.to contain_package("pip") } - # Basic python packages (from pip) - it { is_expected.to contain_package("virtualenv")} - - describe "with python::dev" do - context "true" do - let (:params) {{ :dev => 'present' }} - it { is_expected.to contain_package("python-dev").with_ensure('present') } - end - context "empty/default" do - it { is_expected.to contain_package("python-dev").with_ensure('absent') } - end - end + describe 'with python::version' do + context 'python36' do + let(:params) { { version: 'python36' } } - describe "with manage_gunicorn" do - context "true" do - let (:params) {{ :manage_gunicorn => true }} - it { is_expected.to contain_package("gunicorn") } - end - context "empty args" do - #let (:params) {{ :manage_gunicorn => '' }} - it { is_expected.to contain_package("gunicorn") } - end - context "false" do - let (:params) {{ :manage_gunicorn => false }} - it {is_expected.not_to contain_package("gunicorn")} - end - end + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_package('pip').with_name('python36-pip') } + it { is_expected.to contain_package('python').with_name('python36') } + it { is_expected.to contain_package('python-dev').with_name('python36-devel') } + end + end - describe "with python::provider" do - context "pip" do - let (:params) {{ :provider => 'pip' }} + describe 'with manage_gunicorn' do + context 'true' do + let(:params) { { manage_gunicorn: true } } - it { is_expected.to contain_package("virtualenv").with( - 'provider' => 'pip' - )} - it { is_expected.to contain_package("pip").with( - 'provider' => 'pip' - )} - end + it { is_expected.to contain_package('gunicorn').with_name('python3-gunicorn') } + end + + context 'empty args' do + # let(:params) {{ :manage_gunicorn => '' }} + it { is_expected.to contain_package('gunicorn').with_name('python3-gunicorn') } + end + + context 'false' do + let(:params) { { manage_gunicorn: false } } + + it { is_expected.not_to contain_package('gunicorn').with_name('python3-gunicorn') } + end + end + + describe 'with python::provider' do + context 'scl' do + describe 'with version' do + context '3.6 SCL meta package' do + let(:params) { { version: 'rh-python36' } } + + it { is_expected.to compile.with_all_deps } + end + + context '3.6 SCL python package' do + let(:params) { { version: 'rh-python36-python' } } - # python::provider - context "default" do - let (:params) {{ :provider => '' }} - it { is_expected.to contain_package("virtualenv")} - it { is_expected.to contain_package("pip")} + it { is_expected.to compile.with_all_deps } + end + end - describe "with python::virtualenv" do - context "true" do - let (:params) {{ :provider => '', :virtualenv => 'present' }} - it { is_expected.to contain_package("virtualenv").with_ensure('present') } + describe 'with manage_scl' do + context 'true' do + let(:params) { { provider: 'scl', manage_scl: true } } + + it { is_expected.to contain_package('centos-release-scl') } + it { is_expected.to contain_package('scl-utils') } + end + + context 'false' do + let(:params) { { provider: 'scl', manage_scl: false } } + + it { is_expected.not_to contain_package('centos-release-scl') } + it { is_expected.not_to contain_package('scl-utils') } + end + end + end + end + end end end + when 'Suse' + # written for SLES 11 SP3 + + context 'on a SLES 11 SP3' do + it { is_expected.to contain_class('python::install') } + # Base Suse packages. + it { is_expected.to contain_package('python') } + it { is_expected.to contain_package('python-dev').with_name('python3-devel') } + it { is_expected.to contain_package('python-dev').with_alias('python3-devel') } + it { is_expected.to contain_package('pip') } + + describe 'with python::dev' do + context 'true' do + let(:params) { { dev: 'present' } } + + it { is_expected.to contain_package('python-dev').with_ensure('present') } + end + + context 'empty/default' do + it { is_expected.to contain_package('python-dev').with_ensure('absent') } + end + end + + describe 'with manage_gunicorn' do + context 'true' do + let(:params) { { manage_gunicorn: true } } + + it { is_expected.to contain_package('gunicorn') } + end + + context 'empty args' do + # let(:params) {{ :manage_gunicorn => '' }} + it { is_expected.to contain_package('gunicorn') } + end - describe "with python::virtualenv" do - context "default/empty" do - let (:params) {{ :provider => '' }} - it { is_expected.to contain_package("virtualenv").with_ensure('absent') } + context 'false' do + let(:params) { { manage_gunicorn: false } } + + it { is_expected.not_to contain_package('gunicorn') } + end + end + + describe 'with python::provider' do + context 'pip' do + let(:params) { { provider: 'pip' } } + + it { + expect(subject).to contain_package('pip').with( + 'provider' => 'pip', + ) + } + end + + # python::provider + context 'default' do + let(:params) { { provider: '' } } + + it { is_expected.to contain_package('pip') } + end + end + + describe 'with python::dev' do + context 'true' do + let(:params) { { dev: 'present' } } + + it { is_expected.to contain_package('python-dev').with_ensure('present') } + end + + context 'default/empty' do + it { is_expected.to contain_package('python-dev').with_ensure('absent') } + end + end + + describe 'EPEL does not exist on Suse' do + context 'default/empty' do + it { is_expected.not_to contain_class('epel') } + end end end - end - end + when 'Gentoo' - describe "with python::dev" do - context "true" do - let (:params) {{ :dev => 'present' }} - it { is_expected.to contain_package("python-dev").with_ensure('present') } - end - context "default/empty" do - it { is_expected.to contain_package("python-dev").with_ensure('absent') } + context 'on a Gentoo OS' do + it { is_expected.to contain_class('python::install') } + # Base debian packages. + it { is_expected.to contain_package('python') } + it { is_expected.to contain_package('pip').with('name' => 'dev-python/pip') } + # Python::Dev + it { is_expected.not_to contain_package('python-dev') } + + describe 'with manage_gunicorn' do + context 'true' do + let(:params) { { manage_gunicorn: true } } + + it { is_expected.to contain_package('gunicorn') } + end + + context 'empty args' do + # let(:params) {{ :manage_gunicorn => '' }} + it { is_expected.to contain_package('gunicorn') } + end + + context 'false' do + let(:params) { { manage_gunicorn: false } } + + it { is_expected.not_to contain_package('gunicorn') } + end + end + + describe 'with python::provider' do + context 'pip' do + let(:params) { { pip: 'present', provider: 'pip' } } + + it { is_expected.to contain_package('pip').with('provider' => 'pip') } + end + end + end end end end end +# rubocop:enable RSpec/RepeatedExampleGroupDescription diff --git a/spec/default_module_facts.yml b/spec/default_module_facts.yml new file mode 100644 index 00000000..3f8d54e2 --- /dev/null +++ b/spec/default_module_facts.yml @@ -0,0 +1,3 @@ +--- +python_version: "2.7" +python3_version: ~ diff --git a/spec/defines/dotfile_spec.rb b/spec/defines/dotfile_spec.rb new file mode 100644 index 00000000..a44426f7 --- /dev/null +++ b/spec/defines/dotfile_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'python::dotfile', type: :define do + on_supported_os.each do |os, facts| + context("on #{os}") do + let :facts do + facts + end + + let(:root_group) do + if facts[:os]['family'] == 'FreeBSD' + 'wheel' + else + 'root' + end + end + + let(:pre_condition) { 'include python' } + + describe 'dotfile as' do + context 'fails with empty string filename' do + let(:title) { '' } + + it { is_expected.to raise_error(%r{Evaluation Error: Empty string title at 0. Title strings must have a length greater than zero.}) } + end + + context 'fails with incorrect mode' do + let(:title) { '/etc/pip.conf' } + let(:params) { { mode: 'not-a-mode' } } + + it { is_expected.to raise_error(%r{Evaluation Error: Error while evaluating a Resource}) } + end + + context 'succeeds with filename in existing path' do + let(:title) { '/etc/pip.conf' } + + it { is_expected.to contain_file('/etc/pip.conf').with_mode('0644') } + end + + context 'succeeds with filename in a non-existing path' do + let(:title) { '/home/someuser/.pip/pip.conf' } + + it { is_expected.to contain_exec('create /home/someuser/.pip/pip.conf\'s parent dir').with_command("install -o root -g #{root_group} -d /home/someuser/.pip") } + it { is_expected.to contain_file('/home/someuser/.pip/pip.conf').with_mode('0644') } + end + + context 'succeeds when set owner' do + let(:title) { '/home/someuser/.pip/pip.conf' } + let(:params) { { owner: 'someuser' } } + + it { is_expected.to contain_exec('create /home/someuser/.pip/pip.conf\'s parent dir').with_command("install -o someuser -g #{root_group} -d /home/someuser/.pip") } + it { is_expected.to contain_file('/home/someuser/.pip/pip.conf').with_owner('someuser') } + end + + context 'succeeds when set group set' do + let(:title) { '/home/someuser/.pip/pip.conf' } + let(:params) { { group: 'somegroup' } } + + it { is_expected.to contain_exec('create /home/someuser/.pip/pip.conf\'s parent dir').with_command('install -o root -g somegroup -d /home/someuser/.pip') } + it { is_expected.to contain_file('/home/someuser/.pip/pip.conf').with_group('somegroup') } + end + end + end + end +end diff --git a/spec/defines/gunicorn_spec.rb b/spec/defines/gunicorn_spec.rb index 5e51bb21..de36c580 100644 --- a/spec/defines/gunicorn_spec.rb +++ b/spec/defines/gunicorn_spec.rb @@ -1,30 +1,46 @@ +# frozen_string_literal: true + require 'spec_helper' -describe 'python::gunicorn', :type => :define do +describe 'python::gunicorn', type: :define do let(:title) { 'test-app' } - context 'on Debian OS' do - let :facts do - { - :id => 'root', - :kernel => 'Linux', - :lsbdistcodename => 'squeeze', - :osfamily => 'Debian', - :operatingsystem => 'Debian', - :operatingsystemrelease => '6', - :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', - :concat_basedir => '/dne', - } - end - describe 'test-app with default parameter values' do - context 'configures test app with default parameter values' do - let(:params) { { :dir => '/srv/testapp' } } - it { is_expected.to contain_file('/etc/gunicorn.d/test-app').with_mode('0644').with_content(/--log-level=error/) } - end + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts } + + describe 'test-app with default parameter values' do + context 'configures test app with default parameter values' do + let(:params) { { dir: '/srv/testapp' } } + let(:expected_workers) do + # manifests/gunicorn.pp + processor_count = facts.dig(:processors, 'count') + + # templates/gunicorn.erb + (processor_count.to_i * 2) + 1 + end + + it { is_expected.to contain_file('/etc/gunicorn.d/test-app').with_mode('0644').with_content(%r{--log-level=error}) } + it { is_expected.to contain_file('/etc/gunicorn.d/test-app').with_mode('0644').with_content(%r{--workers=#{expected_workers}}) } + end + + context 'test-app with custom log level' do + let(:params) { { dir: '/srv/testapp', log_level: 'info' } } + + it { is_expected.to contain_file('/etc/gunicorn.d/test-app').with_mode('0644').with_content(%r{--log-level=info}) } + end + + context 'test-app with custom gunicorn preload arguments' do + let(:params) { { dir: '/srv/testapp', args: ['--preload'] } } + + it { is_expected.to contain_file('/etc/gunicorn.d/test-app').with_mode('0644').with_content(%r{--preload}) } + end + + context 'test-app with custom workers count' do + let(:params) { { dir: '/srv/testapp', workers: 42 } } - context 'test-app with custom log level' do - let(:params) { { :dir => '/srv/testapp', :log_level => 'info' } } - it { is_expected.to contain_file('/etc/gunicorn.d/test-app').with_mode('0644').with_content(/--log-level=info/) } + it { is_expected.to contain_file('/etc/gunicorn.d/test-app').with_mode('0644').with_content(%r{--workers=#{params[:workers]}}) } + end end end end diff --git a/spec/defines/pip_spec.rb b/spec/defines/pip_spec.rb index d603edc8..a00a7792 100644 --- a/spec/defines/pip_spec.rb +++ b/spec/defines/pip_spec.rb @@ -1,71 +1,249 @@ +# frozen_string_literal: true + require 'spec_helper' +# rubocop:disable RSpec/MultipleDescribes +# rubocop:disable RSpec/RepeatedExampleGroupDescription +describe 'python::pip', type: :define do + let(:title) { 'rpyc' } -describe 'python::pip', :type => :define do - let (:title) { 'rpyc' } - context "on Debian OS" do + context 'on Debian OS' do let :facts do { - :id => 'root', - :kernel => 'Linux', - :lsbdistcodename => 'squeeze', - :osfamily => 'Debian', - :operatingsystem => 'Debian', - :operatingsystemrelease => '6', - :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', - :concat_basedir => '/dne', + id: 'root', + kernel: 'Linux', + lsbdistcodename: 'squeeze', + os: { + family: 'Debian', + release: { major: '6' }, + }, + osfamily: 'Debian', + operatingsystem: 'Debian', + operatingsystemrelease: '6', + path: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + concat_basedir: '/dne', + pip_version: '18.1', } end - describe "virtualenv as" do - context "fails with non qualified path" do - let (:params) {{ :virtualenv => "venv" }} - it { is_expected.to raise_error(/"venv" is not an absolute path./) } + describe 'virtualenv as' do + context 'fails with non qualified path' do + let(:params) { { virtualenv: 'venv' } } + + it { is_expected.to raise_error(%r{expects a match for Variant\[Enum\['system'\].*Stdlib::Windowspath = Pattern\[/.*/\], Stdlib::Unixpath = Pattern\[/.*/\]\]}) } end - context "suceeds with qualified path" do - let (:params) {{ :virtualenv => "/opt/venv" }} - it { is_expected.to contain_exec("pip_install_rpyc").with_cwd('/opt/venv') } + + context 'suceeds with qualified path' do + let(:params) { { virtualenv: '/opt/venv' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_cwd('/opt/venv') } end - context "defaults to system" do - let (:params) {{ }} - it { is_expected.to contain_exec("pip_install_rpyc").with_cwd('/') } + + context 'defaults to system' do + let(:params) { {} } + + it { is_expected.to contain_exec('pip_install_rpyc').with_cwd('/') } end end - describe "proxy as" do - context "defaults to empty" do - let (:params) {{ }} - it { is_expected.to contain_exec("pip_install_rpyc").without_command(/--proxy/) } + describe 'pip_provide as' do + context 'defaults to pip' do + let(:params) { {} } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command(%r{pip}) } + it { is_expected.not_to contain_exec('pip_install_rpyc').with_command(%r{pip3}) } end - context "does not add proxy to search command if set to latest and proxy is unset" do - let (:params) {{ :ensure => 'latest' }} - it { is_expected.to contain_exec("pip_install_rpyc").without_command(/--proxy/) } - it { is_expected.to contain_exec("pip_install_rpyc").without_unless(/--proxy/) } + + context 'use pip instead of pip3 when specified' do + let(:params) { { pip_provider: 'pip' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command(%r{pip}) } + it { is_expected.not_to contain_exec('pip_install_rpyc').with_command(%r{pip3}) } + end + + context 'use pip3 instead of pip when specified' do + let(:params) { { pip_provider: 'pip3' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command(%r{pip3}) } end - context "adds proxy to install command if proxy set" do - let (:params) {{ :proxy => "http://my.proxy:3128" }} - it { is_expected.to contain_exec("pip_install_rpyc").with_command("pip wheel --help > /dev/null 2>&1 && { pip wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { pip --log /tmp/pip.log install $wheel_support_flag --proxy=http://my.proxy:3128 rpyc || pip --log /tmp/pip.log install --proxy=http://my.proxy:3128 rpyc ;}") } + end + + describe 'proxy as' do + context 'defaults to empty' do + let(:params) { {} } + + it { is_expected.not_to contain_exec('pip_install_rpyc').with_command(%r{--proxy}) } end - context "adds proxy to search command if set to latest" do - let (:params) {{ :proxy => "http://my.proxy:3128", :ensure => 'latest' }} - it { is_expected.to contain_exec("pip_install_rpyc").with_command("pip wheel --help > /dev/null 2>&1 && { pip wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { pip --log /tmp/pip.log install --upgrade $wheel_support_flag --proxy=http://my.proxy:3128 rpyc || pip --log /tmp/pip.log install --upgrade --proxy=http://my.proxy:3128 rpyc ;}") } - it { is_expected.to contain_exec("pip_install_rpyc").with_unless('pip search --proxy=http://my.proxy:3128 rpyc | grep -i INSTALLED | grep -i latest') } + + context 'adds proxy to install command if proxy set' do + let(:params) { { proxy: 'http://my.proxy:3128' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command('pip install --proxy=http://my.proxy:3128 rpyc') } end end describe 'index as' do context 'defaults to empty' do - let (:params) {{ }} - it { is_expected.to contain_exec('pip_install_rpyc').without_command(/--index-url/) } + let(:params) { {} } + + it { is_expected.not_to contain_exec('pip_install_rpyc').with_command(%r{--index-url}) } end + context 'adds index to install command if index set' do - let (:params) {{ :index => 'http://www.example.com/simple/' }} - it { is_expected.to contain_exec('pip_install_rpyc').with_command("pip wheel --help > /dev/null 2>&1 && { pip wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { pip --log /tmp/pip.log install $wheel_support_flag --index-url=http://www.example.com/simple/ rpyc || pip --log /tmp/pip.log install --index-url=http://www.example.com/simple/ rpyc ;}") } + let(:params) { { index: 'http://www.example.com/simple/' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command('pip install --index-url=http://www.example.com/simple/ rpyc') } + end + end + + describe 'extra_index as' do + context 'defaults to empty' do + let(:params) { {} } + + it { is_expected.not_to contain_exec('pip_install_rpyc').with_command(%r{--extra-index-url}) } + end + + context 'adds extra_index to install command if extra_index set' do + let(:params) { { extra_index: 'http://www.example.com/extra/simple/' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command('pip install --extra-index-url=http://www.example.com/extra/simple/ rpyc') } + end + end + + describe 'path as' do + context 'adds anaconda path to pip invocation if provider is anaconda' do + let(:params) { {} } + let(:pre_condition) { 'class {"python": provider => "anaconda", anaconda_install_path => "/opt/python3"}' } + + it { is_expected.to contain_exec('pip_install_rpyc').with_path(['/opt/python3/bin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin']) } + end + end + + describe 'install_args as' do + context 'adds install_args to install command if install_args set' do + let(:params) { { install_args: '--pre' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command('pip install --pre rpyc') } + end + end + + describe 'install latest' do + context 'does not use legacy resolver in unless' do + let(:params) { { ensure: 'latest' } } + + it { is_expected.not_to contain_exec('pip_install_rpyc').with_unless(%r{--use-deprecated=legacy-resolver}) } + end + + context 'does not use pip search in unless' do + let(:params) { { ensure: 'latest' } } + + it { is_expected.not_to contain_exec('pip_install_rpyc').with_unless(%r{search}) } + end + + context 'checks installed version of a package by converting underscores in its name to dashes' do + let(:params) { { ensure: 'latest', pkgname: 'wordpress_json' } } + + # yes, the exec title does not change if we use different pgkname + it { is_expected.to contain_exec('pip_install_rpyc').with_unless(%r{wordpress-json}) } + end + end + + describe 'install specific version' do + context 'supports v-prefixed version string' do + let(:params) { { ensure: 'v1.7.0' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command('pip install rpyc==v1.7.0') } + end + + context 'supports version string without v-prefix' do + let(:params) { { ensure: '1.7.0' } } + + it { is_expected.to contain_exec('pip_install_rpyc').with_command('pip install rpyc==1.7.0') } end - context 'adds index to search command if set to latest' do - let (:params) {{ :index => 'http://www.example.com/simple/', :ensure => 'latest' }} - it { is_expected.to contain_exec('pip_install_rpyc').with_command("pip wheel --help > /dev/null 2>&1 && { pip wheel --version > /dev/null 2>&1 || wheel_support_flag='--no-use-wheel'; } ; { pip --log /tmp/pip.log install --upgrade $wheel_support_flag --index-url=http://www.example.com/simple/ rpyc || pip --log /tmp/pip.log install --upgrade --index-url=http://www.example.com/simple/ rpyc ;}") } + end + + describe 'uninstall' do + context 'adds correct title' do + let(:params) { { ensure: 'absent' } } + + it { is_expected.not_to contain_exec('pip_install_rpyc') } + + it { is_expected.to contain_exec('pip_uninstall_rpyc').with_command(%r{uninstall.*rpyc$}) } + end + + context 'passes correct package name' do + let(:params) { { ensure: 'absent', pkgname: 'r-pyc' } } + + it { is_expected.not_to contain_exec('pip_install_rpyc') } + + it { is_expected.to contain_exec('pip_uninstall_rpyc').with_command(%r{uninstall.*r-pyc$}) } end end + end + context 'on Debian OS with pip_version 20.3.4' do + let :facts do + { + id: 'root', + kernel: 'Linux', + lsbdistcodename: 'buster', + os: { + family: 'Debian', + release: { major: '10' }, + }, + osfamily: 'Debian', + operatingsystem: 'Debian', + operatingsystemrelease: '10.12', + path: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + concat_basedir: '/dne', + pip_version: '20.3.4', + } + end + + describe 'install latest' do + context 'with legacy resolver in unless cmd' do + let(:params) { { ensure: 'latest' } } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_exec('pip_install_rpyc').with_unless(%r{--use-deprecated=legacy-resolver}) } + end + end + end +end + +describe 'python::pip', type: :define do + # rubocop:enable RSpec/RepeatedExampleGroupDescription + let(:title) { 'requests' } + + context 'on Debian OS' do + let :facts do + { + id: 'root', + kernel: 'Linux', + lsbdistcodename: 'squeeze', + os: { + family: 'Debian', + }, + osfamily: 'Debian', + operatingsystem: 'Debian', + operatingsystemrelease: '6', + path: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + concat_basedir: '/dne', + } + end + + describe 'extras as' do + context 'suceeds with no extras' do + let(:params) { {} } + + it { is_expected.to contain_exec('pip_install_requests').with_command('pip install requests') } + end + + context 'succeeds with extras' do + let(:params) { { extras: ['security'] } } + + it { is_expected.to contain_exec('pip_install_requests').with_command('pip install requests[security]') } + end + end end end +# rubocop:enable RSpec/MultipleDescribes diff --git a/spec/defines/pyvenv_spec.rb b/spec/defines/pyvenv_spec.rb index b19a0317..6ce6646b 100644 --- a/spec/defines/pyvenv_spec.rb +++ b/spec/defines/pyvenv_spec.rb @@ -1,21 +1,100 @@ +# frozen_string_literal: true + require 'spec_helper' -describe 'python::pyvenv', :type => :define do - let (:title) { '/opt/env' } - - it { - is_expected.to contain_file( '/opt/env') - is_expected.to contain_exec( "python_virtualenv_/opt/env").with_command("pyvenv --clear /opt/env") - } - - describe 'when ensure' do - context "is absent" do - let (:params) {{ - :ensure => 'absent' - }} - it { - is_expected.to contain_file( '/opt/env').with_ensure('absent').with_purge( true) - } +describe 'python::pyvenv', type: :define do + on_supported_os.each do |os, facts| + next if os == 'gentoo-3-x86_64' + + let :title do + '/opt/env' + end + + context "on #{os}" do + context 'with default parameters' do + let :facts do + facts + end + + it { is_expected.to compile } + end + + context 'with a specific python3 version' do + let :facts do + # python3 is required to use pyvenv + facts.merge( + python3_version: '3.5.1', + ) + end + + context 'with default parameters' do + it { is_expected.to contain_file('/opt/env').that_requires('Class[python::install]') } + it { is_expected.to contain_exec('python_virtualenv_/opt/env').with_command('pyvenv-3.5 --clear /opt/env && /opt/env/bin/pip install --upgrade pip && /opt/env/bin/pip install --upgrade setuptools') } + end + + describe 'when ensure' do + context 'is absent' do + let :params do + { + ensure: 'absent', + } + end + + it { + expect(subject).to contain_file('/opt/env').with_ensure('absent').with_purge(true) + } + end + end + end + + context "prompt on #{os} with python 3.6" do + let :facts do + # python 3.6 is required for venv and prompt + facts.merge( + python3_version: '3.6.1', + ) + end + let :title do + '/opt/env' + end + + context 'with prompt' do + let :params do + { + prompt: 'custom prompt', + } + end + + it { + is_expected.to contain_file('/opt/env').that_requires('Class[python::install]') + is_expected.to contain_exec('python_virtualenv_/opt/env').with_command('python3.6 -m venv --clear --prompt custom\\ prompt /opt/env && /opt/env/bin/pip install --upgrade pip && /opt/env/bin/pip install --upgrade setuptools') + } + end + end + + context "prompt on #{os} with python 3.5" do + let :facts do + facts.merge( + python3_version: '3.5.1', + ) + end + let :title do + '/opt/env' + end + + context 'with prompt' do + let :params do + { + prompt: 'custom prompt', + } + end + + it { + is_expected.to contain_file('/opt/env').that_requires('Class[python::install]') + is_expected.to contain_exec('python_virtualenv_/opt/env').with_command('pyvenv-3.5 --clear /opt/env && /opt/env/bin/pip install --upgrade pip && /opt/env/bin/pip install --upgrade setuptools') + } + end + end end end end diff --git a/spec/defines/requirements_spec.rb b/spec/defines/requirements_spec.rb index daf56423..82dc0b37 100644 --- a/spec/defines/requirements_spec.rb +++ b/spec/defines/requirements_spec.rb @@ -1,48 +1,69 @@ +# frozen_string_literal: true + require 'spec_helper' -describe 'python::requirements', :type => :define do - let (:title) { '/requirements.txt' } - context "on Debian OS" do - let :facts do - { - :id => 'root', - :kernel => 'Linux', - :lsbdistcodename => 'squeeze', - :osfamily => 'Debian', - :operatingsystem => 'Debian', - :operatingsystemrelease => '6', - :path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', - :concat_basedir => '/dne', - } - end +describe 'python::requirements', type: :define do + on_supported_os.each do |os, facts| + context "on #{os}" do + let :facts do + facts + end - describe "requirements as" do - context "/requirements.txt" do - let (:params) {{ :requirements => "/requirements.txt" }} - it { is_expected.to contain_file("/requirements.txt").with_mode('0644') } + let :title do + '/requirements.txt' end - context "/requirements.txt" do - let (:params) {{ :requirements => "/requirements.txt", :manage_requirements => false }} - it { is_expected.not_to contain_file("/requirements.txt") } + + context 'with /requirements.txt' do + let :params do + { + requirements: '/requirements.txt', + } + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_file('/requirements.txt').with_mode('0644') } + + context 'with manage_requirements => false' do + let(:params) { super().merge(manage_requirements: false) } + + it { is_expected.not_to contain_file('/requirements.txt') } + end end - describe "with owner" do - context "bob:bob" do - let (:params) {{ - :owner => 'bob', - :group => 'bob' - }} - it do - expect { - is_expected.to compile - }.to raise_error(/root user must be used when virtualenv is system/) + describe 'with owner' do + context 'bob:bob' do + let :params do + { + owner: 'bob', + group: 'bob', + } end + + it { is_expected.to compile.and_raise_error(%r{root user must be used when virtualenv is system}) } end end - describe "with owner" do - context "default" do - it { is_expected.to contain_file("/requirements.txt").with_owner('root').with_group('root') } + context 'without parameters' do + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_class('python::config') } + it { is_expected.to contain_class('python::install') } + it { is_expected.to contain_class('python::params') } + it { is_expected.to contain_class('python') } + it { is_expected.to contain_exec('python_requirements/requirements.txt') } + + if facts[:os]['family'] == 'Archlinux' + it { is_expected.not_to contain_package('pip') } + else + it { is_expected.to contain_package('pip') } + end + it { is_expected.to contain_package('python') } + it { is_expected.to contain_package('gunicorn') } + it { is_expected.to contain_file('/requirements.txt').with_owner('root').with_group('root') } + + if %w[Archlinux FreeBSD Gentoo].include?(facts[:os]['name']) + it { is_expected.not_to contain_package('python-dev') } + else + it { is_expected.to contain_package('python-dev') } end end end diff --git a/spec/spec.opts b/spec/spec.opts deleted file mode 100644 index 91cd6427..00000000 --- a/spec/spec.opts +++ /dev/null @@ -1,6 +0,0 @@ ---format -s ---colour ---loadby -mtime ---backtrace diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2c6f5664..58c9b66a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1 +1,24 @@ -require 'puppetlabs_spec_helper/module_spec_helper' +# frozen_string_literal: true + +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ + +# puppetlabs_spec_helper will set up coverage if the env variable is set. +# We want to do this if lib exists and it hasn't been explicitly set. +ENV['COVERAGE'] ||= 'yes' if Dir.exist?(File.expand_path('../lib', __dir__)) + +require 'voxpupuli/test/spec_helper' + +RSpec.configure do |c| + c.facterdb_string_keys = false +end + +add_mocked_facts! + +if File.exist?(File.join(__dir__, 'default_module_facts.yml')) + facts = YAML.safe_load(File.read(File.join(__dir__, 'default_module_facts.yml'))) + facts&.each do |name, value| + add_custom_fact name.to_sym, value + end +end +Dir['./spec/support/spec/**/*.rb'].sort.each { |f| require f } diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index c1bb6226..2681792e 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -1,33 +1,10 @@ -require 'beaker-rspec' +# frozen_string_literal: true -UNSUPPORTED_PLATFORMS = [ 'windows' ] +# Managed by modulesync - DO NOT EDIT +# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ -unless ENV['RS_PROVISION'] == 'no' or ENV['BEAKER_provision'] == 'no' - hosts.each do |host| - if host.is_pe? - install_pe - else - install_puppet - end - end -end +require 'voxpupuli/acceptance/spec_helper_acceptance' -RSpec.configure do |c| - # Project root - proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) +configure_beaker(modules: :metadata) - # Readable test descriptions - c.formatter = :documentation - - # Configure all nodes in nodeset - c.before :suite do - # Install module and dependencies - hosts.each do |host| - shell("rm -rf /etc/puppet/modules/python/") - copy_module_to(host, :source => proj_root, :module_name => 'python') - shell("/bin/touch #{default['puppetpath']}/hiera.yaml") - on host, puppet('module install puppetlabs-stdlib'), { :acceptable_exit_codes => [0,1] } - on host, puppet('module install stahnma-epel'), { :acceptable_exit_codes => [0,1] } - end - end -end +Dir['./spec/support/acceptance/**/*.rb'].sort.each { |f| require f } diff --git a/spec/type_aliases/venv/pipversion_spec.rb b/spec/type_aliases/venv/pipversion_spec.rb new file mode 100644 index 00000000..920cf061 --- /dev/null +++ b/spec/type_aliases/venv/pipversion_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Python::Venv::PipVersion' do + describe 'valid values' do + [ + '< 1', + '< 0.1', + '< 1.2.3', + '< 1.2.3.40', + '<= 1', + '<= 0.1', + '<= 1.2.3', + '<= 1.2.3.40', + '> 1', + '> 0.1', + '> 1.2.3', + '> 1.2.3.40', + '>= 1', + '>= 0.1', + '>= 1.2.3', + '>= 1.2.3.40', + '== 1', + '== 0.1', + '== 1.2.3', + '== 1.2.3.40', + ].each do |value| + describe value.inspect do + it { + expect(subject).to allow_value(value) + } + end + end + end + + describe 'invalid values' do + [ + '+ 1', + '- 0.1', + '< -1', + '< 1.+2.3.40', + '<=', + '<= 0.-1', + '<= 1.f.3', + '1.2.3.0', + 'pip > 1', + 'all', + -1, + 65_536, + :undef, + ].each do |value| + describe value.inspect do + it { + expect(subject).not_to allow_value(value) + } + end + end + end +end diff --git a/spec/unit/facter/pip_version_spec.rb b/spec/unit/facter/pip_version_spec.rb index 63a6187b..3e6cd29f 100644 --- a/spec/unit/facter/pip_version_spec.rb +++ b/spec/unit/facter/pip_version_spec.rb @@ -1,32 +1,78 @@ -require "spec_helper" +# frozen_string_literal: true + +require 'spec_helper' describe Facter::Util::Fact do - before { + before do Facter.clear - } + end + + let(:pip_version_output) do + <<~EOS + pip 6.0.6 from /opt/boxen/homebrew/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pip-6.0.6-py2.7.egg (python 2.7) + EOS + end + + let(:pip2_version_output) do + <<~EOS + pip 9.0.1 from /usr/lib/python2.7/dist-packages/pip (python 2.7) + EOS + end - let(:pip_version_output) { <<-EOS -pip 6.0.6 from /opt/boxen/homebrew/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pip-6.0.6-py2.7.egg (python 2.7) -EOS - } + let(:pip3_version_output) do + <<~EOS + pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7) + EOS + end - describe "pip_version" do + describe 'pip_version' do context 'returns pip version when pip present' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("pip").returns(true) - Facter::Util::Resolution.expects(:exec).with("pip --version 2>&1").returns(pip_version_output) - expect(Facter.value(:pip_version)).to eq("6.0.6") + allow(Facter::Util::Resolution).to receive(:which).with('pip').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('pip --version 2>&1').and_return(pip_version_output) + expect(Facter.value(:pip_version)).to eq('6.0.6') end end context 'returns nil when pip not present' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("pip").returns(false) - expect(Facter.value(:pip_version)).to eq(nil) + allow(Facter::Util::Resolution).to receive(:which).with('pip').and_return(false) + expect(Facter.value(:pip_version)).to be_nil + end + end + end + + describe 'pip2_version' do + context 'returns pip2 version when pip2 present' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('pip2').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('pip2 --version 2>&1').and_return(pip2_version_output) + expect(Facter.value(:pip2_version)).to eq('9.0.1') end end + context 'returns nil when pip2 not present' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('pip2').and_return(false) + expect(Facter.value(:pip2_version)).to be_nil + end + end + end + + describe 'pip3_version' do + context 'returns pip3 version when pip3 present' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('pip3').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('pip3 --version 2>&1').and_return(pip3_version_output) + expect(Facter.value(:pip3_version)).to eq('18.1') + end + end + + context 'returns nil when pip3 not present' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('pip3').and_return(false) + expect(Facter.value(:pip3_version)).to be_nil + end + end end end diff --git a/spec/unit/facter/python_release_spec.rb b/spec/unit/facter/python_release_spec.rb new file mode 100644 index 00000000..6026c1e3 --- /dev/null +++ b/spec/unit/facter/python_release_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Facter::Util::Fact do + before do + Facter.clear + end + + let(:python2_version_output) do + <<~EOS + Python 2.7.9 + EOS + end + let(:python3_version_output) do + <<~EOS + Python 3.3.0 + EOS + end + + describe 'python_release' do + context 'returns Python release when `python` present' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python -V 2>&1').and_return(python2_version_output) + expect(Facter.value(:python_release)).to eq('2.7') + end + end + + context 'returns nil when `python` not present' do + it do + allow(Facter::Util::Resolution).to receive(:exec).and_return(false) + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(false) + expect(Facter.value(:python_release)).to be_nil + end + end + end + + describe 'python2_release' do + context 'returns Python 2 release when `python` is present and Python 2' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python -V 2>&1').and_return(python2_version_output) + expect(Facter.value(:python2_release)).to eq('2.7') + end + end + + context 'returns Python 2 release when `python` is Python 3 and `python2` is present' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python -V 2>&1').and_return(python3_version_output) + allow(Facter::Util::Resolution).to receive(:which).with('python2').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python2 -V 2>&1').and_return(python2_version_output) + expect(Facter.value(:python2_release)).to eq('2.7') + end + end + + context 'returns nil when `python` is Python 3 and `python2` is absent' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python -V 2>&1').and_return(python3_version_output) + allow(Facter::Util::Resolution).to receive(:which).with('python2').and_return(false) + expect(Facter.value(:python2_release)).to be_nil + end + end + + context 'returns nil when `python2` and `python` are absent' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(false) + allow(Facter::Util::Resolution).to receive(:which).with('python2').and_return(false) + expect(Facter.value(:python2_release)).to be_nil + end + end + end + + describe 'python3_release' do + context 'returns Python 3 release when `python3` present' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('python3').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python3 -V 2>&1').and_return(python3_version_output) + expect(Facter.value(:python3_release)).to eq('3.3') + end + end + + context 'returns nil when `python3` not present' do + it do + allow(Facter::Util::Resolution).to receive(:which).with('python3').and_return(false) + expect(Facter.value(:python3_release)).to be_nil + end + end + end +end diff --git a/spec/unit/facter/python_version_spec.rb b/spec/unit/facter/python_version_spec.rb index 48ab1ede..d66d280b 100644 --- a/spec/unit/facter/python_version_spec.rb +++ b/spec/unit/facter/python_version_spec.rb @@ -1,98 +1,91 @@ -require "spec_helper" +# frozen_string_literal: true + +require 'spec_helper' describe Facter::Util::Fact do - before { + before do Facter.clear - } + end - let(:python2_version_output) { <<-EOS -Python 2.7.9 -EOS - } - let(:python3_version_output) { <<-EOS -Python 3.3.0 -EOS - } + let(:python2_version_output) do + <<~EOS + Python 2.7.9 + EOS + end + let(:python3_version_output) do + <<~EOS + Python 3.3.0 + EOS + end - describe "python_version" do + describe 'python_version' do context 'returns Python version when `python` present' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("python").returns(true) - Facter::Util::Resolution.expects(:exec).with("python -V 2>&1").returns(python2_version_output) - expect(Facter.value(:python_version)).to eq("2.7.9") + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python -V 2>&1').and_return(python2_version_output) + expect(Facter.value(:python_version)).to eq('2.7.9') end end context 'returns nil when `python` not present' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("python").returns(false) - expect(Facter.value(:python_version)).to eq(nil) + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(false) + expect(Facter.value(:python_version)).to be_nil end end - end - describe "python2_version" do + describe 'python2_version' do context 'returns Python 2 version when `python` is present and Python 2' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("python").returns(true) - Facter::Util::Resolution.expects(:exec).with("python -V 2>&1").returns(python2_version_output) + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python -V 2>&1').and_return(python2_version_output) expect(Facter.value(:python2_version)).to eq('2.7.9') end end context 'returns Python 2 version when `python` is Python 3 and `python2` is present' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("python").returns(true) - Facter::Util::Resolution.expects(:exec).with("python -V 2>&1").returns(python3_version_output) - Facter::Util::Resolution.expects(:which).with("python2").returns(true) - Facter::Util::Resolution.expects(:exec).with("python2 -V 2>&1").returns(python2_version_output) + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python -V 2>&1').and_return(python3_version_output) + allow(Facter::Util::Resolution).to receive(:which).with('python2').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python2 -V 2>&1').and_return(python2_version_output) expect(Facter.value(:python2_version)).to eq('2.7.9') end end context 'returns nil when `python` is Python 3 and `python2` is absent' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("python").returns(true) - Facter::Util::Resolution.expects(:exec).with("python -V 2>&1").returns(python3_version_output) - Facter::Util::Resolution.expects(:which).with("python2").returns(false) - expect(Facter.value(:python2_version)).to eq(nil) + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python -V 2>&1').and_return(python3_version_output) + allow(Facter::Util::Resolution).to receive(:which).with('python2').and_return(false) + expect(Facter.value(:python2_version)).to be_nil end end context 'returns nil when `python2` and `python` are absent' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("python").returns(false) - Facter::Util::Resolution.expects(:which).with("python2").returns(false) - expect(Facter.value(:python2_version)).to eq(nil) + allow(Facter::Util::Resolution).to receive(:which).with('python2').and_return(false) + allow(Facter::Util::Resolution).to receive(:which).with('python').and_return(false) + expect(Facter.value(:python2_version)).to be_nil end end - end - describe "python3_version" do + describe 'python3_version' do context 'returns Python 3 version when `python3` present' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("python3").returns(true) - Facter::Util::Resolution.expects(:exec).with("python3 -V 2>&1").returns(python3_version_output) - expect(Facter.value(:python3_version)).to eq("3.3.0") + allow(Facter::Util::Resolution).to receive(:which).with('python3').and_return(true) + allow(Facter::Util::Resolution).to receive(:exec).with('python3 -V 2>&1').and_return(python3_version_output) + expect(Facter.value(:python3_version)).to eq('3.3.0') end end context 'returns nil when `python3` not present' do it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("python3").returns(false) - expect(Facter.value(:python3_version)).to eq(nil) + allow(Facter::Util::Resolution).to receive(:which).with('python3').and_return(false) + expect(Facter.value(:python3_version)).to be_nil end end - end end diff --git a/spec/unit/facter/virtualenv_version_spec.rb b/spec/unit/facter/virtualenv_version_spec.rb deleted file mode 100644 index b9aac90e..00000000 --- a/spec/unit/facter/virtualenv_version_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require "spec_helper" - -describe Facter::Util::Fact do - before { - Facter.clear - } - - let(:virtualenv_version_output) { <<-EOS -12.0.7 -EOS - } - - describe "virtualenv_version" do - context 'returns virtualenv version when virtualenv present' do - it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("virtualenv").returns(true) - Facter::Util::Resolution.expects(:exec).with("virtualenv --version 2>&1").returns(virtualenv_version_output) - expect(Facter.value(:virtualenv_version)).to eq("12.0.7") - end - end - - context 'returns nil when virtualenv not present' do - it do - Facter::Util::Resolution.stubs(:exec) - Facter::Util::Resolution.expects(:which).with("virtualenv").returns(false) - expect(Facter.value(:virtualenv_version)).to eq(nil) - end - end - - end -end diff --git a/templates/gunicorn.erb b/templates/gunicorn.erb index ca01e7d7..57dd4748 100644 --- a/templates/gunicorn.erb +++ b/templates/gunicorn.erb @@ -24,6 +24,11 @@ CONFIG = { 'python': '/usr/bin/python', <% end -%> 'args': ( +<% if @args.any? -%> +<% for arg in @args do -%> + '<%= arg %>', +<% end -%> +<% end -%> <% if !@virtualenv and !@bind -%> '--bind=unix:/tmp/gunicorn-<%= @name %>.socket', <% elsif @virtualenv and !@bind -%> @@ -31,7 +36,11 @@ CONFIG = { <% else -%> '--bind=<%= @bind %>', <% end -%> - '--workers=<%= @processorcount.to_i*2 %>', +<% if @workers -%> + '--workers=<%= @workers %>', +<% else -%> + '--workers=<%= @processor_count.to_i*2 + 1 %>', +<% end -%> '--timeout=<%= @timeout %>', <% if @access_log_format -%> '--access-logformat=<%= @access_log_format %>', diff --git a/tests/init.pp b/tests/init.pp deleted file mode 100644 index 4c51cc80..00000000 --- a/tests/init.pp +++ /dev/null @@ -1,5 +0,0 @@ -class { 'python': - version => 'system', - dev => true, - virtualenv => true, -} diff --git a/tests/pyvenv.pp b/tests/pyvenv.pp deleted file mode 100644 index 93677a5a..00000000 --- a/tests/pyvenv.pp +++ /dev/null @@ -1,12 +0,0 @@ -class { 'python': - pip => false, - version => '3', -} - -python::pyvenv { "/opt/uwsgi": -} - -python::pip { "uwsgi": - ensure => "latest", - virtualenv => "/opt/uwsgi" -} diff --git a/tests/virtualenv.pp b/tests/virtualenv.pp deleted file mode 100644 index da5ab57c..00000000 --- a/tests/virtualenv.pp +++ /dev/null @@ -1,13 +0,0 @@ -class { 'python': - version => 'system', - dev => true, - virtualenv => true, -} - -python::virtualenv { '/var/www/project1': - ensure => present, - version => 'system', - requirements => '/var/www/project1/requirements.txt', - proxy => 'http://proxy.domain.com:3128', - systempkgs => true, -} diff --git a/types/loglevel.pp b/types/loglevel.pp new file mode 100644 index 00000000..849c2236 --- /dev/null +++ b/types/loglevel.pp @@ -0,0 +1,3 @@ +# @summary Match all valid loglevels for python +# +type Python::Loglevel = Enum['debug', 'info', 'warning', 'error', 'critical'] diff --git a/types/package/ensure.pp b/types/package/ensure.pp new file mode 100644 index 00000000..9fa54968 --- /dev/null +++ b/types/package/ensure.pp @@ -0,0 +1,3 @@ +# @summary Match all valid package ensures for python +# +type Python::Package::Ensure = Enum['absent', 'present', 'installed', 'latest'] diff --git a/types/provider.pp b/types/provider.pp new file mode 100644 index 00000000..68fd7ff6 --- /dev/null +++ b/types/provider.pp @@ -0,0 +1,3 @@ +# @summary Match all valid provider for python +# +type Python::Provider = Enum['pip', 'scl', 'rhscl', 'anaconda', ''] diff --git a/types/umask.pp b/types/umask.pp new file mode 100644 index 00000000..39038093 --- /dev/null +++ b/types/umask.pp @@ -0,0 +1,3 @@ +# @summary Match valid umask for python +# +type Python::Umask = Pattern[/[0-7]{1,4}/] diff --git a/types/venv/pipversion.pp b/types/venv/pipversion.pp new file mode 100644 index 00000000..37584264 --- /dev/null +++ b/types/venv/pipversion.pp @@ -0,0 +1,6 @@ +# @summary A version type to ensure a specific Pip version in a virtual env. +# +type Python::Venv::PipVersion = Pattern[ + /^(<|>|<=|>=|==) [0-9]*(\.[0-9]+)*$/, + /\Alatest\Z/ +] diff --git a/types/version.pp b/types/version.pp new file mode 100644 index 00000000..5bbcaaa8 --- /dev/null +++ b/types/version.pp @@ -0,0 +1,8 @@ +# @summary Match all valid versions for python +# +type Python::Version = Pattern[ + /\A(python)?[0-9](\.?[0-9])*/, + /\Apypy\Z/, + /\Asystem\Z/, + /\Arh-python[0-9]{2}(?:-python)?\Z/ +]