diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7e1fe9..51a1ea0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,7 @@ jobs: # If its not configured, the repository default branch is used. base: ${{ github.ref }} filters: | - boxcutter-chef-cookbooks/boxcutter_python: 'boxcutter-chef-cookbooks/boxcutter_python/**' + boxcutter-chef-cookbooks/cookbooks/boxcutter_python: 'boxcutter-chef-cookbooks/cookbooks/boxcutter_python/**' kitchen: needs: changes diff --git a/cookbooks/boxcutter_python/.gitignore b/cookbooks/boxcutter_python/.gitignore new file mode 100644 index 0000000..f1e57b8 --- /dev/null +++ b/cookbooks/boxcutter_python/.gitignore @@ -0,0 +1,25 @@ +.vagrant +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ + +# Bundler +Gemfile.lock +gems.locked +bin/* +.bundle/* + +# test kitchen +.kitchen/ +kitchen.local.yml + +# Chef Infra +Berksfile.lock +.zero-knife.rb +Policyfile.lock.json + +.idea/ + diff --git a/cookbooks/boxcutter_python/CHANGELOG.md b/cookbooks/boxcutter_python/CHANGELOG.md new file mode 100644 index 0000000..34223b1 --- /dev/null +++ b/cookbooks/boxcutter_python/CHANGELOG.md @@ -0,0 +1,10 @@ +# boxcutter_python CHANGELOG + +This file is used to list changes made in each version of the boxcutter_python cookbook. + +## 0.1.0 + +Initial release. + +- change 0 +- change 1 diff --git a/cookbooks/boxcutter_python/LICENSE b/cookbooks/boxcutter_python/LICENSE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/cookbooks/boxcutter_python/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/cookbooks/boxcutter_python/Policyfile.rb b/cookbooks/boxcutter_python/Policyfile.rb new file mode 100644 index 0000000..252e21c --- /dev/null +++ b/cookbooks/boxcutter_python/Policyfile.rb @@ -0,0 +1,18 @@ +# Policyfile.rb - Describe how you want Chef Infra Client to build your system. +# +# For more information on the Policyfile feature, visit +# https://docs.chef.io/policyfile/ + +# A name that describes what the system you're building with Chef does. +name 'boxcutter_python' + +# Where to find external cookbooks: +default_source :chef_repo, '../../../chef-cookbooks/cookbooks' +default_source :chef_repo, '../' + +# run_list: chef-client will run these recipes in the order specified. +run_list 'boxcutter_ohai', 'boxcutter_init', 'boxcutter_python_test::default' + +# Specify a custom source for a single cookbook: +cookbook 'boxcutter_python', path: '.' +cookbook 'boxcutter_python_test', path: 'test/cookbooks/boxcutter_python_test' diff --git a/cookbooks/boxcutter_python/README.md b/cookbooks/boxcutter_python/README.md new file mode 100644 index 0000000..387f7b5 --- /dev/null +++ b/cookbooks/boxcutter_python/README.md @@ -0,0 +1,4 @@ +# boxcutter_python + +TODO: Enter the cookbook description here. + diff --git a/cookbooks/boxcutter_python/attributes/default.rb b/cookbooks/boxcutter_python/attributes/default.rb new file mode 100644 index 0000000..2441d8a --- /dev/null +++ b/cookbooks/boxcutter_python/attributes/default.rb @@ -0,0 +1,3 @@ +default['polymath_python'] = { + 'pyenv' => {}, +} diff --git a/cookbooks/boxcutter_python/chefignore b/cookbooks/boxcutter_python/chefignore new file mode 100644 index 0000000..cc170ea --- /dev/null +++ b/cookbooks/boxcutter_python/chefignore @@ -0,0 +1,115 @@ +# Put files/directories that should be ignored in this file when uploading +# to a Chef Infra Server or Supermarket. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +ehthumbs.db +Icon? +nohup.out +Thumbs.db +.envrc + +# EDITORS # +########### +.#* +.project +.settings +*_flymake +*_flymake.* +*.bak +*.sw[a-z] +*.tmproj +*~ +\#* +REVISION +TAGS* +tmtags +.vscode +.editorconfig + +## COMPILED ## +############## +*.class +*.com +*.dll +*.exe +*.o +*.pyc +*.so +*/rdoc/ +a.out +mkmf.log + +# Testing # +########### +.circleci/* +.codeclimate.yml +.delivery/* +.foodcritic +.kitchen* +.mdlrc +.overcommit.yml +.rspec +.rubocop.yml +.travis.yml +.watchr +.yamllint +azure-pipelines.yml +Dangerfile +examples/* +features/* +Guardfile +kitchen.yml* +mlc_config.json +Procfile +Rakefile +spec/* +test/* + +# SCM # +####### +.git +.gitattributes +.gitconfig +.github/* +.gitignore +.gitkeep +.gitmodules +.svn +*/.bzr/* +*/.git +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +Berksfile +Berksfile.lock +cookbooks/* +tmp + +# Bundler # +########### +vendor/* +Gemfile +Gemfile.lock + +# Policyfile # +############## +Policyfile.rb +Policyfile.lock.json + +# Documentation # +############# +CODE_OF_CONDUCT* +CONTRIBUTING* +documentation/* +TESTING* +UPGRADING* + +# Vagrant # +########### +.vagrant +Vagrantfile diff --git a/cookbooks/boxcutter_python/compliance/README.md b/cookbooks/boxcutter_python/compliance/README.md new file mode 100644 index 0000000..998facd --- /dev/null +++ b/cookbooks/boxcutter_python/compliance/README.md @@ -0,0 +1,25 @@ +# compliance + +This directory contains Cinc Auditor profile, waiver and input objects which are used with the Cinc Infra Compliance Phase. + +Detailed information on the Cinc Infra Compliance Phase can be found in the [Chef Documentation](https://docs.chef.io/chef_compliance_phase/). + +```plain +./compliance +├── inputs +├── profiles +└── waivers +``` + +Use the `cinc generate` command from Cinc Workstation to create content for these directories: + +```sh +# Generate a Cinc Auditor profile +cinc generate profile PROFILE_NAME + +# Generate a Cinc Auditor waiver file +cinc generate waiver WAIVER_NAME + +# Generate a Cinc Auditor input file +cinc generate input INPUT_NAME +``` diff --git a/cookbooks/boxcutter_python/kitchen.yml b/cookbooks/boxcutter_python/kitchen.yml new file mode 100644 index 0000000..bdb5895 --- /dev/null +++ b/cookbooks/boxcutter_python/kitchen.yml @@ -0,0 +1,59 @@ +--- +driver: + name: dokken + privileged: true # allows systemd services to start + docker_registry: docker.io + chef_image: boxcutter/cinc + chef_version: current + +provisioner: + name: dokken + product_name: cinc + chef_binary: /opt/cinc/bin/cinc-client + chef_license: accept-no-persist + chef_log_level: auto + client_rb: + # Passwd plugin is needed to use the fb_users cookbook + ohai.optional_plugins: + - Passwd + ohai.critical_plugins: + - Passwd + +transport: + name: dokken + +verifier: + name: inspec + +platforms: + # @see https://github.com/chef-cookbooks/testing_examples/blob/main/kitchen.dokken.yml + # @see https://hub.docker.com/u/dokken + - name: ubuntu-20.04 + driver: + image: boxcutter/dokken-ubuntu-20.04 + pid_one_command: /bin/systemd + + - name: ubuntu-22.04 + driver: + image: boxcutter/dokken-ubuntu-22.04 + pid_one_command: /bin/systemd + + - name: centos-stream-9 + driver: + image: boxcutter/dokken-centos-stream-9 + pid_one_command: /usr/lib/systemd/systemd + intermediate_instructions: + # stub out /etc/fstab for fb_fstab + - RUN touch /etc/fstab + # enable EPEL (for stuff like hddtemp) + - RUN rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm + +suites: + - name: default + # run_list set in Policyfile.rb, this does nothing + # run_list: + # - recipe[boxcutter_python::default] + verifier: + inspec_tests: + - test/integration/default + attributes: diff --git a/cookbooks/boxcutter_python/metadata.rb b/cookbooks/boxcutter_python/metadata.rb new file mode 100644 index 0000000..260804c --- /dev/null +++ b/cookbooks/boxcutter_python/metadata.rb @@ -0,0 +1,7 @@ +name 'boxcutter_python' +maintainer 'Boxcutter' +maintainer_email 'noreply@boxcutter.io' +license 'Apache-2.0' +description 'Installs/Configures boxcutter_python' +source_url 'https://github.com/boxcutter/boxcutter-chef-cookbooks/' +version '0.1.0' diff --git a/cookbooks/boxcutter_python/recipes/default.rb b/cookbooks/boxcutter_python/recipes/default.rb new file mode 100644 index 0000000..47c25a1 --- /dev/null +++ b/cookbooks/boxcutter_python/recipes/default.rb @@ -0,0 +1,61 @@ +# +# Cookbook:: boxcutter_python +# Recipe:: default +# +# Copyright:: 2023, Boxcutter +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +case node['platform_family'] +when 'rhel' + package %w( + git + gcc + zlib-devel + bzip2 + bzip2-devel + readline-devel + sqlite + sqlite-devel + openssl-devel + tk-devel + libffi-devel + xz-devel + ) do + action :upgrade + end +when 'debian' + package %w( + build-essential + git + libssl-dev + zlib1g-dev + libbz2-dev + libreadline-dev + libsqlite3-dev + wget + curl + llvm + libncursesw5-dev + xz-utils + tk-dev + libxml2-dev + libxmlsec1-dev + libffi-dev + liblzma-dev + ) do + action :upgrade + end +end + +boxcutter_python_pyenv 'manage' diff --git a/cookbooks/boxcutter_python/resources/pyenv.rb b/cookbooks/boxcutter_python/resources/pyenv.rb new file mode 100644 index 0000000..02e0e7f --- /dev/null +++ b/cookbooks/boxcutter_python/resources/pyenv.rb @@ -0,0 +1,84 @@ +action :manage do + node['polymath_python']['pyenv'].each do |pyenv_root, pyenv_data| + build_environment = { + 'PYENV_ROOT' => pyenv_root, + } + + execute 'run pyenv installer' do + command <<-BASH + curl https://pyenv.run | bash + BASH + user pyenv_data['user'] + group pyenv_data['group'] + environment build_environment + creates "#{pyenv_root}" + end + + current_pythons = Dir.glob("#{::File.join(pyenv_root, 'versions')}/*").select { |f| !::File.symlink?(f) }.sort + desired_pythons = pyenv_data['pythons'].keys.map { |d| ::File.join(pyenv_root, 'versions', d) } + pythons_to_delete = current_pythons - desired_pythons + Chef::Log.info("current_pythons=#{current_pythons}, pythons_to_delete=#{pythons_to_delete}") + + pythons_to_delete.each do |python_name| + directory python_name do + recursive true + action :delete + end + end + + current_virtualenvs = Dir.glob("#{::File.join(pyenv_root, 'versions')}/*").select { |f| ::File.symlink?(f) }.map { |f| ::File.basename(f) }.sort + desired_virtualenvs = pyenv_data['virtualenvs'].keys + virtualenvs_to_delete = current_virtualenvs - desired_virtualenvs + Chef::Log.info("current_virtualenvs=#{current_virtualenvs}, virtualenvs_to_delete=#{virtualenvs_to_delete}") + + virtualenvs_to_delete.each do |virtualenv_name| + boxcutter_python_pyenv_script "pyenv-uninstall-#{virtualenv_name}" do + code %(eval "$(pyenv virtualenv-init -)" && pyenv uninstall --force #{virtualenv_name}) + pyenv_root pyenv_root + user pyenv_data['user'] + group pyenv_data['group'] + live_stream true + action :run + end + end + + pyenv_data['pythons'].each do |python_name, _python_config| + boxcutter_python_pyenv_script "pyenv-install-#{python_name}" do + code "pyenv install #{python_name} -v" + pyenv_root pyenv_root + user pyenv_data['user'] + group pyenv_data['group'] + environment build_environment + live_stream true + not_if { ::File.exist?(::File.join(pyenv_root, 'versions', python_name)) } + action :run + end + + boxcutter_python_pyenv_script "upgrade-pip-#{python_name}" do + code 'python -m pip install -U pip' + pyenv_root pyenv_root + user pyenv_data['user'] + group pyenv_data['group'] + environment build_environment + live_stream true + not_if { ::File.exist?(::File.join(pyenv_root, 'versions', python_name)) } + action :run + end + end + + pyenv_data['virtualenvs'].each do |virtualenv_name, virtualenv_config| + virtualenv_python = virtualenv_config['python'] + + boxcutter_python_pyenv_script "pyenv-virtualenv-#{virtualenv_name}" do + code %(eval "$(pyenv virtualenv-init -)" && pyenv virtualenv #{virtualenv_python} #{virtualenv_name}) + pyenv_root pyenv_root + user pyenv_data['user'] + group pyenv_data['group'] + environment build_environment + live_stream true + not_if { ::File.exist?(::File.join(pyenv_root, 'versions', virtualenv_name)) } + action :run + end + end + end +end diff --git a/cookbooks/boxcutter_python/resources/pyenv_package.rb b/cookbooks/boxcutter_python/resources/pyenv_package.rb new file mode 100644 index 0000000..60b6305 --- /dev/null +++ b/cookbooks/boxcutter_python/resources/pyenv_package.rb @@ -0,0 +1,65 @@ +property :package_name, String, name_property: true +property :version, String +property :user, String +property :group, String +property :pyenv_root, String +property :pyenv_version, String +property :environment, String +property :live_stream, [true, false], default: true + +action :install do + pip_package_installed?(new_resource.package_name, new_resource.version) + + command = if new_resource.version + "pip install #{new_resource.package_name}==#{new_resource.version}" + else + "pip install #{new_resource.package_name}" + end + + boxcutter_python_pyenv_script "pip-install#{new_resource.package_name}-#{new_resource.pyenv_version}" do + code %(eval "$(pyenv virtualenv-init -)" && #{command}) + pyenv_root new_resource.pyenv_root + pyenv_version new_resource.pyenv_version + user new_resource.user + group new_resource.group + live_stream true + not_if { pip_package_installed?(new_resource.package_name, new_resource.version) } + end +end + +action_class do + def pip_package_installed?(python_package, python_version) + cmd = Mixlib::ShellOut.new(script_code(%(eval "$(pyenv virtualenv-init -)" && pip freeze --all)), user: new_resource.user, group: new_resource.group, environment: script_environment) + cmd.run_command + package_string = if python_version.nil? + "#{python_package}==#{python_version}" + else + python_package + end + cmd.stdout.include?(package_string) + end + + def script_code(command) + script = [] + script << %(export PYENV_ROOT="#{new_resource.pyenv_root}") + script << %(export PATH="${PYENV_ROOT}/bin:$PATH") + script << %{eval "$(pyenv init -)"} + if new_resource.pyenv_version + script << %(export PYENV_VERSION="#{new_resource.pyenv_version}") + end + script << command + script.join("\n").concat("\n") + end + + def script_environment + script_env = { 'PYENV_ROOT' => new_resource.pyenv_root } + script_env.merge!(new_resource.environment) if new_resource.environment + + if new_resource.user + script_env['USER'] = new_resource.user + script_env['HOME'] = ::File.expand_path("~#{new_resource.user}") + end + + script_env + end +end diff --git a/cookbooks/boxcutter_python/resources/pyenv_script.rb b/cookbooks/boxcutter_python/resources/pyenv_script.rb new file mode 100644 index 0000000..e6a7e56 --- /dev/null +++ b/cookbooks/boxcutter_python/resources/pyenv_script.rb @@ -0,0 +1,58 @@ +property :pyenv_root, String +property :pyenv_version, String +property :code, String +property :creates, String +property :cwd, String +property :environment, Hash +property :group, String +property :path, Array +property :returns, Array, default: [0] +property :timeout, Integer +property :user, String +property :umask, [String, Integer] +property :live_stream, [true, false], default: true + +action :run do + bash new_resource.name do + code script_code + creates new_resource.creates if new_resource.creates + cwd new_resource.cwd if new_resource.cwd + user new_resource.user if new_resource.user + group new_resource.group if new_resource.group + returns new_resource.returns if new_resource.returns + timeout new_resource.timeout if new_resource.timeout + umask new_resource.umask if new_resource.umask + environment(script_environment) + live_stream new_resource.live_stream + end +end + +action_class do + def script_code + script = [] + script << %(export PYENV_ROOT="#{new_resource.pyenv_root}") + script << %(export PATH="${PYENV_ROOT}/bin:$PATH") + script << %{eval "$(pyenv init -)"} + if new_resource.pyenv_version + script << %(export PYENV_VERSION="#{new_resource.pyenv_version}") + end + script << new_resource.code + script.join("\n").concat("\n") + end + + def script_environment + script_env = { 'PYENV_ROOT' => new_resource.pyenv_root } + script_env.merge!(new_resource.environment) if new_resource.environment + + if new_resource.path + script_env['PATH'] = "#{new_resource.path.join(':')}:#{ENV['PATH']}" + end + + if new_resource.user + script_env['USER'] = new_resource.user + script_env['HOME'] = ::File.expand_path("~#{new_resource.user}") + end + + script_env + end +end diff --git a/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/metadata.rb b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/metadata.rb new file mode 100644 index 0000000..6f39cdd --- /dev/null +++ b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/metadata.rb @@ -0,0 +1,4 @@ +name 'boxcutter_python_test' +# this number should NEVER change +version '0.1.0' +depends 'boxcutter_python' diff --git a/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/recipes/default.rb b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/recipes/default.rb new file mode 100644 index 0000000..1b65764 --- /dev/null +++ b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/recipes/default.rb @@ -0,0 +1,66 @@ +# +# Cookbook:: boxcutter_python_test +# Recipe:: default +# + +python_user = 'python' +python_group = 'python' +python_home = '/home/python' + +node.default['polymath_python']['pyenv'] = { + ::File.join(python_home, '.pyenv') => { + 'user' => python_user, + 'group' => python_group, + 'default_python' => '3.8.13', + 'pythons' => { + '3.8.13' => nil, + '3.10.4' => nil, + }, + 'virtualenvs' => { + 'venv38' => { + 'python' => '3.8.13', + }, + 'venv310' => { + 'python' => '3.10.4', + }, + }, + }, +} + +FB::Users.initialize_group(node, python_user) +node.default['fb_users']['users'][python_user] = { + 'home' => python_home, + 'shell' => '/bin/bash', + 'gid' => python_group, + 'action' => :add, +} + +directory ::File.join(python_home, '.bashrc.d') do + owner python_user + group python_group + mode '0700' +end + +template ::File.join(python_home, '.bashrc.d', '100.pyenv.bashrc') do + source 'pyenv.bashrc.erb' + owner python_user + group python_group + mode '0700' +end + +template ::File.join(python_home, '.bashrc') do + source 'python.bashrc.erb' + owner python_user + group python_group + mode '0644' +end + +include_recipe 'boxcutter_python::default' + +# boxcutter_python_pyenv_package 'flask' do +# pyenv_root ::File.join(python_home, '.pyenv') +# pyenv_version '3.8.13' +# user python_user +# group python_group +# live_stream true +# end diff --git a/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/centos-9/python.bashrc.erb b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/centos-9/python.bashrc.erb new file mode 100644 index 0000000..7abc618 --- /dev/null +++ b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/centos-9/python.bashrc.erb @@ -0,0 +1,27 @@ +# .bashrc + +# Source global definitions +if [ -f /etc/bashrc ]; then + . /etc/bashrc +fi + +# User specific environment +if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]] +then + PATH="$HOME/.local/bin:$HOME/bin:$PATH" +fi +export PATH + +# Uncomment the following line if you don't like systemctl's auto-paging feature: +# export SYSTEMD_PAGER= + +# User specific aliases and functions +if [ -d ~/.bashrc.d ]; then + for rc in ~/.bashrc.d/*; do + if [ -f "$rc" ]; then + . "$rc" + fi + done +fi + +unset rc diff --git a/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/pyenv.bashrc.erb b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/pyenv.bashrc.erb new file mode 100644 index 0000000..d1a51dd --- /dev/null +++ b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/pyenv.bashrc.erb @@ -0,0 +1,4 @@ +export PATH="$HOME/.pyenv/bin:$PATH" +eval "$(pyenv init -)" +eval "$(pyenv virtualenv-init -)" +pyenv global 3.8.13 diff --git a/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/ubuntu-22.04/python.bashrc.erb b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/ubuntu-22.04/python.bashrc.erb new file mode 100644 index 0000000..94d260f --- /dev/null +++ b/cookbooks/boxcutter_python/test/cookbooks/boxcutter_python_test/templates/ubuntu-22.04/python.bashrc.erb @@ -0,0 +1,136 @@ +# ~/.bashrc: executed by bash(1) for non-login shells. +# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) +# for examples + +# If not running interactively, don't do anything +case $- in + *i*) ;; + *) return;; +esac + +# stolen from RHEL /etc/bashrc - should be ported to global files +if ! shopt -q login_shell ; then + for i in /etc/profile.d/*.sh; do + if [ -r "$i" ]; then + if [ "$PS1" ]; then + . "$i" + else + . "$i" >/dev/null + fi + fi + done + unset i +fi + +# don't put duplicate lines or lines starting with space in the history. +# See bash(1) for more options +HISTCONTROL=ignoreboth + +# append to the history file, don't overwrite it +shopt -s histappend + +# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) +HISTSIZE=1000 +HISTFILESIZE=2000 + +# check the window size after each command and, if necessary, +# update the values of LINES and COLUMNS. +shopt -s checkwinsize + +# If set, the pattern "**" used in a pathname expansion context will +# match all files and zero or more directories and subdirectories. +#shopt -s globstar + +# make less more friendly for non-text input files, see lesspipe(1) +[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" + +# set variable identifying the chroot you work in (used in the prompt below) +if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then + debian_chroot=$(cat /etc/debian_chroot) +fi + +# set a fancy prompt (non-color, unless we know we "want" color) +case "$TERM" in + xterm-color|*-256color) color_prompt=yes;; +esac + +# uncomment for a colored prompt, if the terminal has the capability; turned +# off by default to not distract the user: the focus in a terminal window +# should be on the output of commands, not on the prompt +#force_color_prompt=yes + +if [ -n "$force_color_prompt" ]; then + if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi +fi + +if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' +else + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' +fi +unset color_prompt force_color_prompt + +# If this is an xterm set the title to user@host:dir +case "$TERM" in +xterm*|rxvt*) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" + ;; +*) + ;; +esac + +# enable color support of ls and also add handy aliases +if [ -x /usr/bin/dircolors ]; then + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + #alias dir='dir --color=auto' + #alias vdir='vdir --color=auto' + + alias grep='grep --color=auto' + alias fgrep='fgrep --color=auto' + alias egrep='egrep --color=auto' +fi + +# colored GCC warnings and errors +#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' + +# some more ls aliases +alias ll='ls -alF' +alias la='ls -A' +alias l='ls -CF' + +# Add an "alert" alias for long running commands. Use like so: +# sleep 10; alert +alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' + +# Alias definitions. +# You may want to put all your additions into a separate file like +# ~/.bash_aliases, instead of adding them here directly. +# See /usr/share/doc/bash-doc/examples in the bash-doc package. + +if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases +fi + +# enable programmable completion features (you don't need to enable +# this, if it's already enabled in /etc/bash.bashrc and /etc/profile +# sources /etc/bash.bashrc). +if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi +fi + +for config in ~/.bashrc.d/*.bashrc; do + source $config +done +unset config diff --git a/cookbooks/boxcutter_python/test/integration/default/default_test.rb b/cookbooks/boxcutter_python/test/integration/default/default_test.rb new file mode 100644 index 0000000..cc311c1 --- /dev/null +++ b/cookbooks/boxcutter_python/test/integration/default/default_test.rb @@ -0,0 +1,26 @@ +# Chef InSpec test for recipe boxcutter_python::default + +# The Chef InSpec reference, with examples and extensive documentation, can be +# found at https://docs.chef.io/inspec/resources/ + +pyenv_user = 'python' +pyenv_root = '/home/python/.pyenv' + +describe command("PYENV_ROOT=#{pyenv_root} #{pyenv_root}/bin/pyenv --version") do + its('stdout') { should match(/pyenv /) } +end + +describe command("PYENV_ROOT=#{pyenv_root} #{pyenv_root}/bin/pyenv virtualenv --version") do + its('stdout') { should match(/pyenv-virtualenv /) } +end + +describe command("PYENV_ROOT=#{pyenv_root} #{pyenv_root}/bin/pyenv which python") do + its('exit_status') { should cmp 0 } +end + +describe command("su --login --command \"PYENV_ROOT=#{pyenv_root} #{pyenv_root}/bin/pyenv versions --bare\" #{pyenv_user}") do + its('stdout') { should match('3.8.13') } + its('stdout') { should match('3.10.4') } + its('stdout') { should match('venv38') } + its('stdout') { should match('venv310') } +end