├── .github └── workflows │ └── ruby.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin └── fpm-cook ├── docker ├── Dockerfile.ubuntu-16.04 ├── Dockerfile.ubuntu-18.04 └── docker-entrypoint.sh ├── docs ├── .gitignore ├── Makefile ├── conf.py ├── index.rst └── pages │ ├── getting-started.rst │ └── using-hiera.rst ├── fpm-cookery.gemspec ├── lib ├── fpm │ └── cookery │ │ ├── book.rb │ │ ├── book_hook.rb │ │ ├── chain_packager.rb │ │ ├── cli.rb │ │ ├── config.rb │ │ ├── dependency_inspector.rb │ │ ├── docker_packager.rb │ │ ├── environment.rb │ │ ├── exceptions.rb │ │ ├── facts.rb │ │ ├── hiera.rb │ │ ├── hiera │ │ ├── defaults.rb │ │ └── scope.rb │ │ ├── inheritable_attr.rb │ │ ├── lifecycle_hooks.rb │ │ ├── log.rb │ │ ├── log │ │ ├── color.rb │ │ ├── hiera.rb │ │ └── output │ │ │ ├── console.rb │ │ │ ├── console_color.rb │ │ │ └── null.rb │ │ ├── omnibus_packager.rb │ │ ├── package │ │ ├── cpan.rb │ │ ├── dir.rb │ │ ├── gem.rb │ │ ├── maintainer.rb │ │ ├── npm.rb │ │ ├── package.rb │ │ ├── pear.rb │ │ ├── python.rb │ │ ├── version.rb │ │ └── virtualenv.rb │ │ ├── packager.rb │ │ ├── path.rb │ │ ├── path_helper.rb │ │ ├── recipe.rb │ │ ├── shellout.rb │ │ ├── source.rb │ │ ├── source_handler.rb │ │ ├── source_handler │ │ ├── curl.rb │ │ ├── directory.rb │ │ ├── git.rb │ │ ├── hg.rb │ │ ├── local_path.rb │ │ ├── noop.rb │ │ ├── svn.rb │ │ └── template.rb │ │ ├── source_integrity_check.rb │ │ ├── utils.rb │ │ └── version.rb └── hiera │ └── fpm_cookery_logger.rb ├── recipes ├── activemq │ └── recipe.rb ├── arr-pm │ └── recipe.rb ├── backports │ └── recipe.rb ├── cabin │ └── recipe.rb ├── clamp │ └── recipe.rb ├── facter │ └── recipe.rb ├── fpm-cookery-gem │ ├── addressable.rb │ ├── arr-pm.rb │ ├── backports.rb │ ├── cabin.rb │ ├── childprocess.rb │ ├── clamp.rb │ ├── facter.rb │ ├── ffi.rb │ ├── fpm.rb │ ├── hiera.rb │ ├── json.rb │ ├── json_pure.rb │ ├── puppet.rb │ ├── recipe.rb │ ├── rgen.rb │ └── systemu.rb ├── fpm-cookery │ ├── fpm-cook.bin │ ├── recipe.rb │ └── ruby.rb ├── fpm │ └── recipe.rb ├── httpie │ └── recipe.rb ├── json │ └── recipe.rb ├── nodejs │ └── recipe.rb ├── omnibustest │ ├── bundler-gem.rb │ ├── recipe.rb │ └── ruby.rb ├── open4 │ └── recipe.rb ├── python-requests │ └── recipe.rb ├── python-test │ └── recipe.rb └── redis │ ├── config │ ├── common.yaml │ ├── git_2.4.2_tag.yaml │ ├── git_2.4.yaml │ ├── git_sha_072a905.yaml │ ├── svn_r2400.yaml │ └── svn_trunk.yaml │ ├── recipe.rb │ └── redis-server.init.d └── spec ├── book_spec.rb ├── config_spec.rb ├── environment_spec.rb ├── facts_spec.rb ├── fixtures ├── hiera_config │ ├── CentOS.yaml │ ├── common.yaml │ ├── custom.yaml │ └── rpm.yaml ├── test-config-1.yml └── test-source-1.0.tar.gz ├── hiera_spec.rb ├── inheritable_attr_spec.rb ├── package_dir_spec.rb ├── package_gem_spec.rb ├── package_maintainer_spec.rb ├── package_spec.rb ├── package_version_spec.rb ├── path_helper_spec.rb ├── path_spec.rb ├── recipe_spec.rb ├── source_handler_spec.rb ├── source_integrity_check_spec.rb ├── source_spec.rb ├── spec_helper.rb ├── support └── shared_context.rb └── utils_spec.rb /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | ruby: ["2.7", "3.0", "3.1", "3.2"] 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Set up Ruby ${{ matrix.ruby }} 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: ${{ matrix.ruby }} 19 | bundler-cache: true 20 | - name: Build and test with Rake 21 | run: | 22 | sudo apt-get update -qq 23 | sudo apt-get install -yqq python3-sphinx 24 | gem install bundler 25 | bundle install --jobs 4 --retry 3 26 | bundle exec rake 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | cache/ 6 | pkg/ 7 | tmp-build/ 8 | tmp-dest/ 9 | .buildpath 10 | .project 11 | .rspec 12 | .vagrant 13 | Vagrantfile 14 | /vendor 15 | /coverage 16 | /doc 17 | 18 | # vim backups 19 | *~ 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2012 Max Howell and other contributors. 2 | Copyright (c) 2011 Bernd Ahlers 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fpm-cookery - For building software 2 | 3 | ![Build Status](https://github.com/bernd/fpm-cookery/actions/workflows/ruby.yml/badge.svg) 4 | 5 | A tool for building software packages with 6 | [fpm](https://github.com/jordansissel/fpm). 7 | 8 | The [fpm](https://github.com/jordansissel/fpm) project is really nice for 9 | building operating system packages like `.deb` and `.rpm`. But it only helps 10 | you to create the packages and doesn't help you with actually building the 11 | software. 12 | 13 | __fpm-cookery__ provides an infrastructure to automatically build software 14 | based on recipes. It's heavily inspired and borrows code from the great 15 | [homebrew](https://github.com/mxcl/homebrew) and 16 | [brew2deb](https://github.com/tmm1/brew2deb) projects. 17 | The [OpenBSD Ports System](http://www.openbsd.org/faq/ports/index.html) is 18 | probably another source of inspiration since I've been working with that for 19 | quite some time 20 | 21 | It is using __fpm__ to create the actual packages. 22 | 23 | ## Documentation 24 | 25 | Please find the documentation page here: https://fpm-cookery.readthedocs.org/ 26 | 27 | The documentation source is located in the `docs/` folder. Pull requests welcome! :) 28 | 29 | Hosting and building of the documentation is provided by the great [Read the Docs project](https://readthedocs.org/)! 30 | 31 | ## Why? 32 | 33 | Building operating system packages for Debian/Ubuntu and RedHat using the 34 | official process and tools is pretty annoying if you just want some custom 35 | packages. Jordan's [fpm](https://github.com/jordansissel/fpm) removes the 36 | biggest hurdle by providing a simple command line tool to build packages 37 | for different operating systems. 38 | 39 | Before you can use __fpm__ to create the package, you have to build the software, 40 | though. In the past I've been using some shell scripts and Makefiles to 41 | automate this task. 42 | 43 | Then I discovered Aman's [brew2deb](https://github.com/tmm1/brew2deb) which is 44 | actually [homebrew](https://github.com/mxcl/homebrew) with some modifications 45 | to make it work on Linux. (only Debian/Ubuntu for now) Since __homebrew__ was 46 | designed for Mac OS X, I thought it would be nice to have a "native" Linux 47 | tool for the job. 48 | 49 | __fpm-cookery__ is my attempt to build such a tool. 50 | 51 | ## Features 52 | 53 | * Download of the source archives. (via __curl(1)__) 54 | * Recipes to describe and execute the software build. 55 | (e.g. configure, make, make install) 56 | * Sandboxed builds. 57 | * Package creation via __fpm__. 58 | * Standalone recipe trees/books/you name it. No need to put the recipes into 59 | the __fpm-cookery__ source tree. 60 | * Can build [Omnibus](http://wiki.opscode.com/display/chef/Omnibus+Information) 61 | style packages (allows you to embed many builds into the same package - 62 | used by the Opscode folks to build an embedded Ruby and the gems for Chef into 63 | a single package; also the [Sensu](https://github.com/sensu/sensu) guys do something similar.) 64 | 65 | ## Upcoming Features 66 | 67 | * Recipe validation. 68 | * More source types. (hg, bzr, ...) 69 | * Progress output and logging. 70 | * Extend recipe features and build/install helpers. 71 | * Configuration file. (for stuff like vendor and maintainer) 72 | * Options for the `fpm-cook` command. 73 | * Manpage for the `fpm-cook` command. 74 | 75 | ## Getting Started 76 | 77 | __fpm-cookery__ is available as a gem. 78 | 79 | $ gem install fpm-cookery 80 | 81 | Create a recipe directory or change into an existing recipe tree. 82 | 83 | $ cd recipes/redis 84 | $ fpm-cook clean 85 | $ fpm-cook 86 | 87 | You can install the development dependencies with `bundle install` and run 88 | the included test suite with `rake test`. 89 | 90 | ## Status 91 | 92 | It can build the included `recipes/redis/recipe.rb` and 93 | `recipes/nodejs/recipe.rb` recipes. (both imported from __brew2deb__) 94 | See __CAVEATS__ for an incomplete list of missing stuff. 95 | 96 | ## Example Recipe 97 | 98 | The following is an example recipe. I have some more in my recipe collection 99 | [over here](https://github.com/bernd/fpm-recipes). 100 | 101 | ```ruby 102 | class Redis < FPM::Cookery::Recipe 103 | homepage 'http://redis.io' 104 | source 'http://redis.googlecode.com/files/redis-2.2.5.tar.gz' 105 | md5 'fe6395bbd2cadc45f4f20f6bbe05ed09' 106 | 107 | name 'redis-server' 108 | version '2.2.5' 109 | revision '1' 110 | 111 | description 'An advanced key-value store.' 112 | 113 | conflicts 'redis-server' 114 | 115 | config_files '/etc/redis/redis.conf' 116 | 117 | def build 118 | make 119 | 120 | inline_replace 'redis.conf' do |s| 121 | s.gsub! 'daemonize no', 'daemonize yes' 122 | end 123 | end 124 | 125 | def install 126 | # make :install, 'DESTDIR' => destdir 127 | 128 | var('lib/redis').mkdir 129 | 130 | %w(run log/redis).each {|p| var(p).mkdir } 131 | 132 | bin.install ['src/redis-server', 'src/redis-cli'] 133 | 134 | etc('redis').install 'redis.conf' 135 | etc('init.d').install 'redis-server.init.d' => 'redis-server' 136 | end 137 | end 138 | ``` 139 | 140 | ## CAVEATS 141 | 142 | * At the moment, there's only a small subset of the __homebrew__ DSL implemented. 143 | * No recipe documentation and API documentation yet. 144 | * No recipe validation yet. 145 | * No dependency validation yet. 146 | * Pretty new and not well tested. 147 | 148 | ## Credits 149 | 150 | __fpm-cookery__ borrows lots of __ideas__ and also __code__ from the 151 | [homebrew](https://github.com/mxcl/homebrew) and 152 | [brew2deb](https://github.com/tmm1/brew2deb) projects. 153 | 154 | ## License 155 | 156 | The BSD 2-Clause License - See [LICENSE](LICENSE) for details 157 | 158 | ## How To Contribute 159 | 160 | * I'd love to hear if you like it, hate it, use it and if you have suggestions 161 | and/or problems. 162 | * Send pull requests. (hugs for topic branches and tests) 163 | * Have fun! 164 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require 'bundler/gem_tasks' 3 | 4 | desc 'Run all specs' 5 | RSpec::Core::RakeTask.new(:spec) 6 | 7 | task :default => :spec 8 | 9 | namespace :docs do |ns| 10 | require 'systemu' 11 | 12 | docs_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'docs') 13 | 14 | Dir.chdir docs_dir do 15 | sphinxbuild = ENV['SPHINXBUILD'] || 'sphinx-build' 16 | 17 | status, stdout, stderr = systemu "make SPHINXBUILD=#{sphinxbuild} help" 18 | if status != 0 and Rake.verbose 19 | $stderr.puts '# Unable to load tasks in the `docs` namespace:' 20 | stderr.each_line { |l| $stderr.puts "# #{l}" } 21 | end 22 | 23 | desc 'clean up doc builds' 24 | task 'clean' do 25 | Dir.chdir docs_dir do 26 | system "make SPHINXBUILD=#{sphinxbuild} clean" 27 | end 28 | end 29 | 30 | stdout.each_line.grep(/^\s+(\w+?)\s+(.*)$/) do 31 | t, d = $1, $2 32 | 33 | desc d 34 | task t do 35 | Dir.chdir docs_dir do 36 | system "make SPHINXBUILD=#{sphinxbuild} #{t}" 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /bin/fpm-cook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 4 | 5 | require 'fpm/cookery/cli' 6 | 7 | FPM::Cookery::CLI.run 8 | -------------------------------------------------------------------------------- /docker/Dockerfile.ubuntu-16.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update \ 4 | && apt-get install --no-install-recommends -y \ 5 | curl \ 6 | wget \ 7 | git \ 8 | ruby2.3-dev \ 9 | build-essential \ 10 | man-db \ 11 | unzip \ 12 | && gem install --no-ri --no-rdoc fpm-cookery \ 13 | && apt-get clean \ 14 | && rm -rf \ 15 | /tmp/* \ 16 | /root/.gem \ 17 | /var/cache/debconf/* \ 18 | /var/lib/gems/*/cache/* \ 19 | /var/lib/apt/lists/* \ 20 | /var/log/* \ 21 | /usr/share/X11 \ 22 | /usr/share/doc/* 23 | 24 | COPY docker-entrypoint.sh / 25 | 26 | ENTRYPOINT ["/docker-entrypoint.sh"] 27 | CMD ["fpm-cook"] 28 | -------------------------------------------------------------------------------- /docker/Dockerfile.ubuntu-18.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update \ 4 | && apt-get install --no-install-recommends -y \ 5 | curl \ 6 | wget \ 7 | git \ 8 | ruby2.5-dev \ 9 | build-essential \ 10 | man-db \ 11 | unzip \ 12 | && gem install --no-ri --no-rdoc fpm-cookery \ 13 | && apt-get clean \ 14 | && rm -rf \ 15 | /tmp/* \ 16 | /root/.gem \ 17 | /var/cache/debconf/* \ 18 | /var/lib/gems/*/cache/* \ 19 | /var/lib/apt/lists/* \ 20 | /var/log/* \ 21 | /usr/share/X11 \ 22 | /usr/share/doc/* 23 | 24 | COPY docker-entrypoint.sh / 25 | 26 | ENTRYPOINT ["/docker-entrypoint.sh"] 27 | CMD ["fpm-cook"] 28 | -------------------------------------------------------------------------------- /docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | if [ "$1" != "fpm-cook" ]; then 6 | exec "$@" 7 | fi 8 | 9 | # Signal fpm-cook that we are already running inside a container to avoid 10 | # trying to start another one. 11 | export FPMC_INSIDE_DOCKER=true 12 | 13 | if [ -x "/usr/bin/apt-get" ]; then 14 | # Ubuntu/Debian containers use a custom apt config to remove archives 15 | # after installing a package. We don't want that because it removes 16 | # the possibility to cache the apt archive directory. 17 | rm -f /etc/apt/apt.conf.d/docker-clean 18 | 19 | # Make sure we have updated apt repositories. Container images usually 20 | # have either no updated lists or they are outdated. 21 | if [ -n "$FPMC_DEBUG" ]; then 22 | apt-get update 23 | else 24 | apt-get update >/dev/null 25 | fi 26 | fi 27 | 28 | # Remove existing temporary directories to ensure a full build 29 | rm -rf tmp-build tmp-dest 30 | 31 | shift 32 | fpm-cook "$@" 33 | 34 | if [ -n "$FPMC_UID" -a -n "$FPMC_GID" ]; then 35 | # Change ownership to make sure the user that executed fpm-cook can 36 | # modify created files. 37 | chown -R ${FPMC_UID}:${FPMC_GID} cache pkg tmp-build tmp-dest 38 | fi 39 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/fpm-cookery.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/fpm-cookery.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/fpm-cookery" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/fpm-cookery" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. fpm-cookery documentation main file, created by 2 | sphinx-quickstart on Sat Jul 25 11:02:45 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to the fpm-cookery documentation! 7 | ========================================= 8 | 9 | **Current version:** |version| 10 | 11 | fpm-cookery provides an infrastructure to automatically build software based on recipes. It's heavily inspired and borrows code from the great `homebrew `_ and `brew2deb `_ projects. 12 | 13 | Features 14 | ^^^^^^^^ 15 | 16 | * Source archive download and caching. 17 | * Recipes to describe and execute the software build. (e.g. configure, make, make install) 18 | * Sandboxed builds. 19 | * Package creation via `fpm `_. 20 | * Standalone recipe trees/books/you name it. No need to put the recipes into the fpm-cookery source tree. 21 | 22 | Documentation Contents 23 | ^^^^^^^^^^^^^^^^^^^^^^ 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | 28 | pages/getting-started 29 | pages/using-hiera 30 | 31 | 32 | Indices and tables 33 | ================== 34 | 35 | * :ref:`genindex` 36 | * :ref:`modindex` 37 | * :ref:`search` 38 | 39 | -------------------------------------------------------------------------------- /docs/pages/getting-started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | This page helps you to get started with the fpm-cookery tool and guides you 5 | through the installation and the creation of a simple recipe to build your 6 | first package. 7 | 8 | You will create a package for the `tmux `_ program. 9 | 10 | Prerequisites 11 | ------------- 12 | 13 | The following instructions have been tested with an Ubuntu 12.04 Linux system. 14 | It might work on other versions or other Linux systems but that cannot be 15 | guaranteed. Please use something like `Vagrant `_ to 16 | create an Ubuntu 12.04 VM if you do not have one at hand. 17 | 18 | Installation 19 | ------------ 20 | 21 | Rubygems 22 | ^^^^^^^^ 23 | 24 | fpm-cookery is written in Ruby. Before we can actually install the rubygem, you 25 | have to install a Ruby interpreter and some build tools. 26 | Execute the following to install the required packages:: 27 | 28 | $ sudo apt-get install ruby1.9.1 ruby1.9.1-dev build-essential curl 29 | 30 | Ruby 1.9 includes the ``gem`` program to install rubygems:: 31 | 32 | $ sudo gem install fpm-cookery 33 | 34 | This installs the fpm-cookery rubygem and its dependencies. At the end you 35 | should see something like "Successfully installed fpm-cookery-|version|". 36 | 37 | Your fpm-cookery installation is ready to build some packages now! 38 | 39 | OS Package 40 | ^^^^^^^^^^ 41 | 42 | We are planning to provide a packaged version for different operating systems. 43 | Please use the Rubygems installation method above in the meantime. 44 | 45 | The Recipe 46 | ---------- 47 | 48 | The recipe is a Ruby file that contains a simple class which acts as a DSL 49 | to set the attributes of a package (like name and version) and to describe 50 | the build and installation process of a package. 51 | 52 | You might want to create some folders to organize your recipes:: 53 | 54 | $ mkdir recipes 55 | $ mkdir recipes/tmux 56 | $ cd recipes/tmux 57 | $ touch recipe.rb 58 | 59 | The last command creates an empty recipe file. See the following snippet for 60 | the complete recipe to build a tmux package. We will go through each step 61 | afterwards. Use your text editor to add the code to the ``recipe.rb`` file. 62 | 63 | .. code-block:: ruby 64 | 65 | class Tmux < FPM::Cookery::Recipe 66 | description 'terminal multiplexer' 67 | 68 | name 'tmux' 69 | version '1.9a' 70 | homepage 'http://tmux.github.io' 71 | source 'https://github.com/tmux/tmux/releases/download/1.9a/tmux-1.9a.tar.gz' 72 | 73 | build_depends 'libevent-dev', 'libncurses5-dev' 74 | depends 'libevent-2.0-5' 75 | 76 | def build 77 | configure :prefix => prefix 78 | make 79 | end 80 | 81 | def install 82 | make :install, 'DESTDIR' => destdir 83 | end 84 | end 85 | 86 | Example Workflow 87 | ---------------- 88 | 89 | The following commands require the ``recipe.rb`` recipe file created above. 90 | 91 | .. code-block:: none 92 | 93 | $ fpm-cook 94 | ===> Starting package creation for tmux-1.9a (ubuntu, deb) 95 | ===> 96 | ===> Verifying build_depends and depends with Puppet 97 | ===> Verifying package: libevent-dev 98 | ===> Verifying package: libevent-2.0-5 99 | ===> Missing/wrong version packages: libevent-dev 100 | ERROR: Not running as root; please run 'sudo fpm-cook install-deps' to install dependencies. 101 | 102 | .. code-block:: none 103 | 104 | $ sudo fpm-cook install-deps 105 | ===> Verifying build_depends and depends with Puppet 106 | ===> Verifying package: libevent-dev 107 | ===> Verifying package: libevent-2.0-5 108 | ===> Missing/wrong version packages: libevent-dev 109 | ===> Running as root; installing missing/wrong version build_depends and depends with Puppet 110 | ===> Installing package: libevent-dev 111 | ===> ensure changed 'purged' to 'present' 112 | ===> All dependencies installed! 113 | 114 | .. code-block:: none 115 | 116 | $ fpm-cook 117 | ===> Starting package creation for tmux-1.9a (ubuntu, deb) 118 | ===> 119 | ===> Verifying build_depends and depends with Puppet 120 | ===> Verifying package: libevent-dev 121 | ===> Verifying package: libncurses5-dev 122 | ===> Verifying package: libevent-2.0-5 123 | ===> All build_depends and depends packages installed 124 | ===> Fetching source: 125 | ######################################################################## 100.0% 126 | ===> Building in /home/vagrant/recipes/tmux/tmp-build/tmux-1.9a 127 | checking for a BSD-compatible install... /usr/bin/install -c 128 | checking whether build environment is sane... yes 129 | 130 | [lots of output removed] 131 | 132 | make[1]: Nothing to be done for `install-data-am'. 133 | make[1]: Leaving directory `/home/vagrant/recipes/tmux/tmp-build/tmux-1.9a' 134 | ===> [FPM] Converting dir to deb {} 135 | ===> [FPM] No deb_installed_size set, calculating now. {} 136 | ===> [FPM] Reading template {"path":"/var/lib/gems/1.9.1/gems/fpm-1.0.2/templates/deb.erb"} 137 | ===> [FPM] Creating {"path":"/tmp/package-deb-build20140308-7998-1v6uqm5/control.tar.gz","from":"/tmp/package-deb-build20140308-7998-1v6uqm5/control"} 138 | ===> [FPM] Created deb package {"path":"tmux_1.9a-1_amd64.deb"} 139 | ===> Created package: /home/vagrant/recipes/tmux/pkg/tmux_1.9a-1_amd64.deb 140 | 141 | .. code-block:: none 142 | 143 | . 144 | |-- cache 145 | | `-- tmux-1.9a.tar.gz 146 | |-- pkg 147 | | `-- tmux_1.9a-1_amd64.deb 148 | |-- recipe.rb 149 | |-- tmp-build 150 | | `-- tmux-1.9a 151 | `-- tmp-dest 152 | `-- usr 153 | 154 | .. code-block:: none 155 | 156 | $ dpkg -c pkg/tmux_1.9a-1_amd64.deb 157 | drwxrwxr-x 0/0 0 2014-03-08 01:26 ./ 158 | drwxrwxr-x 0/0 0 2014-03-08 01:26 ./usr/ 159 | drwxrwxr-x 0/0 0 2014-03-08 01:26 ./usr/share/ 160 | drwxrwxr-x 0/0 0 2014-03-08 01:26 ./usr/share/man/ 161 | drwxrwxr-x 0/0 0 2014-03-08 01:26 ./usr/share/man/man1/ 162 | -rw-r--r-- 0/0 93888 2014-03-08 01:26 ./usr/share/man/man1/tmux.1 163 | drwxrwxr-x 0/0 0 2014-03-08 01:26 ./usr/bin/ 164 | -rwxr-xr-x 0/0 491016 2014-03-08 01:26 ./usr/bin/tmux 165 | 166 | .. code-block:: none 167 | 168 | $ dpkg -I pkg/tmux_1.9a-1_amd64.deb 169 | new debian package, version 2.0. 170 | size 235488 bytes: control archive= 437 bytes. 171 | 260 bytes, 12 lines control 172 | 105 bytes, 2 lines md5sums 173 | Package: tmux 174 | Version: 1.9a-1 175 | License: unknown 176 | Vendor: 177 | Architecture: amd64 178 | Maintainer: 179 | Installed-Size: 571 180 | Depends: libevent-2.0-5 181 | Section: optional 182 | Priority: extra 183 | Homepage: http://tmux.sourceforce.net/ 184 | Description: terminal multiplexer 185 | -------------------------------------------------------------------------------- /fpm-cookery.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "fpm/cookery/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "fpm-cookery" 7 | s.version = FPM::Cookery::VERSION 8 | s.authors = ["Bernd Ahlers"] 9 | s.email = ["bernd@tuneafish.de"] 10 | s.homepage = "" 11 | s.summary = %q{A tool for building software packages with fpm.} 12 | s.description = %q{A tool for building software packages with fpm.} 13 | 14 | s.rubyforge_project = "fpm-cookery" 15 | 16 | s.files = `git ls-files`.split("\n") 17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | s.require_paths = ["lib"] 20 | 21 | s.add_development_dependency "rspec", "~> 3.3" 22 | s.add_development_dependency "rake" 23 | s.add_development_dependency "pry" 24 | s.add_development_dependency "simplecov", "~> 0.11" 25 | s.add_runtime_dependency "fpm", "~> 1.1" 26 | s.add_runtime_dependency "facter" 27 | s.add_runtime_dependency "puppet", ">= 3.4", "< 8.0" 28 | s.add_runtime_dependency "addressable", "~> 2.8" 29 | s.add_runtime_dependency "systemu" 30 | s.add_runtime_dependency "json", "~> 2.6" 31 | s.add_runtime_dependency "json_pure", "~> 2.6" 32 | s.add_runtime_dependency "safe_yaml", "~> 1.0.4" 33 | s.add_runtime_dependency "uri-ssh_git", "~> 2.0" 34 | end 35 | -------------------------------------------------------------------------------- /lib/fpm/cookery/book.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | require 'fpm/cookery/path' 3 | 4 | module FPM 5 | module Cookery 6 | class Book 7 | include Singleton 8 | 9 | attr_accessor :filename, :config 10 | 11 | def initialize 12 | @recipe = nil 13 | end 14 | 15 | # Load the given file and instantiate an object. Wrap the class in an 16 | # anonymous module to avoid namespace cluttering. (see Kernel.load) 17 | def load_recipe(filename, config, &callback) 18 | @filename = FPM::Cookery::Path.new(filename).realpath 19 | @config = config 20 | 21 | Kernel.load(@filename.to_path, true) 22 | callback.call(@recipe.new) 23 | end 24 | 25 | def add_recipe_class(klass) 26 | @recipe = klass 27 | end 28 | 29 | # Hijack the recipe singleton to make the +filename+ and +config+ objects 30 | # available when a descendent of BaseRecipe is declared. This makes it 31 | # possible to do Hiera lookups at class definition time. 32 | def inject_class_methods!(klass) 33 | # It's necessary to close over local variables because the receiver 34 | # changes within the scope of +define_method+, so +self.filename+ would 35 | # wrongly refer to +singleton_klass.filename+. 36 | filename = self.filename 37 | config = self.config 38 | 39 | singleton_klass = (class << klass ; self ; end) 40 | 41 | singleton_klass.send(:define_method, :filename) do 42 | filename 43 | end 44 | 45 | singleton_klass.send(:define_method, :config) do 46 | config 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/fpm/cookery/book_hook.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/book' 2 | 3 | module FPM 4 | module Cookery 5 | module BookHook 6 | def self.included(base) 7 | base.extend(ClassMethods) 8 | end 9 | 10 | module ClassMethods 11 | def inherited(klass) 12 | FPM::Cookery::Book.instance.add_recipe_class(klass) 13 | FPM::Cookery::Book.instance.inject_class_methods!(klass) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/fpm/cookery/chain_packager.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/packager' 2 | require 'fpm/cookery/omnibus_packager' 3 | require 'fpm/cookery/exceptions' 4 | 5 | module FPM 6 | module Cookery 7 | class ChainPackager 8 | include FPM::Cookery::Utils 9 | 10 | attr_reader :packager, :recipe, :config 11 | 12 | def initialize(packager, config) 13 | @packager = packager 14 | @recipe = packager.recipe 15 | @config = config 16 | end 17 | 18 | def install_build_deps 19 | recipe.run_lifecycle_hook(:before_dependency_installation) 20 | DependencyInspector.verify!([], recipe.build_depends) 21 | recipe.chain_recipes.each do |name| 22 | recipe_file = build_recipe_file_path(name) 23 | unless File.exist?(recipe_file) 24 | error_message = "Cannot find a recipe for #{name} at #{recipe_file}" 25 | Log.fatal error_message 26 | raise Error::ExecutionFailure, error_message 27 | end 28 | FPM::Cookery::Book.instance.load_recipe(recipe_file, config) do |dep_recipe| 29 | depPackager = FPM::Cookery::Packager.new(dep_recipe, config.to_hash) 30 | depPackager.target = FPM::Cookery::Facts.target.to_s 31 | 32 | #Chain, chain, chain ... 33 | if dep_recipe.omnibus_package == true 34 | FPM::Cookery::OmnibusPackager.new(depPackager, config).install_build_deps 35 | elsif dep_recipe.chain_package == true 36 | FPM::Cookery::ChainPackager.new(depPackager, config).install_build_deps 37 | else 38 | depPackager.install_build_deps 39 | end 40 | end 41 | recipe.run_lifecycle_hook(:after_dependency_installation) 42 | Log.info("Build dependencies installed!") 43 | end 44 | end 45 | 46 | def run 47 | Log.info "Recipe #{recipe.name} is a chain package; looking for child recipes to build" 48 | 49 | recipe.chain_recipes.each do |name| 50 | recipe_file = build_recipe_file_path(name) 51 | 52 | unless File.exist?(recipe_file) 53 | Log.fatal "Cannot find a recipe for #{name} at #{recipe_file}" 54 | exit 1 55 | end 56 | 57 | Log.info "Located recipe at #{recipe_file} for child recipe #{name}; starting build" 58 | 59 | FPM::Cookery::Book.instance.load_recipe(recipe_file, config) do |dep_recipe| 60 | depPackager = FPM::Cookery::Packager.new(dep_recipe, config.to_hash) 61 | depPackager.target = FPM::Cookery::Facts.target.to_s 62 | 63 | #Chain, chain, chain ... 64 | if dep_recipe.omnibus_package == true 65 | FPM::Cookery::OmnibusPackager.new(depPackager, config).run 66 | elsif dep_recipe.chain_package == true 67 | FPM::Cookery::ChainPackager.new(depPackager, config).run 68 | else 69 | depPackager.dispense 70 | end 71 | end 72 | 73 | Log.info "Finished building #{name}, moving on to next recipe" 74 | end 75 | 76 | packager.dispense 77 | end 78 | 79 | private 80 | 81 | def build_recipe_file_path(name) 82 | # Look for recipes in the same dir as the recipe we loaded 83 | File.expand_path(File.dirname(recipe.filename) + "/#{name}.rb") 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/fpm/cookery/cli.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/book_hook' 2 | require 'fpm/cookery/recipe' 3 | require 'fpm/cookery/facts' 4 | require 'fpm/cookery/packager' 5 | require 'fpm/cookery/docker_packager' 6 | require 'fpm/cookery/chain_packager' 7 | require 'fpm/cookery/omnibus_packager' 8 | require 'fpm/cookery/log' 9 | require 'fpm/cookery/log/output/console' 10 | require 'fpm/cookery/log/output/console_color' 11 | require 'fpm/cookery/config' 12 | require 'clamp' 13 | 14 | module FPM 15 | module Cookery 16 | class CLI < Clamp::Command 17 | option ['-c', '--color'], :flag, 'toggle color' 18 | option ['-D', '--debug'], :flag, 'enable debug output' 19 | option ['-t', '--target'], 'TARGET', 'set desired fpm output target (deb, rpm, etc)' 20 | option ['-p', '--platform'], 'PLATFORM', 'set the target platform (centos, ubuntu, debian)' 21 | option ['-q', '--quiet'], :flag, 'Disable verbose output like progress bars' 22 | option ['-V', '--version'], :flag, 'show fpm-cookery and fpm version' 23 | option '--[no-]deps', :flag, 'enable/disable dependency checking', 24 | :attribute_name => 'dependency_check' 25 | option '--tmp-root', 'DIR', 'directory root for temporary files', 26 | :attribute_name => 'tmp_root' 27 | option '--pkg-dir', 'DIR', 'directory for built packages', 28 | :attribute_name => 'pkg_dir' 29 | option '--cache-dir', 'DIR', 'directory for downloaded sources', 30 | :attribute_name => 'cache_dir' 31 | option '--data-dir', 'DIR', 'directory for Hiera data files', 32 | :attribute_name => 'data_dir' 33 | option '--hiera-config', 'FILE', 'Hiera configuration file', 34 | :attribute_name => 'hiera_config' 35 | option '--skip-package', :flag, 'do not call FPM to build the package', 36 | :attribute_name => 'skip_package' 37 | option '--vendor-delimiter', 'DELIMITER', 'vendor delimiter for version string', 38 | :attribute_name => 'vendor_delimiter' 39 | 40 | # Docker related flags 41 | option '--docker', :flag, 'Execute fpm-cookery inside a Docker container', 42 | :attribute_name => 'docker' 43 | option '--docker-bin', 'BINARY', 'Docker binary to use', 44 | :attribute_name => 'docker_bin' 45 | option '--docker-image', 'IMAGE-NAME', 'Docker image to use', 46 | :attribute_name => 'docker_image' 47 | option '--docker-keep-container', :flag, 'Keep Docker container after build (e.g. to debug issues)', 48 | :attribute_name => 'docker_keep_container' 49 | option '--docker-cache', 'PATHS', 'Container paths to cache (can be a comma separated list)', 50 | :attribute_name => 'docker_cache' 51 | option '--dockerfile', 'PATHS', 'The Dockerfile to use for a custom container', 52 | :attribute_name => 'dockerfile' 53 | 54 | class Command < self 55 | def self.add_recipe_parameter! 56 | parameter '[RECIPE]', 'the recipe file', :default => 'recipe.rb' 57 | end 58 | 59 | def recipe_file 60 | file = File.expand_path(recipe) 61 | 62 | # Allow giving the directory containing a recipe.rb 63 | if File.directory?(file) && File.exist?(File.join(file, 'recipe.rb')) 64 | file = File.join(file, 'recipe.rb') 65 | end 66 | 67 | file 68 | end 69 | 70 | def validate 71 | unless File.exist?(recipe_file) 72 | Log.error 'No recipe.rb found in the current directory, abort.' 73 | exit 1 74 | end 75 | 76 | # Override the detected platform. 77 | if platform.nil? 78 | config.platform = FPM::Cookery::Facts.platform 79 | else 80 | FPM::Cookery::Facts.platform = platform 81 | end 82 | 83 | if target.nil? 84 | config.target = FPM::Cookery::Facts.target 85 | else 86 | FPM::Cookery::Facts.target = target 87 | end 88 | end 89 | 90 | def execute 91 | show_version if version? 92 | init_logging 93 | validate 94 | 95 | FPM::Cookery::BaseRecipe.send(:include, FPM::Cookery::BookHook) 96 | 97 | FPM::Cookery::Book.instance.load_recipe(recipe_file, config) do |recipe| 98 | packager = FPM::Cookery::Packager.new(recipe, config.to_hash) 99 | packager.target = FPM::Cookery::Facts.target.to_s 100 | 101 | exec(config, recipe, packager) 102 | end 103 | rescue Error::ExecutionFailure, Error::Misconfiguration 104 | exit 1 105 | end 106 | 107 | def show_version 108 | require 'fpm/version' 109 | require 'fpm/cookery/version' 110 | 111 | puts "fpm-cookery v#{FPM::Cookery::VERSION} (fpm v#{FPM::VERSION})" 112 | exit 0 113 | end 114 | 115 | def config 116 | @config ||= FPM::Cookery::Config.from_cli(self) 117 | end 118 | 119 | def init_logging 120 | FPM::Cookery::Log.enable_debug(config.debug) 121 | 122 | if config.color? 123 | FPM::Cookery::Log.output(FPM::Cookery::Log::Output::ConsoleColor.new) 124 | else 125 | FPM::Cookery::Log.output(FPM::Cookery::Log::Output::Console.new) 126 | end 127 | end 128 | end 129 | 130 | class PackageCmd < Command 131 | add_recipe_parameter! 132 | 133 | def exec(config, recipe, packager) 134 | # Don't try to launch a new container if we are already running inside one 135 | if (config.docker == true or recipe.docker == true) and ENV['FPMC_INSIDE_DOCKER'].nil? 136 | FPM::Cookery::DockerPackager.new(recipe, config).run 137 | elsif recipe.omnibus_package == true 138 | FPM::Cookery::OmnibusPackager.new(packager, config).run 139 | elsif recipe.chain_package == true 140 | FPM::Cookery::ChainPackager.new(packager, config).run 141 | else 142 | packager.dispense 143 | end 144 | end 145 | end 146 | 147 | class CleanCmd < Command 148 | add_recipe_parameter! 149 | 150 | def exec(config, recipe, packager) 151 | packager.cleanup 152 | end 153 | end 154 | 155 | class InstallDepsCmd < Command 156 | add_recipe_parameter! 157 | 158 | def exec(config, recipe, packager) 159 | packager.install_deps 160 | end 161 | end 162 | 163 | class InstallBuildDepsCmd < Command 164 | add_recipe_parameter! 165 | 166 | def exec(config, recipe, packager) 167 | if config.docker == true 168 | # Nothing to do! 169 | elsif recipe.omnibus_package == true 170 | FPM::Cookery::OmnibusPackager.new(packager, config).install_build_deps 171 | elsif recipe.chain_package == true 172 | FPM::Cookery::ChainPackager.new(packager, config).install_build_deps 173 | else 174 | packager.install_build_deps 175 | end 176 | end 177 | end 178 | 179 | class ShowDepsCmd < Command 180 | add_recipe_parameter! 181 | 182 | def exec(config, recipe, packager) 183 | puts recipe.depends_all.join(' ') 184 | end 185 | end 186 | 187 | class InspectCmd < Command 188 | add_recipe_parameter! 189 | 190 | option ['-F', '--format'], 'TEMPLATE', 'ERB template string' 191 | option '--terse', :flag, 'show recipe data in compact form' 192 | 193 | self.description = <-<%= version %>-<%= revision %>.rpm" 200 | foo-1.1-12.rpm 201 | 202 | Without --format, prints a JSON representation of the recipe. 203 | DESCRIPTION 204 | 205 | def exec(config, recipe, packager) 206 | unless format.nil? 207 | puts recipe.template(format) 208 | else 209 | puts terse? ? recipe.to_json : recipe.to_pretty_json 210 | end 211 | end 212 | end 213 | 214 | self.default_subcommand = 'package' 215 | 216 | subcommand 'package', 'builds the package', PackageCmd 217 | subcommand 'clean', 'cleans up', CleanCmd 218 | subcommand 'install-deps', 'installs build and runtime dependencies', InstallDepsCmd 219 | subcommand 'install-build-deps', 'installs build dependencies', InstallBuildDepsCmd 220 | subcommand 'show-deps', 'show build and runtime dependencies', ShowDepsCmd 221 | subcommand 'inspect', 'inspect recipe attributes', InspectCmd 222 | end 223 | end 224 | end 225 | -------------------------------------------------------------------------------- /lib/fpm/cookery/config.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'fpm/cookery/exceptions' 3 | 4 | module FPM 5 | module Cookery 6 | class Config 7 | ATTRIBUTES = [ 8 | :color, :debug, :target, :platform, :maintainer, :vendor, 9 | :skip_package, :keep_destdir, :dependency_check, :quiet, 10 | :tmp_root, :pkg_dir, :cache_dir, :data_dir, :hiera_config, 11 | :vendor_delimiter, :docker, :docker_image, :docker_keep_container, 12 | :docker_cache, :docker_bin, :dockerfile 13 | ].freeze 14 | 15 | DEFAULTS = { 16 | :color => true, 17 | :debug => false, 18 | :dependency_check => true, 19 | :skip_package => false, 20 | :keep_destdir => false, 21 | :quiet => false, 22 | :docker => false, 23 | :docker_image => nil, 24 | :docker_keep_container => false, 25 | :docker_cache => nil, 26 | :docker_bin => 'docker', 27 | :dockerfile => 'Dockerfile' 28 | }.freeze 29 | 30 | def self.load_file(paths) 31 | path = Array(paths).find {|p| File.exist?(p) } 32 | 33 | path ? new(YAML.load_file(path)) : new 34 | end 35 | 36 | def self.from_cli(cli) 37 | new.tap do |config| 38 | ATTRIBUTES.each do |name| 39 | if cli.respond_to?("#{name}?") 40 | value = cli.__send__("#{name}?") 41 | elsif cli.respond_to?(name) 42 | value = cli.__send__(name) 43 | else 44 | value = nil 45 | end 46 | 47 | config.__send__("#{name}=", value) unless value.nil? 48 | end 49 | end 50 | end 51 | 52 | attr_accessor *ATTRIBUTES 53 | 54 | ATTRIBUTES.each do |name| 55 | class_eval %Q( 56 | def #{name}? 57 | !!#{name} 58 | end 59 | ) 60 | end 61 | 62 | def initialize(data = {}) 63 | validate_input(data) 64 | 65 | DEFAULTS.merge(data).each do |key, value| 66 | self.__send__("#{key}=", value) 67 | end 68 | end 69 | 70 | def to_hash 71 | ATTRIBUTES.inject({}) do |hash, attribute| 72 | hash[attribute] = __send__(attribute) 73 | hash 74 | end 75 | end 76 | 77 | private 78 | 79 | def validate_input(data) 80 | errors = [] 81 | 82 | data.keys.each do |key| 83 | unless ATTRIBUTES.include?(key.to_sym) 84 | errors << key 85 | end 86 | end 87 | 88 | unless errors.empty? 89 | e = Error::InvalidConfigKey.new("Invalid config keys: #{errors.join(', ')}") 90 | e.invalid_keys = errors 91 | raise e 92 | end 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/fpm/cookery/dependency_inspector.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/facts' 2 | require 'fpm/cookery/log' 3 | 4 | begin 5 | require 'puppet' 6 | require 'puppet/resource' 7 | require 'puppet/transaction/report' 8 | 9 | # Init Puppet before using it 10 | Puppet.initialize_settings 11 | rescue Exception 12 | end 13 | 14 | module FPM 15 | module Cookery 16 | class DependencyInspector 17 | def self.verify!(depends, build_depends) 18 | unless defined?(Puppet::Resource) 19 | Log.warn "Unable to load Puppet. Automatic dependency installation disabled." 20 | return 21 | end 22 | 23 | Log.info "Verifying build_depends and depends with Puppet" 24 | 25 | missing = missing_packages(build_depends + depends) 26 | 27 | if missing.length == 0 28 | Log.info "All build_depends and depends packages installed" 29 | else 30 | Log.info "Missing/wrong version packages: #{missing.join(', ')}" 31 | if Process.euid != 0 32 | Log.error "Not running as root; please run 'sudo fpm-cook install-deps' to install dependencies." 33 | exit 1 34 | else 35 | Log.info "Running as root; installing missing/wrong version build_depends and depends with Puppet" 36 | missing.each do |package| 37 | self.install_package(package) 38 | end 39 | end 40 | end 41 | 42 | end 43 | 44 | def self.missing_packages(*pkgs) 45 | pkgs.flatten.reject do |package| 46 | self.package_installed?(package) 47 | end 48 | end 49 | 50 | def self.package_installed?(package) 51 | Log.info("Verifying package: #{package}") 52 | return unless self.package_suitable?(package) 53 | 54 | # Use Puppet in noop mode to see if the package exists 55 | Puppet[:noop] = true 56 | resource = Puppet::Resource.new("package", package, :parameters => { 57 | :ensure => "present" 58 | }) 59 | result = Puppet::Resource.indirection.save(resource)[1] 60 | !result.resource_statuses.values.first.out_of_sync 61 | end 62 | 63 | def self.install_package(package) 64 | Log.info("Installing package: #{package}") 65 | return unless self.package_suitable?(package) 66 | 67 | # Use Puppet to install a package 68 | Puppet[:noop] = false 69 | resource = Puppet::Resource.new("package", package, :parameters => { 70 | :ensure => "present" 71 | }) 72 | result = Puppet::Resource.indirection.save(resource)[1] 73 | failed = result.resource_statuses.values.first.failed 74 | if failed 75 | Log.fatal "While processing depends package '#{package}':" 76 | result.logs.each {|log_line| Log.fatal log_line} 77 | exit 1 78 | else 79 | result.logs.each {|log_line| Log.info log_line} 80 | end 81 | end 82 | 83 | def self.package_suitable?(package) 84 | # How can we handle "or" style depends? 85 | if package =~ / \| / 86 | Log.warn "Required package '#{package}' is an 'or' string; not attempting to find/install a package to satisfy" 87 | return false 88 | end 89 | 90 | # We can't handle >=, <<, >>, <=, <, > 91 | if package =~ />=|<<|>>|<=|<|>/ 92 | Log.warn "Required package '#{package}' has a relative version requirement; not attempting to find/install a package to satisfy" 93 | return false 94 | end 95 | true 96 | end 97 | 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/fpm/cookery/docker_packager.rb: -------------------------------------------------------------------------------- 1 | require 'digest/sha1' 2 | require 'fileutils' 3 | require 'pathname' 4 | 5 | require 'fpm/cookery/version' 6 | require 'fpm/cookery/facts' 7 | require 'fpm/cookery/packager' 8 | require 'fpm/cookery/exceptions' 9 | 10 | # Runs the package creation inside a Docker container. 11 | module FPM 12 | module Cookery 13 | class DockerPackager 14 | include FPM::Cookery::Utils 15 | 16 | attr_reader :packager, :recipe, :config 17 | 18 | def initialize(recipe, config) 19 | @recipe = recipe 20 | @config = config 21 | end 22 | 23 | def run 24 | recipe_dir = File.dirname(recipe.filename) 25 | 26 | # The cli settings should have precendence 27 | image_name = config.docker_image || recipe.docker_image 28 | cache_paths = get_cache_paths 29 | docker_bin = config.docker_bin.nil? || config.docker_bin.empty? ? 'docker' : config.docker_bin 30 | dockerfile = get_dockerfile(recipe_dir) 31 | 32 | if File.exist?(dockerfile) 33 | image_name = "local/fpm-cookery/#{File.basename(recipe_dir)}:latest" 34 | Log.info "Building custom Docker image #{image_name} from #{dockerfile}" 35 | build_cmd = [ 36 | config.docker_bin, 'build', 37 | '-f', dockerfile, 38 | '-t', image_name, 39 | '--force-rm', 40 | '.' 41 | ].compact.flatten.join(' ') 42 | sh build_cmd 43 | else 44 | Log.warn "File #{dockerfile} does not exist - not building a custom Docker image" 45 | end 46 | 47 | if image_name.nil? || image_name.empty? 48 | image_name = "fpmcookery/#{FPM::Cookery::Facts.platform}-#{FPM::Cookery::Facts.osrelease}:#{FPM::Cookery::VERSION}" 49 | end 50 | 51 | Log.info "Building #{recipe.name}-#{recipe.version} inside a Docker container using image #{image_name}" 52 | Log.info "Mounting #{recipe_dir} as /recipe" 53 | 54 | cmd = [ 55 | config.docker_bin, "run", "-ti", 56 | "--name", "fpm-cookery-build-#{File.basename(recipe_dir)}", 57 | config.docker_keep_container ? nil : "--rm", 58 | "-e", "FPMC_UID=#{Process.uid}", 59 | "-e", "FPMC_GID=#{Process.gid}", 60 | config.debug ? ["-e", "FPMC_DEBUG=true"] : nil, 61 | build_cache_mounts(cache_paths), 62 | "-v", "#{recipe_dir}:/recipe", 63 | "-w", "/recipe", 64 | image_name, 65 | "fpm-cook", "package", 66 | config.debug ? '-D' : nil, 67 | File.basename(recipe.filename) 68 | ].compact.flatten.join(' ') 69 | 70 | 71 | Log.debug "Running: #{cmd}" 72 | begin 73 | sh cmd 74 | rescue => e 75 | Log.debug e 76 | end 77 | end 78 | 79 | private 80 | 81 | def get_dockerfile(recipe_dir) 82 | path = if config.dockerfile.nil? || config.dockerfile.empty? 83 | Pathname.new(recipe.dockerfile) 84 | else 85 | Pathname.new(config.dockerfile) 86 | end 87 | 88 | path.absolute? ? path.to_s : File.join(recipe_dir, path.to_s) 89 | end 90 | 91 | def get_cache_paths 92 | if config.docker_cache.nil? || config.docker_cache.empty? 93 | recipe.docker_cache 94 | else 95 | config.docker_cache.split(',').select do |path| 96 | !path.empty? 97 | end 98 | end 99 | end 100 | 101 | def build_cache_mounts(cache_paths) 102 | cache_paths.map do |path| 103 | next if path.nil? || path.empty? 104 | "-v #{recipe.cachedir}/docker/#{Digest::SHA256.hexdigest(path)}:#{path}" 105 | end 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/fpm/cookery/environment.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/log' 2 | 3 | module FPM 4 | module Cookery 5 | class Environment < Hash 6 | REMOVALS = %w( 7 | BUNDLE_GEMFILE RUBYOPT BUNDLE_BIN_PATH GEM_HOME GEM_PATH 8 | ).freeze 9 | 10 | # Coerce keys and values to +String+s on creation 11 | def self.[](h = {}) 12 | super(Hash[h.map { |k, v| [k.to_s, v.to_s] }]) 13 | end 14 | 15 | def [](key) 16 | super(key.to_s) 17 | end 18 | 19 | def []=(key, value) 20 | if value.nil? 21 | delete(key.to_s) 22 | else 23 | super(key.to_s, value.to_s) 24 | end 25 | end 26 | 27 | def merge(other = {}) 28 | super(self.class[other]) 29 | end 30 | 31 | def merge!(other = {}) 32 | super(self.class[other]) 33 | end 34 | 35 | def with_clean 36 | saved_env = ENV.to_hash 37 | 38 | REMOVALS.each do |var| 39 | value = ENV.delete(var) 40 | Log.debug("Removing '#{var}' => '#{value}' from environment") 41 | end 42 | 43 | each do |k, v| 44 | Log.debug("Adding '#{k}' => '#{v}' to environment") 45 | ENV[k] = v 46 | end 47 | 48 | yield 49 | ensure 50 | ENV.replace(saved_env.to_hash) 51 | end 52 | 53 | def to_hash 54 | Hash[self] 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/fpm/cookery/exceptions.rb: -------------------------------------------------------------------------------- 1 | module FPM 2 | module Cookery 3 | class Error < StandardError 4 | MethodNotImplemented = Class.new(self) 5 | ExecutionFailure = Class.new(self) 6 | Misconfiguration = Class.new(self) 7 | 8 | class InvalidConfigKey < self 9 | attr_accessor :invalid_keys 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/fpm/cookery/facts.rb: -------------------------------------------------------------------------------- 1 | require 'facter' 2 | 3 | module FPM 4 | module Cookery 5 | class Facts 6 | class << self 7 | def arch 8 | @arch ||= value(:architecture) 9 | end 10 | 11 | def platform 12 | @platform ||= value(:operatingsystem) 13 | end 14 | 15 | def platform=(value) 16 | @platform = value.downcase.to_sym 17 | end 18 | 19 | def osrelease 20 | @osrelease ||= value(:operatingsystemrelease, false) 21 | end 22 | 23 | def lsbcodename 24 | @lsbcodename ||= value(:lsbcodename) || value(:lsbdistcodename) 25 | end 26 | 27 | def osmajorrelease 28 | @osmajorrelease ||= value(:operatingsystemmajrelease, false) 29 | end 30 | 31 | def osfamily 32 | @osfamily ||= value(:osfamily) 33 | end 34 | 35 | def osfamily=(value) 36 | @osfamily = value.downcase.to_sym 37 | end 38 | 39 | def target 40 | @target ||= case osfamily 41 | when :redhat, :suse then :rpm 42 | when :debian then :deb 43 | when :darwin then :osxpkg 44 | when :alpine then :apk 45 | end 46 | end 47 | 48 | def target=(value) 49 | @target = value.to_sym 50 | end 51 | 52 | def reset! 53 | instance_variables.each {|v| instance_variable_set(v, nil) } 54 | end 55 | 56 | private 57 | 58 | def value(fact_name, symbolize = true) 59 | v = Facter.value(fact_name) 60 | return v if v.nil? or !symbolize 61 | return v.downcase.to_sym 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/fpm/cookery/hiera.rb: -------------------------------------------------------------------------------- 1 | require 'hiera' 2 | require 'hiera/version' 3 | require 'fpm/cookery/hiera/defaults' 4 | require 'fpm/cookery/hiera/scope' 5 | require 'fpm/cookery/log/hiera' 6 | 7 | module FPM 8 | module Cookery 9 | # Implement Hiera lookups and interpolation for recipes 10 | module Hiera 11 | # +Hiera+ subclass that wraps a recipe class 12 | class Instance < ::Hiera 13 | include FPM::Cookery::Hiera::Defaults 14 | 15 | attr_reader :recipe, :scope 16 | 17 | # Expects a recipe class and a hash containing one key, +:config+. 18 | def initialize(recipe, options = {}) 19 | @recipe = recipe 20 | @scope = Scope.new(recipe) 21 | 22 | # For some reason, +Hiera+'s constructor expects a hash with just the 23 | # one key. 24 | super({ :config => hiera_config(options) }) 25 | end 26 | 27 | # Provides a default scope, and attempts to look up the key both as a 28 | # string and as a symbol. 29 | def lookup(key, default = nil, scope = self.scope, *rest) 30 | super(key.to_s, default, scope, *rest) 31 | end 32 | alias_method :[], :lookup 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/fpm/cookery/hiera/defaults.rb: -------------------------------------------------------------------------------- 1 | module FPM 2 | module Cookery 3 | module Hiera 4 | module Defaults 5 | module_function 6 | 7 | # This will result in Hiera using the +Hiera::Fpm_cookery_logger+ class 8 | # for logging. 9 | def hiera_logger 10 | 'fpm_cookery' 11 | end 12 | 13 | # Sets the default search hierarchy. +Hiera+ will look for files 14 | # matching +"#{ENV['FPM_ENV']}.yaml"+, etc. 15 | # Note: the including class is expected to define a +recipe+ method 16 | # that responds to +platform+ and +target+. 17 | def hiera_hierarchy 18 | ['common'] 19 | end 20 | 21 | # Default to attempting lookups using both +.yaml+ and +.json+ files. 22 | def hiera_backends 23 | [:yaml, :json] 24 | end 25 | 26 | def hiera_datadir 27 | File.join(Dir.getwd, 'config') 28 | end 29 | 30 | # Sets default values for the +{:config => { ... }}+ options hash 31 | # passed to the +Hiera+ constructor, merging in any options from the 32 | # caller. 33 | def hiera_config(options = {}) 34 | # Hiera accepts a path to a configuration file or a hash; short 35 | # circuit if it's the former. 36 | return options[:config] unless options[:config].is_a?(Hash) 37 | 38 | { 39 | :logger => hiera_logger, 40 | :hierarchy => hiera_hierarchy, 41 | :yaml => { :datadir => hiera_datadir }, 42 | :json => { :datadir => hiera_datadir }, 43 | :backends => hiera_backends 44 | }.merge options[:config] || {} 45 | end 46 | end 47 | end 48 | end 49 | end 50 | 51 | -------------------------------------------------------------------------------- /lib/fpm/cookery/hiera/scope.rb: -------------------------------------------------------------------------------- 1 | require 'facter' 2 | require 'fpm/cookery/facts' 3 | 4 | module FPM 5 | module Cookery 6 | module Hiera 7 | # Wraps a recipe class, adding a +[]+ method so that it can be used as a 8 | # +Hiera+ scope. 9 | class Scope 10 | attr_reader :recipe 11 | 12 | def initialize(recipe) 13 | @recipe = recipe 14 | end 15 | 16 | # Allow Hiera to perform +%{scope("key")}+ interpolations using data 17 | # from the recipe class, +FPM::Cookery::Facts+, and +Facter+. Expects 18 | # +name+ to be a method name or +Facter+ fact name. Returns the result 19 | # of the lookup. Will be +nil+ if lookup failed to fetch a result. 20 | def [](name) 21 | [recipe, FPM::Cookery::Facts].each do |source| 22 | if source.respond_to?(name) 23 | return source.send(name) 24 | end 25 | end 26 | 27 | # As a backup, try to retrieve it from +Facter+. 28 | unless (result = Facter[name]).nil? 29 | result.value 30 | end 31 | end 32 | 33 | # Newer versions of Hiera requires also +#include?+ method for context 34 | def include?(name) 35 | [recipe, FPM::Cookery::Facts].each do |source| 36 | return true if source.respond_to?(name) 37 | end 38 | 39 | # If not found, check in +Facter+. 40 | ! Facter[name].nil? 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/fpm/cookery/inheritable_attr.rb: -------------------------------------------------------------------------------- 1 | # Credit due to Nick Sutterer, whose +Uber::InheritableAttr+ provides much 2 | # of the code that appears with modification here. 3 | # @see https://github.com/apotonick/uber 4 | # @see https://raw.githubusercontent.com/apotonick/uber/master/LICENSE 5 | 6 | # +Uber+'s license reproduced here: 7 | # # Copyright (c) 2012 Nick Sutterer 8 | # 9 | # MIT License 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining 12 | # a copy of this software and associated documentation files (the 13 | # "Software"), to deal in the Software without restriction, including 14 | # without limitation the rights to use, copy, modify, merge, publish, 15 | # distribute, sublicense, and/or sell copies of the Software, and to 16 | # permit persons to whom the Software is furnished to do so, subject to 17 | # the following conditions: 18 | # 19 | # The above copyright notice and this permission notice shall be 20 | # included in all copies or substantial portions of the Software. 21 | # 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | require 'fpm/cookery/path' 31 | 32 | module FPM 33 | module Cookery 34 | # Provides inheritance of class-level attributes. Attributes are cloned 35 | # from the superclass, except for non-clonable attributes, which are 36 | # assigned directly. 37 | # 38 | # This module will automatically define class methods for keeping track of 39 | # inheritable attributes, as follows: 40 | # +attr_rw+ => +klass.scalar_attrs+ 41 | # +attr_rw_list+ => +klass.list_attrs+ 42 | # +attr_rw_hash+ => +klass.hash_attrs+ 43 | # +attr_rw_path+ => +klass.path_attrs+ 44 | # 45 | # @example 46 | # class Foo 47 | # extend FPM::Cookery::InheritableAttr 48 | # 49 | # attr_rw :name, :rank 50 | # attr_rw_list :favorite_things 51 | # attr_rw_hash :meta 52 | # attr_rw_path :home 53 | # 54 | # name("J.J. Jingleheimer-Schmidt") 55 | # favorite_things("brown paper packages", "raindrops on roses") 56 | # meta[:data] = "la la la la la la la la" 57 | # home = "/home/jjschmidt" 58 | # end 59 | # 60 | # Bar = Class.new(Foo) 61 | # Bar.home #=> # 62 | # Bar.home = "/home/free" #=> # 63 | # Foo.home #=> # 64 | # 65 | # Foo.scalar_attrs #=> [:name, :rank] 66 | # Foo.list_attrs #=> [:favorite_things] 67 | # Foo.hash_attrs #=> [:meta] 68 | # Foo.path_attrs #=> [:home] 69 | module InheritableAttr 70 | # Adds a list of attributes keyed to the +type+ key of an internal hash 71 | # tracking class attributes. Also defines the method +"#{type}_attrs"+, 72 | # which will return the list of attribute names keyed to +type+. 73 | # @example 74 | def register_attrs(type, *attrs) 75 | (attr_registry[type] ||= []).concat(attrs) 76 | 77 | unless respond_to?(type_reader = :"#{type}_attrs") 78 | (class << self ; self ; end).send(:define_method, type_reader) do 79 | attr_registry.fetch(type, []).dup 80 | end 81 | end 82 | end 83 | 84 | # Create `scalar' (i.e. non-collection) attributes. 85 | def attr_rw(*attrs) 86 | attrs.each do |attr| 87 | class_eval %Q{ 88 | def self.#{attr}(value = nil) 89 | if value.nil? 90 | return @#{attr} if instance_variable_defined?(:@#{attr}) 91 | @#{attr} = InheritableAttr.inherit_for(self, :#{attr}) 92 | else 93 | @#{attr} = value 94 | end 95 | end 96 | 97 | def #{attr} 98 | self.class.#{attr} 99 | end 100 | } 101 | end 102 | 103 | register_attrs(:scalar, *attrs) 104 | end 105 | 106 | # Create list-style attributes, backed by +Array+s. +nil+ entries will be 107 | # filtered, non-unique entries will be culled to one instance only, and 108 | # the list will be flattened. 109 | def attr_rw_list(*attrs) 110 | attrs.each do |attr| 111 | class_eval %Q{ 112 | def self.#{attr}(*list) 113 | unless instance_variable_defined?(:@#{attr}) 114 | @#{attr} = InheritableAttr.inherit_for(self, :#{attr}) 115 | end 116 | 117 | @#{attr} ||= [] 118 | 119 | unless list.empty? 120 | @#{attr} << list 121 | @#{attr}.flatten! 122 | @#{attr}.uniq! 123 | end 124 | 125 | @#{attr} 126 | end 127 | 128 | def #{attr} 129 | self.class.#{attr} 130 | end 131 | } 132 | end 133 | 134 | register_attrs(:list, *attrs) 135 | end 136 | 137 | # Create +Hash+-style attributes. Supports both hash and argument 138 | # assignment: 139 | # attr_method[:attr1] = xxxx 140 | # attr_method :xxxx=>1, :yyyy=>2 141 | def attr_rw_hash(*attrs) 142 | attrs.each do |attr| 143 | class_eval %Q{ 144 | def self.#{attr}(args = {}) 145 | unless instance_variable_defined?(:@#{attr}) 146 | @#{attr} = InheritableAttr.inherit_for(self, :#{attr}) 147 | end 148 | 149 | (@#{attr} ||= {}).merge!(args) 150 | end 151 | 152 | def #{attr} 153 | self.class.#{attr} 154 | end 155 | } 156 | end 157 | 158 | register_attrs(:hash, *attrs) 159 | end 160 | 161 | # Create methods for attributes representing paths. Arguments to 162 | # writer methods will be converted to +FPM::Cookery::Path+ objects. 163 | def attr_rw_path(*attrs) 164 | attrs.each do |attr| 165 | class_eval %Q{ 166 | def self.#{attr} 167 | return @#{attr} if instance_variable_defined?(:@#{attr}) 168 | @#{attr} = InheritableAttr.inherit_for(self, :#{attr}) 169 | end 170 | 171 | def self.#{attr}=(value) 172 | @#{attr} = FPM::Cookery::Path.new(value) 173 | end 174 | 175 | def #{attr}=(value) 176 | self.class.#{attr} = value 177 | end 178 | 179 | def #{attr}(path = nil) 180 | self.class.#{attr}(path) 181 | end 182 | } 183 | end 184 | 185 | register_attrs(:path, *attrs) 186 | end 187 | 188 | class << self 189 | def inherit_for(klass, name) 190 | return unless klass.superclass.respond_to?(name) 191 | DeepClone.(klass.superclass.send(name)) 192 | end 193 | 194 | def extended(klass) 195 | # Inject the +attr_registry+ attribute into any class that extends 196 | # this module. 197 | klass.attr_rw_hash(:attr_registry) 198 | end 199 | end 200 | 201 | # Provides deep cloning of data structures. Used in 202 | # +InheritableAttr.inherit_for+ to, among other things, avoid 203 | # accidentally propagating to the superclass changes made to 204 | # substructures of an attribute (such as arrays contained in a hash 205 | # attribute). 206 | class DeepClone 207 | def self.call(obj) 208 | case obj 209 | when Hash 210 | obj.class[obj.map { |k, v| [DeepClone.(k), DeepClone.(v)] }] 211 | when Array 212 | obj.map { |v| DeepClone.(v) } 213 | when Symbol, TrueClass, FalseClass, NilClass, Integer, Float 214 | obj 215 | else 216 | obj.respond_to?(:clone) ? obj.clone : obj 217 | end 218 | end 219 | end 220 | end 221 | end 222 | end 223 | -------------------------------------------------------------------------------- /lib/fpm/cookery/lifecycle_hooks.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/log' 2 | 3 | module FPM 4 | module Cookery 5 | module LifecycleHooks 6 | def run_lifecycle_hook(hook_name, *args) 7 | Log.debug("Run lifecycle hook: #{hook_name} (args: #{args.inspect})") 8 | self.__send__(hook_name, *args) 9 | end 10 | 11 | def before_dependency_installation 12 | end 13 | 14 | def after_dependency_installation 15 | end 16 | 17 | def before_source_download 18 | end 19 | 20 | def after_source_download 21 | end 22 | 23 | def before_source_extraction 24 | end 25 | 26 | # Gets a FPM::Cookery::Path object pointing to the extracted source as argument. 27 | def after_source_extraction(extracted_source) 28 | end 29 | 30 | def before_build 31 | end 32 | 33 | def after_build 34 | end 35 | 36 | def before_install 37 | end 38 | 39 | def after_install 40 | end 41 | 42 | # Gets a FPM::Package object as argument. 43 | def before_package_create(package) 44 | end 45 | 46 | # Gets a FPM::Package object as argument. 47 | def after_package_create(package) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/fpm/cookery/log.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/log/output/null' 2 | require 'cabin' # Via fpm. 3 | require 'json' 4 | 5 | module FPM 6 | module Cookery 7 | module Log 8 | @debug = false 9 | @output = FPM::Cookery::Log::Output::Null.new 10 | 11 | @channel = ::Cabin::Channel.get 12 | @channel.subscribe(self) 13 | @channel.level = :info 14 | 15 | class << self 16 | def enable_debug(value = true) 17 | @debug = value 18 | end 19 | 20 | def output(out) 21 | @output = out 22 | end 23 | 24 | def debug(message) 25 | @output.debug(message) if @debug 26 | end 27 | 28 | def info(message) 29 | @output.info(message) 30 | end 31 | 32 | def warn(message) 33 | @output.warn(message) 34 | end 35 | 36 | def error(message) 37 | @output.error(message) 38 | end 39 | 40 | def fatal(message) 41 | @output.fatal(message) 42 | end 43 | 44 | def puts(message) 45 | @output.puts(message) 46 | end 47 | 48 | def deprecated(message) 49 | warn("[DEPRECATED] #{message}") 50 | end 51 | 52 | def <<(event) 53 | level = event.fetch(:level, :info).downcase.to_sym 54 | 55 | event.delete(:level) 56 | 57 | data = event.clone 58 | 59 | data.delete(:message) 60 | data.delete(:timestamp) 61 | 62 | send(level, "[FPM] #{event[:message]} #{data.to_json}") 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/fpm/cookery/log/color.rb: -------------------------------------------------------------------------------- 1 | module FPM 2 | module Cookery 3 | module Log 4 | class Color 5 | CODES = { 6 | :black => 30, 7 | :blue => 34, 8 | :cyan => 36, 9 | :green => 32, 10 | :magenta => 35, 11 | :red => 31, 12 | :reset => 0, 13 | :white => 39, 14 | :yellow => 33, 15 | } 16 | 17 | class << self 18 | def color(name, type = nil) 19 | tint = CODES[name] || 0 20 | 21 | case type 22 | when :bold 23 | escape("1;#{tint}") 24 | when :underline 25 | escape("4;#{tint}") 26 | else 27 | escape(tint) 28 | end 29 | end 30 | 31 | def colorize(string, tint, type = nil) 32 | "#{color(tint, type)}#{string}#{color(:reset)}" 33 | end 34 | 35 | def black(string, type = nil) colorize(string, :black, type) end 36 | def blue(string, type = nil) colorize(string, :blue, type) end 37 | def cyan(string, type = nil) colorize(string, :cyan, type) end 38 | def green(string, type = nil) colorize(string, :green, type) end 39 | def magenta(string, type = nil) colorize(string, :magenta, type) end 40 | def red(string, type = nil) colorize(string, :red, type) end 41 | def reset(string, type = nil) colorize(string, :reset, type) end 42 | def white(string, type = nil) colorize(string, :white, type) end 43 | def yellow(string, type = nil) colorize(string, :yellow, type) end 44 | 45 | private 46 | def escape(num) 47 | "\033[#{num}m" if $stdout.tty? 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/fpm/cookery/log/hiera.rb: -------------------------------------------------------------------------------- 1 | require 'hiera' 2 | require 'fpm/cookery/log' 3 | 4 | module FPM 5 | module Cookery 6 | module Log 7 | module Hiera 8 | extend SingleForwardable 9 | 10 | # These are the methods that Hiera requires to be defined 11 | def_delegators FPM::Cookery::Log, :warn, :debug 12 | 13 | module_function 14 | 15 | def suitable? 16 | defined?(::FPM::Cookery) == "constant" 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/fpm/cookery/log/output/console.rb: -------------------------------------------------------------------------------- 1 | 2 | module FPM 3 | module Cookery 4 | module Log 5 | module Output 6 | class Console 7 | def debug(message) 8 | STDOUT.puts "DEBUG: #{message}" 9 | end 10 | 11 | def info(message) 12 | STDOUT.puts "===> #{message}" 13 | end 14 | 15 | def puts(message) 16 | STDOUT.puts "#{message}" 17 | end 18 | 19 | def warn(message) 20 | STDERR.puts "WARNING: #{message}" 21 | end 22 | 23 | def error(message) 24 | STDERR.puts "ERROR: #{message}" 25 | end 26 | 27 | def fatal(message) 28 | STDERR.puts "FATAL: #{message}" 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/fpm/cookery/log/output/console_color.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/log/color' 2 | 3 | module FPM 4 | module Cookery 5 | module Log 6 | module Output 7 | class ConsoleColor 8 | def debug(message) 9 | puts "#{Color.cyan('DEBUG:')} #{message}" 10 | end 11 | 12 | def info(message) 13 | puts "#{Color.blue('===>')} #{message}" 14 | end 15 | 16 | def puts(message) 17 | Kernel.puts "#{message}" 18 | end 19 | 20 | def warn(message) 21 | STDERR.puts "#{Color.yellow('WARNING:')} #{message}" 22 | end 23 | 24 | def error(message) 25 | STDERR.puts "#{Color.red('ERROR:')} #{message}" 26 | end 27 | 28 | def fatal(message) 29 | STDERR.puts "#{Color.red('FATAL:', :bold)} #{message}" 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/fpm/cookery/log/output/null.rb: -------------------------------------------------------------------------------- 1 | module FPM 2 | module Cookery 3 | module Log 4 | module Output 5 | class Null 6 | def method_missing(*args) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/fpm/cookery/omnibus_packager.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/packager' 2 | require 'fpm/cookery/facts' 3 | require 'fpm/cookery/exceptions' 4 | 5 | module FPM 6 | module Cookery 7 | class OmnibusPackager 8 | include FPM::Cookery::Utils 9 | 10 | attr_reader :packager, :recipe, :config 11 | 12 | def initialize(packager, config) 13 | @packager = packager 14 | @config = config 15 | @recipe = packager.recipe 16 | @depends = [] 17 | end 18 | 19 | def load_omnibus_recipes(_recipe) 20 | dep_recipes = [] 21 | _recipe.omnibus_recipes.each do |name| 22 | recipe_file = build_recipe_file_path(name) 23 | Log.info "Loading dependency recipe #{name} from #{recipe_file}" 24 | unless File.exist?(recipe_file) 25 | error_message = "Cannot find a recipe for #{name} at #{recipe_file}" 26 | Log.fatal error_message 27 | raise Error::ExecutionFailure, error_message 28 | end 29 | 30 | FPM::Cookery::Book.instance.load_recipe(recipe_file, config) do |dep_recipe| 31 | dep_recipe.destdir = "#{recipe.omnibus_dir}/embedded" if recipe.omnibus_dir 32 | dep_recipe.omnibus_installing = true if recipe.omnibus_dir 33 | if dep_recipe.omnibus_recipes.any? 34 | dep_recipes += load_omnibus_recipes(dep_recipe) 35 | end 36 | dep_recipes << dep_recipe 37 | end 38 | end 39 | dep_recipes 40 | end 41 | 42 | 43 | def install_build_deps 44 | build_deps = load_omnibus_recipes(recipe).map(&:build_depends).flatten.uniq 45 | recipe.run_lifecycle_hook(:before_dependency_installation) 46 | DependencyInspector.verify!([], build_deps) 47 | recipe.run_lifecycle_hook(:after_dependency_installation) 48 | Log.info("Build dependencies installed!") 49 | end 50 | 51 | def run 52 | # Omnibus packages are many builds in one package; e.g. Ruby + Puppet together. 53 | Log.info "Recipe #{recipe.name} is an Omnibus package; looking for child recipes to build" 54 | 55 | dep_recipes = load_omnibus_recipes(recipe) 56 | dep_recipes.uniq.each do |dep_recipe| 57 | pkg = FPM::Cookery::Packager.new(dep_recipe, :skip_package => true, 58 | :keep_destdir => true, :dependency_check => config.dependency_check ) 59 | pkg.target = FPM::Cookery::Facts.target.to_s 60 | 61 | Log.info "Located recipe for child recipe #{dep_recipe.name}; starting build" 62 | pkg.dispense 63 | 64 | @depends += dep_recipe.depends 65 | Log.info "Finished building #{dep_recipe.name}, moving on to next recipe" 66 | end 67 | 68 | # Now all child recipes are built; set depends to combined set of dependencies 69 | recipe.class.depends(@depends.flatten.uniq) 70 | Log.info "Combined dependencies: #{recipe.depends.join(', ')}" 71 | 72 | recipe.destdir = recipe.omnibus_dir if recipe.omnibus_dir 73 | 74 | if recipe.omnibus_additional_paths 75 | packager.config[:input] = [ recipe.destdir ] + recipe.omnibus_additional_paths 76 | else 77 | packager.config[:input] = recipe.destdir 78 | end 79 | 80 | packager.config[:keep_destdir] = true 81 | 82 | packager.dispense 83 | end 84 | 85 | private 86 | 87 | def build_recipe_file_path(name) 88 | # Look for recipes in the same dir as the recipe we loaded 89 | File.expand_path(File.dirname(recipe.filename) + "/#{name}.rb") 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/cpan.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/package/cpan' 2 | require 'fpm/cookery/package/package' 3 | 4 | module FPM 5 | module Cookery 6 | module Package 7 | class CPAN < FPM::Cookery::Package::Package 8 | def fpm_object 9 | FPM::Package::CPAN.new 10 | end 11 | 12 | def package_setup 13 | # Other attributes may be passed via fpm_attributes 14 | fpm.version = recipe.version 15 | end 16 | 17 | def package_input 18 | fpm.input(recipe.name) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/dir.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/package/dir' 2 | require 'fpm/cookery/package/package' 3 | 4 | module FPM 5 | module Cookery 6 | module Package 7 | class Dir < FPM::Cookery::Package::Package 8 | def fpm_object 9 | FPM::Package::Dir.new 10 | end 11 | 12 | def package_setup 13 | fpm.attributes[:prefix] = '/' 14 | fpm.attributes[:chdir] = recipe.destdir.to_s 15 | end 16 | 17 | def package_input 18 | inputs = config.fetch(:input, nil) || '.' 19 | 20 | Array(inputs).each do |path| 21 | fpm.input(path.to_s) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/gem.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/package/gem' 2 | require 'fpm/cookery/package/package' 3 | require 'fpm/cookery/utils' 4 | 5 | module FPM 6 | module Cookery 7 | module Package 8 | class Gem < FPM::Cookery::Package::Package 9 | def fpm_object 10 | FPM::Package::Gem.new 11 | end 12 | 13 | def package_setup 14 | fpm.version = recipe.version 15 | 16 | fpm.attributes[:gem_fix_name?] = true 17 | fpm.attributes[:gem_fix_dependencies?] = true 18 | end 19 | 20 | def package_input 21 | recipe.environment.with_clean do 22 | fpm.input(recipe.name) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/maintainer.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/shellout' 2 | require 'socket' 3 | 4 | module FPM 5 | module Cookery 6 | module Package 7 | class Maintainer < Struct.new(:recipe, :config) 8 | def maintainer 9 | config_maintainer || recipe_maintainer || git_maintainer || default_maintainer 10 | end 11 | 12 | def to_s 13 | maintainer 14 | end 15 | alias_method :to_str, :to_s 16 | 17 | private 18 | 19 | def config_maintainer 20 | config[:maintainer] 21 | end 22 | 23 | def recipe_maintainer 24 | recipe.maintainer 25 | end 26 | 27 | def git_maintainer 28 | username = git_config('user.name') 29 | useremail = git_config('user.email') 30 | 31 | username && useremail ? "#{username} <#{useremail}>" : nil 32 | rescue 33 | # This might fail if git is not installed or if the current 34 | # dir is not a git repository. 35 | nil 36 | end 37 | 38 | def git_config(key) 39 | FPM::Cookery::Shellout.git_config_get(key) 40 | end 41 | 42 | def default_maintainer 43 | "<#{ENV['USER']}@#{Socket.gethostname}>" 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/npm.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/package/npm' 2 | require 'fpm/cookery/package/package' 3 | 4 | module FPM 5 | module Cookery 6 | module Package 7 | class NPM < FPM::Cookery::Package::Package 8 | def fpm_object 9 | FPM::Package::NPM.new 10 | end 11 | 12 | def package_setup 13 | fpm.version = recipe.version 14 | end 15 | 16 | def package_input 17 | fpm.input(recipe.name) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/package.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/exceptions' 2 | 3 | module FPM 4 | module Cookery 5 | module Package 6 | class Package 7 | attr_reader :recipe, :config, :fpm 8 | 9 | def initialize(recipe, config = {}) 10 | @recipe = recipe 11 | @config = config 12 | @fpm = fpm_object 13 | 14 | @fpm.name = recipe.name 15 | @fpm.url = recipe.homepage 16 | @fpm.category = recipe.section || 'optional' 17 | @fpm.description = recipe.description.strip if recipe.description 18 | @fpm.architecture = recipe.arch.to_s if recipe.arch 19 | 20 | @fpm.dependencies += recipe.depends 21 | @fpm.conflicts += recipe.conflicts 22 | @fpm.provides += recipe.provides 23 | @fpm.replaces += recipe.replaces 24 | @fpm.config_files += recipe.config_files 25 | @fpm.directories += recipe.directories 26 | 27 | @fpm.attributes[:deb_compression] = 'gz' 28 | @fpm.attributes[:deb_user] = 'root' 29 | @fpm.attributes[:deb_group] = 'root' 30 | @fpm.attributes[:rpm_compression] = 'gzip' 31 | @fpm.attributes[:rpm_digest] = 'md5' 32 | @fpm.attributes[:rpm_user] = 'root' 33 | @fpm.attributes[:rpm_group] = 'root' 34 | @fpm.attributes[:rpm_defattrfile] = '-' 35 | @fpm.attributes[:rpm_defattrdir] = '-' 36 | @fpm.attributes[:rpm_dist] = recipe.rpm_dist.to_s if recipe.rpm_dist 37 | @fpm.attributes[:excludes] = recipe.exclude 38 | 39 | # Package type specific code should be called in package_setup. 40 | package_setup 41 | 42 | # combine recipe specific fpm attributes. here allows to 43 | # overwrite the values from package_setup(). 44 | @fpm.attributes.merge!(recipe.fpm_attributes) 45 | 46 | # The input for the FPM package will be set here. 47 | package_input 48 | 49 | # The call to input() overwrites the license and vendor attributes. 50 | # XXX Needs to be fixed in fpm/package/dir.rb. 51 | fpm.license = recipe.license if recipe.license 52 | end 53 | 54 | def fpm_object 55 | # Has to be overwritten in a subclass. 56 | raise Error::MethodNotImplemented, "The #fpm_object method has not been implemented in #{self.class}" 57 | end 58 | 59 | def package_setup 60 | # Can be overwritten in a subclass. 61 | end 62 | 63 | def package_input 64 | # Has to be overwritten in a subclass. 65 | raise Error::MethodNotImplemented, "The #package_input method has not been implemented in #{self.class}" 66 | end 67 | 68 | def convert(output_class) 69 | fpm.convert(output_class) 70 | end 71 | 72 | def cleanup 73 | fpm.cleanup 74 | end 75 | 76 | def add_script(type, content) 77 | case type.to_sym 78 | when :pre_install 79 | fpm.scripts[:before_install] = content 80 | when :post_install 81 | fpm.scripts[:after_install] = content 82 | when :pre_uninstall 83 | fpm.scripts[:before_remove] = content 84 | when :post_uninstall 85 | fpm.scripts[:after_remove] = content 86 | end 87 | end 88 | 89 | # XXX should go away and set in initializer 90 | def version=(version) 91 | fpm.version = version.version 92 | fpm.iteration = version.revision 93 | fpm.vendor = version.vendor 94 | end 95 | 96 | def maintainer=(value) 97 | fpm.maintainer = value 98 | end 99 | 100 | def vendor=(value) 101 | fpm.vendor = value 102 | end 103 | 104 | def epoch=(value) 105 | fpm.epoch = value 106 | end 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/pear.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/package/pear' 2 | require 'fpm/cookery/package/package' 3 | 4 | module FPM 5 | module Cookery 6 | module Package 7 | class PEAR < FPM::Cookery::Package::Package 8 | def fpm_object 9 | FPM::Package::PEAR.new 10 | end 11 | 12 | def package_setup 13 | fpm.version = recipe.version 14 | # Other attributes may be passed via fpm_attributes 15 | fpm.attributes[:pear_package_name_prefix] = recipe.pear_package_name_prefix unless recipe.pear_package_name_prefix.nil? 16 | fpm.attributes[:pear_channel] = recipe.pear_channel unless recipe.pear_channel.nil? 17 | fpm.attributes[:pear_php_dir] = recipe.pear_php_dir unless recipe.pear_php_dir.nil? 18 | end 19 | 20 | def package_input 21 | fpm.input(recipe.name) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/python.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/package/python' 2 | require 'fpm/cookery/package/package' 3 | 4 | module FPM 5 | module Cookery 6 | module Package 7 | class Python < FPM::Cookery::Package::Package 8 | def fpm_object 9 | FPM::Package::Python.new 10 | end 11 | 12 | def package_setup 13 | fpm.version = recipe.version 14 | 15 | fpm.attributes[:python_fix_name?] = true 16 | fpm.attributes[:python_fix_dependencies?] = true 17 | end 18 | 19 | def package_input 20 | fpm.input(recipe.name) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/version.rb: -------------------------------------------------------------------------------- 1 | module FPM 2 | module Cookery 3 | module Package 4 | # See the following URLs for package naming conventions. 5 | # 6 | # * https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version 7 | # * https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines#Package_Versioning 8 | class Version 9 | REVISION_DELIMITER = { 10 | :default => '-' 11 | } 12 | 13 | VENDOR_DELIMITER = { 14 | :deb => '+', 15 | :rpm => '.', 16 | :default => '-' 17 | } 18 | 19 | # fpm sets the default version in FPM::Command; since we bypass the 20 | # command line interface, we need to set our own value. 21 | DEFAULT_VERSION = '1.0' 22 | 23 | attr_reader :epoch, :revision 24 | 25 | def initialize(recipe, target, config) 26 | @recipe = recipe 27 | @target = target 28 | @config = config 29 | @revision = recipe.revision 30 | @version, @epoch = split_version(@recipe.version) 31 | end 32 | 33 | def vendor 34 | @config[:vendor] || @recipe.vendor 35 | end 36 | 37 | def version 38 | @version ||= DEFAULT_VERSION 39 | end 40 | 41 | def revision_delimiter 42 | REVISION_DELIMITER[:default] 43 | end 44 | 45 | def vendor_delimiter 46 | return @config[:vendor_delimiter] if @config[:vendor_delimiter] 47 | VENDOR_DELIMITER[@target.to_sym] || VENDOR_DELIMITER[:default] 48 | end 49 | 50 | def to_s 51 | s_revision = revision ? "#{revision_delimiter}#{revision}" : "" 52 | s_vendor = vendor ? "#{vendor_delimiter}#{vendor}" : "" 53 | 54 | "#{version}#{s_revision}#{s_vendor}" 55 | end 56 | alias_method :to_str, :to_s 57 | 58 | private 59 | 60 | def split_version(version) 61 | (version || '').split(':', 2).reject(&:empty?).reverse 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/fpm/cookery/package/virtualenv.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/package/virtualenv' 2 | require 'fpm/cookery/package/package' 3 | 4 | module FPM 5 | module Cookery 6 | module Package 7 | class Virtualenv < FPM::Cookery::Package::Package 8 | def fpm_object 9 | FPM::Package::Virtualenv.new 10 | end 11 | 12 | def package_setup 13 | fpm.version = recipe.version 14 | fpm.attributes[:virtualenv_pypi] = recipe.virtualenv_pypi unless recipe.virtualenv_pypi.nil? 15 | fpm.attributes[:virtualenv_pypi_extra_index_urls] = recipe.virtualenv_pypi_extra_index_urls unless recipe.virtualenv_pypi_extra_index_urls.nil? 16 | fpm.attributes[:virtualenv_install_location] = recipe.virtualenv_install_location unless recipe.virtualenv_install_location.nil? 17 | fpm.attributes[:virtualenv_fix_name?] = false 18 | fpm.attributes[:virtualenv_package_name_prefix] = recipe.virtualenv_package_name_prefix unless recipe.virtualenv_package_name_prefix.nil? 19 | fpm.attributes[:virtualenv_other_files_dir] = recipe.virtualenv_other_files_dir unless recipe.virtualenv_other_files_dir.nil? 20 | end 21 | 22 | def package_input 23 | fpm.input(recipe.name) 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/fpm/cookery/path.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'fileutils' 3 | 4 | module FPM 5 | module Cookery 6 | class Path < Pathname 7 | if '1.9' <= RUBY_VERSION 8 | alias_method :to_str, :to_s 9 | end 10 | 11 | def self.pwd(path = nil) 12 | new(Dir.pwd)/path 13 | end 14 | 15 | def +(other) 16 | other = Path.new(other) unless Path === other 17 | Path.new(plus(@path, other.to_s)) 18 | end 19 | 20 | def /(path) 21 | self + (path || '').to_s.gsub(%r{^/}, '') 22 | end 23 | 24 | def =~(regex) 25 | @path =~ regex 26 | end 27 | 28 | def mkdir 29 | # FileUtils.mkdir_p in Ruby 1.8.7 does not return an array. 30 | Array(FileUtils.mkdir_p(self.to_s)) 31 | end 32 | 33 | def install(src, new_basename = nil) 34 | case src 35 | when Array 36 | src.collect {|src| install_p(src) } 37 | when Hash 38 | src.collect {|src, new_basename| install_p(src, new_basename) } 39 | else 40 | install_p(src, new_basename) 41 | end 42 | end 43 | 44 | # @deprecated Will be made private in the future. 45 | # @private 46 | def install_p(src, new_basename = nil) 47 | if new_basename 48 | new_basename = File.basename(new_basename) # rationale: see Pathname.+ 49 | dst = self/new_basename 50 | return_value = Path.new(dst) 51 | else 52 | dst = self 53 | return_value = self/File.basename(src) 54 | end 55 | 56 | src = src.to_s 57 | dst = dst.to_s 58 | 59 | # if it's a symlink, don't resolve it to a file because if we are moving 60 | # files one by one, it's likely we will break the symlink by moving what 61 | # it points to before we move it 62 | # and also broken symlinks are not the end of the world 63 | raise "#{src} does not exist" unless File.symlink? src or File.exist? src 64 | 65 | mkpath 66 | 67 | # We used to use :preserve => true here, but that broke package 68 | # building when the file tree contains broken symlinks. 69 | # :dereference_root => false, results in symlinks being copied instead 70 | # of dereferencing them first. Copying a symlink that points to a 71 | # non-existent file is not an error. 72 | FileUtils.cp_r src, dst, :dereference_root => false 73 | 74 | # if File.symlink? src 75 | # # we use the BSD mv command because FileUtils copies the target and 76 | # # not the link! I'm beginning to wish I'd used Python quite honestly! 77 | # raise unless Kernel.system 'mv', src, dst 78 | # else 79 | # # we mv when possible as it is faster and you should only be using 80 | # # this function when installing from the temporary build directory 81 | # FileUtils.mv src, dst 82 | # end 83 | 84 | return return_value 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/fpm/cookery/path_helper.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/path' 2 | 3 | module FPM 4 | module Cookery 5 | module PathHelper 6 | attr_accessor :installing, :omnibus_installing 7 | 8 | def installing? 9 | installing 10 | end 11 | 12 | def omnibus_installing? 13 | omnibus_installing 14 | end 15 | 16 | # Most of the path helper stuff comes from brew2deb and homebrew. 17 | def prefix(path = nil) 18 | current_pathname_for(default_prefix || '/usr')/path 19 | end 20 | 21 | def etc(path = nil) 22 | current_pathname_for('etc')/path 23 | end 24 | 25 | def opt(path = nil) 26 | current_pathname_for('opt')/path 27 | end 28 | 29 | def var(path = nil) 30 | current_pathname_for('var')/path 31 | end 32 | 33 | def root(path = nil) 34 | current_pathname_for(nil)/path 35 | end 36 | alias_method :root_prefix, :root 37 | 38 | def bin(path = nil) prefix/'bin'/path end 39 | def doc(path = nil) prefix/'share/doc'/path end 40 | def include(path = nil) prefix/'include'/path end 41 | def info(path = nil) prefix/'share/info'/path end 42 | def lib(path = nil) prefix/'lib'/path end 43 | def libexec(path = nil) prefix/'libexec'/path end 44 | def man(path = nil) prefix/'share/man'/path end 45 | def man1(path = nil) man/'man1'/path end 46 | def man2(path = nil) man/'man2'/path end 47 | def man3(path = nil) man/'man3'/path end 48 | def man4(path = nil) man/'man4'/path end 49 | def man5(path = nil) man/'man5'/path end 50 | def man6(path = nil) man/'man6'/path end 51 | def man7(path = nil) man/'man7'/path end 52 | def man8(path = nil) man/'man8'/path end 53 | def sbin(path = nil) prefix/'sbin'/path end 54 | def share(path = nil) prefix/'share'/path end 55 | 56 | # Return real paths for the scope of the given block. 57 | # 58 | # prefix('usr') # => /../software/tmp-dest/usr 59 | # 60 | # with_trueprefix do 61 | # prefix('usr') # => /usr 62 | # end 63 | def with_trueprefix 64 | old_value = installing 65 | self.installing = false 66 | yield 67 | ensure 68 | self.installing = old_value 69 | end 70 | 71 | private 72 | def current_pathname_for(dir) 73 | dir.gsub!(%r{^/}, '') if dir 74 | 75 | if omnibus_installing? 76 | Path.new("/#{dir}") 77 | else 78 | installing? ? destdir/dir : Path.new("/#{dir}") 79 | end 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/fpm/cookery/shellout.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/log' 2 | require 'systemu' 3 | 4 | module FPM 5 | module Cookery 6 | class Shellout 7 | class CommandFailed < StandardError 8 | attr_reader :output 9 | 10 | def initialize(message, output) 11 | super(message) 12 | @output = output 13 | end 14 | end 15 | 16 | def self.git_config_get(key) 17 | new('git config --get %s', key).run.chomp 18 | end 19 | 20 | def initialize(command, *args) 21 | @command = command.to_s % args 22 | end 23 | 24 | def run 25 | status, stdout, stderr = systemu(@command) 26 | 27 | if status.success? 28 | stdout 29 | else 30 | raise CommandFailed.new("Shellout command failed: #{@command}", stderr) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source.rb: -------------------------------------------------------------------------------- 1 | require 'addressable/uri' 2 | require 'uri/ssh_git' 3 | 4 | module FPM 5 | module Cookery 6 | class Source 7 | attr_reader :provider, :options 8 | 9 | def initialize(url, options = nil) 10 | options ||= {} 11 | begin 12 | @url = Addressable::URI.parse(url.to_s) 13 | rescue Addressable::URI::InvalidURIError 14 | @url = URI::SshGit.parse(url.to_s) 15 | end 16 | @provider = options[:with] 17 | @options = options 18 | end 19 | 20 | def provider? 21 | !!@provider 22 | end 23 | 24 | def local? 25 | @url.scheme.to_s.downcase == 'file' 26 | end 27 | 28 | # If the Addressable::URI is empty, there's nothing to fetch 29 | def fetchable? 30 | !@url.to_s.empty? 31 | end 32 | 33 | def url 34 | @url.to_s 35 | end 36 | [:to_s, :to_str].each { |m| alias_method m, :url } 37 | 38 | def path 39 | @url.path 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | require 'fpm/cookery/source_handler/curl' 3 | require 'fpm/cookery/source_handler/svn' 4 | require 'fpm/cookery/source_handler/git' 5 | require 'fpm/cookery/source_handler/hg' 6 | require 'fpm/cookery/source_handler/local_path' 7 | require 'fpm/cookery/source_handler/noop' 8 | require 'fpm/cookery/source_handler/directory' 9 | require 'fpm/cookery/log' 10 | require 'fpm/cookery/exceptions' 11 | 12 | module FPM 13 | module Cookery 14 | class SourceHandler 15 | DEFAULT_HANDLER = :curl 16 | LOCAL_HANDLER = :local_path 17 | REQUIRED_METHODS = [:fetch, :extract] 18 | 19 | extend Forwardable 20 | def_delegators :@handler, :fetch, :extract, :local_path, :checksum?, :fetchable? 21 | 22 | attr_reader :source_url 23 | 24 | def initialize(source, cachedir, builddir) 25 | @source = source 26 | @cachedir = cachedir 27 | @builddir = builddir 28 | 29 | if @source.provider? 30 | @source_provider = @source.provider 31 | elsif @source.local? 32 | @source_provider = LOCAL_HANDLER 33 | else 34 | @source_provider = DEFAULT_HANDLER 35 | end 36 | 37 | @handler = get_source_handler(@source_provider) 38 | end 39 | 40 | private 41 | def get_source_handler(provider) 42 | klass = handler_to_class(provider) 43 | # XXX Refactor handler to avoid passing the options. 44 | klass.new(@source, @source.options, @cachedir, @builddir) 45 | end 46 | 47 | def handler_to_class(provider) 48 | begin 49 | maybe_klass = self.class.const_get(provider.to_s.split('_').map(&:capitalize).join) 50 | 51 | instance_method_map = Hash[maybe_klass.instance_methods.map { |m| [m, true] }] 52 | missing_methods = REQUIRED_METHODS.find_all { |m| !instance_method_map.key?(m) } 53 | 54 | unless missing_methods.empty? 55 | formatted_missing = missing_methods.map { |m| "`#{m}'" }.join(', ') 56 | message = %{#{maybe_klass} does not implement required method(s): #{formatted_missing}} 57 | Log.error message 58 | raise Error::Misconfiguration, message 59 | end 60 | 61 | maybe_klass 62 | rescue NameError => e 63 | Log.error "Specified provider #{provider} does not exist." 64 | raise Error::Misconfiguration, e.message 65 | end 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler/curl.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/source_handler/template' 2 | require 'fpm/cookery/log' 3 | 4 | module FPM 5 | module Cookery 6 | class SourceHandler 7 | class Curl < FPM::Cookery::SourceHandler::Template 8 | 9 | NAME = :curl 10 | CHECKSUM = true 11 | 12 | def fetch(config = {}) 13 | if local_path.exist? 14 | Log.info "Using cached file #{local_path}" 15 | else 16 | Dir.chdir(cachedir) do 17 | curl(url, local_path, config) unless local_path.exist? 18 | end 19 | end 20 | local_path 21 | end 22 | 23 | def extract(config = {}) 24 | Dir.chdir(builddir) do 25 | case local_path.extname 26 | when '.bz2', '.gz', '.tgz', '.xz', '.tar' 27 | safesystem('tar', 'xf', local_path) 28 | when '.shar', '.bin' 29 | File.chmod(0755, local_path) 30 | safesystem(local_path) 31 | when '.zip' 32 | safesystem('unzip', '-d', local_path.basename('.zip'), local_path) 33 | else 34 | if !local_path.directory? && !local_path.basename.exist? 35 | Dir.mkdir(local_path.basename) 36 | end 37 | 38 | FileUtils.cp_r(local_path, local_path.basename) 39 | end 40 | (builddir/extracted_source).to_s 41 | end 42 | end 43 | 44 | private 45 | def curl(url, path, config) 46 | args = options[:args] || '-fL' 47 | cmd = ['curl', args] 48 | cmd << (config[:quiet] ? '-s' : '--progress-bar') 49 | cmd += ['-o', path, url] 50 | safesystem(*cmd) 51 | rescue RuntimeError => e 52 | Log.error("Command failed: #{cmd.join(' ')}") 53 | raise 54 | end 55 | 56 | def extracted_source 57 | entries = Dir['*'].select {|dir| File.directory?(dir) } 58 | 59 | case entries.size 60 | when 0 61 | files = Dir['*'].select {|dir| File.file?(dir) } 62 | 63 | if files.size > 0 64 | '' 65 | else 66 | raise "Empty archive! (#{local_path})" 67 | end 68 | when 1 69 | entries.first 70 | else 71 | # Use the directory that was created last. 72 | dir = entries.sort do |a, b| 73 | File.stat(a).ctime <=> File.stat(b).ctime 74 | end.last 75 | 76 | if File.exist?(dir) 77 | dir 78 | else 79 | raise "Could not find source directory for #{local_path.basename}" 80 | end 81 | end 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler/directory.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/source_handler/template' 2 | require 'fpm/cookery/log' 3 | require 'fpm/cookery/path' 4 | require 'fileutils' 5 | 6 | module FPM 7 | module Cookery 8 | class SourceHandler 9 | class Directory < FPM::Cookery::SourceHandler::Template 10 | CHECKSUM = false 11 | NAME = :directory 12 | 13 | def fetch(config = {}) 14 | cachedir 15 | end 16 | 17 | def extract(config = {}) 18 | path = FPM::Cookery::Path.new(source.path) 19 | 20 | unless path.absolute? 21 | Log.error("Source path needs to be absolute: #{source.path}") 22 | raise "Source path needs to be absolute: #{source.path}" 23 | end 24 | 25 | Log.info("Copying files from #{path}") 26 | FileUtils.cp_r(path, builddir) 27 | builddir 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler/git.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/source_handler/template' 2 | require 'fpm/cookery/log' 3 | 4 | module FPM 5 | module Cookery 6 | class SourceHandler 7 | class Git < FPM::Cookery::SourceHandler::Template 8 | CHECKSUM = false 9 | NAME = :git 10 | 11 | def fetch(config = {}) 12 | rev = options[:sha] || options[:tag] 13 | 14 | if local_path.exist? 15 | Dir.chdir(local_path) do 16 | if rev and has_rev?(rev) 17 | Log.info("Skipping fetch, rev #{rev} exists.") 18 | else 19 | git('fetch', url) 20 | git('fetch', '--tags', url) 21 | end 22 | end 23 | else 24 | Dir.chdir(cachedir) do 25 | git('clone', url, local_path) 26 | end 27 | end 28 | 29 | local_path 30 | end 31 | 32 | def extract(config = {}) 33 | extracted_source = (builddir/local_path.basename('.git').to_s).to_s 34 | 35 | Dir.chdir(local_path) do 36 | if options[:sha] 37 | git('reset', '--hard', options[:sha]) 38 | extracted_source << "-#{options[:sha]}" 39 | elsif options[:tag] 40 | git('checkout', '-f', options[:tag]) 41 | extracted_source << "-tag-#{options[:tag]}" 42 | elsif options[:branch] 43 | git('checkout', '-f', "origin/#{options[:branch]}") 44 | extracted_source << "-branch-#{options[:branch]}" 45 | else 46 | git('checkout', '-f', 'origin/HEAD') 47 | extracted_source << '-HEAD' 48 | end 49 | 50 | # Initialize submodules after sha/tag/branch has been set. 51 | # See: https://github.com/bernd/fpm-cookery/issues/144 52 | git('submodule', 'update', '--init') if options[:submodule] 53 | 54 | case options.fetch(:extract, :default).to_s.to_sym 55 | when :clone 56 | if File.exist?(extracted_source) 57 | Log.info("Source directory has already been cloned into #{extracted_source}") 58 | else 59 | git('clone', '-l', '--recurse-submodules', Dir.pwd, extracted_source) 60 | end 61 | else 62 | # Trailing '/' is important! (see git-checkout-index(1)) 63 | git('checkout-index', '-a', '-f', "--prefix=#{extracted_source}/") 64 | 65 | if options[:submodule] 66 | git('submodule', 'foreach', "mkdir -p #{extracted_source}/$path && cp -r . #{extracted_source}/$path") 67 | end 68 | end 69 | end 70 | 71 | extracted_source 72 | end 73 | 74 | private 75 | def git(command, *args) 76 | Log.debug "git #{command} #{args.join(' ')}" 77 | safesystem('git', command, *args) 78 | end 79 | 80 | def has_rev?(rev) 81 | Log.debug "git show #{rev} >/dev/null 2>&1" 82 | safesystem("git show #{rev} >/dev/null 2>&1") 83 | true 84 | rescue 85 | false 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler/hg.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/source_handler/template' 2 | require 'fpm/cookery/log' 3 | 4 | module FPM 5 | module Cookery 6 | class SourceHandler 7 | class Hg < FPM::Cookery::SourceHandler::Template 8 | CHECKSUM = false 9 | NAME = :hg 10 | 11 | def fetch(config = {}) 12 | if local_path.exist? 13 | Dir.chdir(local_path) do 14 | hg('pull') 15 | hg('update') 16 | end 17 | else 18 | Dir.chdir(cachedir) do 19 | hg('clone', url, local_path) 20 | end 21 | end 22 | 23 | local_path 24 | end 25 | 26 | def extract(config = {}) 27 | src = (builddir/local_path.basename('.hg').to_s).to_s 28 | 29 | Dir.chdir(local_path) do 30 | if options[:rev] 31 | src << "-#{options[:rev]}" 32 | hg('archive', '-y', '-r', options[:rev], '-t', 'files', src) 33 | else 34 | src << '-tip' 35 | hg('archive', '-y', '-t', 'files', src) 36 | end 37 | end 38 | 39 | src 40 | end 41 | 42 | private 43 | def hg(command, *args) 44 | Log.debug "hg #{command} #{args.join(' ')}" 45 | safesystem('hg', command, *args) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler/local_path.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/source_handler/template' 2 | require 'fpm/cookery/log' 3 | require 'fileutils' 4 | 5 | module FPM 6 | module Cookery 7 | class SourceHandler 8 | class LocalPath < FPM::Cookery::SourceHandler::Curl 9 | CHECKSUM = false 10 | NAME = :local_path 11 | 12 | def fetch(config = {}) 13 | if local_path.exist? 14 | Log.info "Using cached file #{local_path}" 15 | else 16 | Log.info "Copying #{source.path} to cache" 17 | FileUtils.cp_r(source.path, cachedir) 18 | end 19 | local_path 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler/noop.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/source_handler/template' 2 | 3 | module FPM 4 | module Cookery 5 | class SourceHandler 6 | class Noop < FPM::Cookery::SourceHandler::Template 7 | CHECKSUM = false 8 | NAME = :noop 9 | 10 | def fetch(config = {}) 11 | Log.info "Noop source_handler; do nothing." 12 | end 13 | 14 | def extract(config = {}) 15 | Log.info "Not extracting - noop source handler" 16 | builddir.to_s 17 | end 18 | 19 | def fetchable? 20 | true 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler/svn.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/source_handler/template' 2 | 3 | module FPM 4 | module Cookery 5 | class SourceHandler 6 | class Svn < FPM::Cookery::SourceHandler::Template 7 | 8 | CHECKSUM = false 9 | NAME = :svn 10 | 11 | def fetch(config = {}) 12 | # TODO(lusis) - implement some caching using 'svn info'? 13 | Dir.chdir(cachedir) do 14 | svn(url, local_path) 15 | end 16 | local_path 17 | end 18 | 19 | def extract(config = {}) 20 | Dir.chdir(builddir) do 21 | safesystem('cp', '-Rp', local_path, '.') 22 | (builddir/extracted_source).to_s 23 | end 24 | end 25 | 26 | private 27 | def svn(url, path) 28 | extra_args = [] 29 | 30 | extra_args << '--ignore-externals' if !options[:externals] 31 | 32 | [:username, :password].each do |opt| 33 | if options.key? opt 34 | extra_args << "--#{opt}" << options[opt] 35 | end 36 | end 37 | 38 | revision = options[:revision] || 'HEAD' 39 | 40 | safesystem('svn', 'export', '--force', *extra_args, '-q', '-r', revision, url, path) 41 | end 42 | 43 | def extracted_source 44 | entries = Dir['*'].select {|dir| File.directory?(dir) } 45 | 46 | case entries.size 47 | when 0 48 | raise "Empty checkout! (#{local_path})" 49 | when 1 50 | entries.first 51 | else 52 | # Use the directory that was created last. 53 | dir = entries.sort do |a, b| 54 | File.stat(a).ctime <=> File.stat(b).ctime 55 | end.last 56 | 57 | if File.exist?(dir) 58 | dir 59 | else 60 | raise "Could not find source directory for #{local_path.basename}" 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_handler/template.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/utils' 2 | 3 | module FPM 4 | module Cookery 5 | class SourceHandler 6 | class Template 7 | include FPM::Cookery::Utils 8 | 9 | NAME = :template 10 | CHECKSUM = true 11 | 12 | attr_reader :url, :options, :cachedir, :builddir, :has_checksum, :name 13 | 14 | def initialize(source_url, options, cachedir, builddir) 15 | @url = source_url 16 | @options = options 17 | @cachedir = cachedir 18 | @builddir = builddir 19 | @has_checksum = self.class::CHECKSUM 20 | @name = self.class::NAME 21 | end 22 | 23 | def source 24 | @url 25 | end 26 | 27 | def fetch(config = {}) 28 | raise "#{self}#fetch not implemented!" 29 | end 30 | 31 | def fetchable? 32 | @url.fetchable? 33 | end 34 | 35 | def extract(config = {}) 36 | raise "#{self}#extract not implemented!" 37 | end 38 | 39 | def checksum? 40 | @has_checksum 41 | end 42 | 43 | def local_path 44 | @local_path ||= cachedir/(options[:as] || File.basename(url)) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/fpm/cookery/source_integrity_check.rb: -------------------------------------------------------------------------------- 1 | require 'digest/sha1' 2 | 3 | module FPM 4 | module Cookery 5 | class SourceIntegrityCheck 6 | attr_reader :checksum_expected, :checksum_actual, :filename, :digest 7 | 8 | def initialize(recipe) 9 | @recipe = recipe 10 | @error = false 11 | @filename = recipe.local_path 12 | @digest = nil 13 | @checksum_expected = nil 14 | @checksum_actual = nil 15 | verify! 16 | end 17 | 18 | def error? 19 | @error 20 | end 21 | 22 | def checksum_missing? 23 | checksum_expected.nil? 24 | end 25 | 26 | private 27 | def verify! 28 | digest, checksum = get_checksum 29 | 30 | @digest = digest 31 | @checksum_expected = checksum 32 | @checksum_actual = build_checksum(digest) 33 | 34 | unless digest 35 | @error = true 36 | @digest = :sha256 37 | @checksum_actual = build_checksum(@digest) 38 | end 39 | 40 | if @checksum_expected.to_s != @checksum_actual.to_s 41 | @error = true 42 | end 43 | end 44 | 45 | def get_checksum 46 | type = [:sha512, :sha256, :sha1, :md5].find do |digest| 47 | @recipe.respond_to?(digest) and 48 | @recipe.send(digest) and 49 | !@recipe.send(digest).empty? 50 | end 51 | 52 | [type, @recipe.send(type)] 53 | rescue TypeError 54 | [nil, nil] 55 | end 56 | 57 | def build_checksum(type) 58 | return unless type 59 | 60 | digest = Digest.const_get(type.to_s.upcase).new 61 | 62 | File.open(@recipe.local_path, 'r') do |file| 63 | while chunk = file.read(4096) 64 | digest.update(chunk) 65 | end 66 | end 67 | 68 | digest.hexdigest 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/fpm/cookery/utils.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/log' 2 | 3 | module FPM 4 | module Cookery 5 | module Utils 6 | protected 7 | # From fpm. (lib/fpm/util.rb) 8 | def safesystem(*args) 9 | # Process the arguments: 10 | # - Convert all arguments to strings because "system" cannot handle 11 | # keys (avoids TypeError) 12 | # - Make sure to avoid nil elements in args. This might happen on 1.8. 13 | safe_args = args.map(&:to_s).compact.flatten 14 | Log.debug("Executing command: #{safe_args.inspect}") 15 | success = system(*safe_args) 16 | if !success 17 | raise "'system(#{args.inspect})' failed with error code: #{$?.exitstatus}" 18 | end 19 | return success 20 | end 21 | 22 | # Alias for safesystem. 23 | def sh(*args) 24 | safesystem(*args) 25 | end 26 | 27 | def cleanenv_safesystem(*args) 28 | Log.deprecated("Use `environment.with_clean { safesystem(...) }` instead of `cleanenv_safesystem(...)` in the recipe") 29 | environment.with_clean { safesystem(*args) } 30 | end 31 | 32 | # From brew2deb. (lib/debian_formula.rb) 33 | def argument_build(*args) 34 | if args.last.is_a?(Hash) 35 | opts = args.pop 36 | args += opts.map{ |k,v| 37 | option = k.to_s.gsub('_','-') 38 | if v == true 39 | "--#{option}" 40 | else 41 | "--#{option}=#{v}" 42 | end 43 | } 44 | end 45 | args 46 | end 47 | 48 | def configure(*args) 49 | args = argument_build(*args) 50 | safesystem './configure', *args 51 | end 52 | 53 | def autogen(*args) 54 | args = argument_build(*args) 55 | safesystem './autogen.sh', *args 56 | end 57 | 58 | # From brew2deb. (lib/debian_formula.rb) 59 | def make(*args) 60 | env = args.pop if args.last.is_a?(Hash) 61 | env ||= {} 62 | 63 | args += env.map{ |k,v| "#{k}=#{v}" } 64 | args.map!{ |a| a.to_s } 65 | 66 | safesystem 'make', *args 67 | end 68 | 69 | # From homebrew. (Library/Homebrew/utils.rb) 70 | def inline_replace(path, before = nil, after = nil) 71 | [*path].each do |path| 72 | f = File.open(path, 'r') 73 | s = f.read 74 | 75 | if before == nil and after == nil 76 | # s.extend(StringInreplaceExtension) 77 | yield s 78 | else 79 | s.gsub!(before, after) 80 | end 81 | 82 | f.reopen(path, 'w').write(s) 83 | f.close 84 | end 85 | end 86 | alias_method :inreplace, :inline_replace # homebrew compat 87 | 88 | def patch(src, level = 0) 89 | raise "patch level must be integer" unless level.is_a?(Fixnum) 90 | raise "#{src} does not exist" unless File.exist? src 91 | 92 | if "#{src}".end_with?('.gz') 93 | safesystem "gunzip -c #{src} | patch -p#{level} --batch" 94 | else 95 | safesystem "patch -p#{level} --batch < #{src}" 96 | end 97 | end 98 | 99 | def go(*args) 100 | args = argument_build(*args) 101 | args.each {|a| a == '--mod=vendor' && ENV['GO111MODULE'] = 'on' } 102 | safesystem 'go', *args 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/fpm/cookery/version.rb: -------------------------------------------------------------------------------- 1 | module FPM 2 | module Cookery 3 | VERSION = '0.37.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/hiera/fpm_cookery_logger.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/log/hiera' 2 | 3 | # Note: this file exists because hiera runs +require "hiera/#{logger}_logger", 4 | # where +logger+ refers to the name passed as the +:logger+ option to the 5 | # +Hiera+ class' constructor. 6 | 7 | # Note: This weird name is due to the relationship between how Hiera looks for the 8 | # module to load ("require "#{logger}_logger") and how it infers the class 9 | # name of its logger (Hiera.const_get("#{logger.capitalize}_logger") This 10 | # class name is what would be constructed if the hiera object were created 11 | # as follows: Hiera.new(:config => {:logger => "fpm_cookery"}). 12 | Hiera::Fpm_cookery_logger = FPM::Cookery::Log::Hiera 13 | -------------------------------------------------------------------------------- /recipes/activemq/recipe.rb: -------------------------------------------------------------------------------- 1 | class ActiveMQ < FPM::Cookery::Recipe 2 | name 'apache-activemq' 3 | version '5.15.6' 4 | url 'http://archive.apache.org/dist/activemq/5.15.6/apache-activemq-5.15.6-bin.tar.gz' 5 | # Checksum from http://archive.apache.org/dist/activemq/5.15.6/apache-activemq-5.15.6-bin.tar.gz.sha512 6 | sha512 'a1b931a25c513f83f4f712cc126ee67a2b196ea23a243aa6cafe357ea03f721fba6cb566701e5c0e1f2f7ad8954807361364635c45d5069ec2dbf0ba5c6b588b' 7 | 8 | def build 9 | end 10 | 11 | def install 12 | opt.install Dir["#{builddir}/*"] 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /recipes/arr-pm/recipe.rb: -------------------------------------------------------------------------------- 1 | class ArrPmRubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'arr-pm' 3 | version '0.0.7' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/backports/recipe.rb: -------------------------------------------------------------------------------- 1 | class BackportsRubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'backports' 3 | version '2.6.2' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/cabin/recipe.rb: -------------------------------------------------------------------------------- 1 | class CabinRubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'cabin' 3 | version '0.6.0' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/clamp/recipe.rb: -------------------------------------------------------------------------------- 1 | class ClampRubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'clamp' 3 | version '0.3.1' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/facter/recipe.rb: -------------------------------------------------------------------------------- 1 | class FacterRubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'facter' 3 | version '1.6.16' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/addressable.rb: -------------------------------------------------------------------------------- 1 | class AddressableRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "addressable" 3 | version "2.3.5" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/arr-pm.rb: -------------------------------------------------------------------------------- 1 | class ArrPmRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "arr-pm" 3 | version "0.0.9" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/backports.rb: -------------------------------------------------------------------------------- 1 | class BackportsRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "backports" 3 | version "2.6.2" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/cabin.rb: -------------------------------------------------------------------------------- 1 | class CabinRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "cabin" 3 | version "0.6.0" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/childprocess.rb: -------------------------------------------------------------------------------- 1 | class ChildprocessRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "childprocess" 3 | version "0.3.9" 4 | 5 | chain_package true 6 | chain_recipes "ffi" 7 | end 8 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/clamp.rb: -------------------------------------------------------------------------------- 1 | class ClampRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "clamp" 3 | version "0.6.0" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/facter.rb: -------------------------------------------------------------------------------- 1 | class FacterRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "facter" 3 | version "1.7.1" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/ffi.rb: -------------------------------------------------------------------------------- 1 | class FfiRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "ffi" 3 | version "1.0.11" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/fpm.rb: -------------------------------------------------------------------------------- 1 | class FpmRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "fpm" 3 | version "1.1.0" 4 | 5 | chain_package true 6 | chain_recipes "json", "cabin", "backports", "arr-pm", "clamp", "childprocess" 7 | end 8 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/hiera.rb: -------------------------------------------------------------------------------- 1 | class HieraRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "hiera" 3 | version "1.2.1" 4 | 5 | chain_package true 6 | chain_recipes "json_pure" 7 | end 8 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/json.rb: -------------------------------------------------------------------------------- 1 | class JsonRubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'json' 3 | version '1.7.7' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/json_pure.rb: -------------------------------------------------------------------------------- 1 | class JsonPureRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "json_pure" 3 | version "1.7.7" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/puppet.rb: -------------------------------------------------------------------------------- 1 | class PuppetRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "puppet" 3 | version "3.4.3" 4 | 5 | chain_package true 6 | chain_recipes "hiera", "rgen" 7 | end 8 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/recipe.rb: -------------------------------------------------------------------------------- 1 | class FpmCookeryRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "fpm-cookery" 3 | version "0.24.0" 4 | 5 | chain_package true 6 | chain_recipes "fpm", "facter", "puppet", "addressable", "systemu" 7 | end 8 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/rgen.rb: -------------------------------------------------------------------------------- 1 | class RgenRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "rgen" 3 | version "0.6.5" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery-gem/systemu.rb: -------------------------------------------------------------------------------- 1 | class RgenRubyGem < FPM::Cookery::RubyGemRecipe 2 | name "systemu" 3 | version "2.6.4" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/fpm-cookery/fpm-cook.bin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Stolen from Vagrant 4 | 5 | # Get the directory where this script is. This will also resolve 6 | # any symlinks in the directory/script, so it will be the fully 7 | # resolved path. 8 | SOURCE="${BASH_SOURCE[0]}" 9 | while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done 10 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 11 | 12 | # Useful variables 13 | EMBEDDED_DIR="${DIR}/../embedded" 14 | 15 | # Unset some variables that might be set by rvm and friends. 16 | unset RUBYOPT BUNDLE_GEMFILE BUNDLE_BIN_PATH GEM_HOME GEM_PATH GEMRC 17 | 18 | # Call the actual fpm-cook bin with our arguments 19 | ${EMBEDDED_DIR}/bin/fpm-cook "$@" 20 | -------------------------------------------------------------------------------- /recipes/fpm-cookery/recipe.rb: -------------------------------------------------------------------------------- 1 | class FPMCookery < FPM::Cookery::Recipe 2 | description 'building packages' 3 | 4 | name 'fpm-cookery' 5 | version '0.15.0' 6 | revision 0 7 | homepage 'https://github.com/bernd/fpm-cookery' 8 | license 'MIT' 9 | 10 | source '', :with => :noop 11 | 12 | omnibus_package true 13 | omnibus_recipes 'ruby' 14 | omnibus_dir '/opt/fpm-cookery' 15 | 16 | def build 17 | gem_install 'fpm-cookery', version 18 | end 19 | 20 | def install 21 | destdir('bin').install workdir('fpm-cook.bin'), 'fpm-cook' 22 | 23 | with_trueprefix do 24 | create_post_install_hook <<-EOF 25 | set -e 26 | 27 | BIN_PATH="#{destdir}/bin" 28 | BIN="fpm-cook" 29 | 30 | update-alternatives --install /usr/bin/$BIN $BIN $BIN_PATH/$BIN 100 31 | 32 | exit 0 33 | EOF 34 | 35 | create_pre_uninstall_hook <<-EOF 36 | set -e 37 | 38 | BIN_PATH="#{destdir}/bin" 39 | BIN="fpm-cook" 40 | 41 | if [ "$1" != "upgrade" ]; then 42 | update-alternatives --remove $BIN $BIN_PATH/$BIN 43 | fi 44 | 45 | exit 0 46 | EOF 47 | end 48 | end 49 | 50 | private 51 | 52 | def gem_install(name, version = nil) 53 | v = version.nil? ? '' : "-v #{version}" 54 | cleanenv_safesystem "#{destdir}/embedded/bin/gem install --no-ri --no-rdoc #{v} #{name}" 55 | end 56 | 57 | def create_post_install_hook(script, interpreter = "/bin/sh") 58 | File.open(builddir('post-install'), 'w', 0755) do |f| 59 | f.write "#!#{interpreter}\n" + script.gsub(/^\s+/, '') 60 | self.class.post_install(File.expand_path(f.path)) 61 | end 62 | end 63 | 64 | def create_pre_uninstall_hook(script, interpreter = "/bin/sh") 65 | File.open(builddir('pre-uninstall'), 'w', 0755) do |f| 66 | f.write "#!#{interpreter}\n" + script.gsub(/^\s+/, '') 67 | self.class.pre_uninstall(File.expand_path(f.path)) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /recipes/fpm-cookery/ruby.rb: -------------------------------------------------------------------------------- 1 | class Ruby200 < FPM::Cookery::Recipe 2 | description 'The Ruby virtual machine' 3 | 4 | name 'ruby' 5 | version '2.0.0.247' 6 | revision 0 7 | homepage 'http://www.ruby-lang.org/' 8 | source 'http://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p247.tar.bz2' 9 | sha256 '08e3d4b85b8a1118a8e81261f59dd8b4ddcfd70b6ae554e0ec5ceb99c3185e8a' 10 | 11 | license 'The Ruby License' 12 | section 'interpreters' 13 | 14 | platforms [:ubuntu, :debian] do 15 | build_depends 'autoconf', 'libreadline6-dev', 'bison', 'zlib1g-dev', 16 | 'libssl-dev', 'libyaml-dev', 'libffi-dev', 'libgdbm-dev', 17 | 'libncurses5-dev', 'libreadline6-dev' 18 | depends 'libffi6', 'libncurses5', 'libreadline6', 'libssl1.0.0', 19 | 'libtinfo5', 'libyaml-0-2', 'zlib1g', 'libffi6', 'libgdbm3', 20 | 'libncurses5', 'libreadline6' 21 | end 22 | 23 | platforms [:fedora, :redhat, :centos] do 24 | build_depends 'rpmdevtools', 'libffi-devel', 'autoconf', 'bison', 25 | 'libxml2-devel', 'libxslt-devel', 'openssl-devel', 26 | 'gdbm-devel', 'zlib-devel' 27 | depends 'zlib', 'libffi', 'gdbm' 28 | end 29 | platforms [:fedora] do depends.push('openssl-libs') end 30 | platforms [:redhat, :centos] do depends.push('openssl') end 31 | 32 | def build 33 | configure :prefix => destdir, 34 | 'enable-shared' => true, 35 | 'disable-install-doc' => true 36 | make 37 | end 38 | 39 | def install 40 | make :install 41 | 42 | # Shrink package. 43 | rm_f "#{destdir}/lib/libruby-static.a" 44 | safesystem "strip #{destdir}/bin/ruby" 45 | safesystem "find #{destdir} -name '*.so' -or -name '*.so.*' | xargs strip" 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /recipes/fpm/recipe.rb: -------------------------------------------------------------------------------- 1 | class FpmRubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'fpm' 3 | version '0.4.29' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/httpie/recipe.rb: -------------------------------------------------------------------------------- 1 | class Httpie < FPM::Cookery::VirtualenvRecipe 2 | description 'user-friendly cURL replacement featuring intuitive UI, JSON support, syntax highlighting' 3 | 4 | name 'httpie' 5 | version '0.9.2' 6 | revision '1' 7 | arch 'all' 8 | 9 | homepage 'http://httpie.org' 10 | 11 | build_depends 'python-virtualenv', 'python-setuptools' 12 | 13 | virtualenv_fix_name false 14 | virtualenv_install_location '/opt' 15 | end 16 | -------------------------------------------------------------------------------- /recipes/json/recipe.rb: -------------------------------------------------------------------------------- 1 | class JsonRubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'json' 3 | version '1.6.6' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/nodejs/recipe.rb: -------------------------------------------------------------------------------- 1 | class NodeJS < FPM::Cookery::Recipe 2 | source 'http://nodejs.org/dist/node-v0.4.10.tar.gz' 3 | # head 'https://github.com/joyent/node.git' 4 | homepage 'http://nodejs.org/' 5 | md5 '2e8b82a9788308727e285d2d4a129c29' 6 | 7 | section 'interpreters' 8 | name 'nodejs' 9 | version '0.4.10+github1' 10 | description 'Evented I/O for V8 JavaScript' 11 | 12 | build_depends \ 13 | 'libssl-dev', 14 | 'g++', 15 | 'python' 16 | 17 | depends \ 18 | 'openssl' 19 | 20 | def build 21 | inreplace 'wscript' do |s| 22 | s.gsub! '/usr/local', '/usr' 23 | s.gsub! '/opt/local/lib', '/usr/lib' 24 | end 25 | 26 | configure \ 27 | :prefix => prefix, 28 | :debug => true 29 | make 30 | end 31 | 32 | def install 33 | make :install, 'DESTDIR' => destdir 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /recipes/omnibustest/bundler-gem.rb: -------------------------------------------------------------------------------- 1 | class BundlerGem < FPM::Cookery::Recipe 2 | description 'Bundler gem' 3 | 4 | name 'bundler' 5 | version '1.3.4' 6 | revision 0 7 | source "nothing", :with => :noop 8 | 9 | vendor 'fpm' 10 | license 'Unknown' 11 | 12 | section 'interpreters' 13 | 14 | def build 15 | cleanenv_safesystem "#{destdir}/bin/gem install #{name} -v #{version}" 16 | end 17 | 18 | def install 19 | # Do nothing! 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /recipes/omnibustest/recipe.rb: -------------------------------------------------------------------------------- 1 | class OmnibusTest < FPM::Cookery::Recipe 2 | homepage 'http://test' 3 | 4 | section 'interpreters' 5 | name 'omnibus-test' 6 | version '1.0.0' 7 | description 'Testing Omnibus package' 8 | revision 0 9 | source '', :with => :noop 10 | 11 | omnibus_package true 12 | omnibus_recipes "ruby", "bundler-gem" 13 | omnibus_dir '/opt/omnibustest' 14 | 15 | def build 16 | end 17 | 18 | def install 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /recipes/omnibustest/ruby.rb: -------------------------------------------------------------------------------- 1 | class Ruby210 < FPM::Cookery::Recipe 2 | description 'The Ruby virtual machine' 3 | 4 | name 'ruby' 5 | version '2.1.2' 6 | revision 0 7 | homepage 'http://www.ruby-lang.org/' 8 | source 'http://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.bz2' 9 | sha256 '6948b02570cdfb89a8313675d4aa665405900e27423db408401473f30fc6e901' 10 | 11 | vendor 'fpm' 12 | license 'The Ruby License' 13 | 14 | section 'interpreters' 15 | 16 | build_depends 'autoconf', 'libreadline6-dev', 'bison', 'zlib1g-dev', 17 | 'libssl-dev', 'libyaml-dev', 'libffi-dev', 'libgdbm-dev', 'libncurses5-dev', 18 | 'libreadline6-dev' 19 | 20 | depends 'libffi6', 'libncurses5', 'libreadline6', 'libssl1.0.0', 21 | 'libtinfo5', 'libyaml-0-2', 'zlib1g', 'libffi6', 'libgdbm3', 'libncurses5', 22 | 'libreadline6' 23 | 24 | def build 25 | configure :prefix => destdir, 'disable-install-doc' => true 26 | make 27 | end 28 | 29 | def install 30 | make :install 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /recipes/open4/recipe.rb: -------------------------------------------------------------------------------- 1 | class Open4RubyGem < FPM::Cookery::RubyGemRecipe 2 | name 'open4' 3 | version '1.3.0' 4 | end 5 | -------------------------------------------------------------------------------- /recipes/python-requests/recipe.rb: -------------------------------------------------------------------------------- 1 | class PythonRequests < FPM::Cookery::PythonRecipe 2 | homepage "http://python-requests.org" 3 | name "requests" 4 | build_depends ["python-setuptools"] 5 | depends ["python"] 6 | version "2.2.1" 7 | end 8 | -------------------------------------------------------------------------------- /recipes/python-test/recipe.rb: -------------------------------------------------------------------------------- 1 | class MinitestPython < FPM::Cookery::PythonRecipe 2 | name "minitest" 3 | version "1.1.0" 4 | end 5 | -------------------------------------------------------------------------------- /recipes/redis/config/common.yaml: -------------------------------------------------------------------------------- 1 | homepage: 'http://redis.io' 2 | 3 | source: 'http://redis.googlecode.com/files/redis-%{hiera("version")}.tar.gz' 4 | md5: 'c4b0b5e4953a11a503cb54cf6b09670e' 5 | name: 'redis-server' 6 | version: '2.4.2' 7 | revision: '0' # => redis-server-2.2.5+fpm1 8 | description: 'An advanced key-value store.' 9 | conflicts: '%{hiera("name")}' 10 | config_files: '/etc/redis/redis.conf' 11 | patches: 'patches/test.patch' 12 | -------------------------------------------------------------------------------- /recipes/redis/config/git_2.4.2_tag.yaml: -------------------------------------------------------------------------------- 1 | source: 2 | - 'https://github.com/antirez/redis' 3 | - :with: :git 4 | :tag: '2.4.2' 5 | -------------------------------------------------------------------------------- /recipes/redis/config/git_2.4.yaml: -------------------------------------------------------------------------------- 1 | source: 2 | - 'https://github.com/antirez/redis' 3 | - :with: :git 4 | :branch: '2.4' 5 | -------------------------------------------------------------------------------- /recipes/redis/config/git_sha_072a905.yaml: -------------------------------------------------------------------------------- 1 | source: 2 | - 'https://github.com/antirez/redis' 3 | - :with: :git 4 | :sha: '072a905' 5 | -------------------------------------------------------------------------------- /recipes/redis/config/svn_r2400.yaml: -------------------------------------------------------------------------------- 1 | source: 2 | - 'https://github.com/antirez/redis/trunk' 3 | - :with :svn 4 | :revision: '2400' 5 | -------------------------------------------------------------------------------- /recipes/redis/config/svn_trunk.yaml: -------------------------------------------------------------------------------- 1 | source: 2 | - 'https://github.com/antirez/redis/trunk' 3 | - :with: :svn 4 | -------------------------------------------------------------------------------- /recipes/redis/recipe.rb: -------------------------------------------------------------------------------- 1 | class Redis < FPM::Cookery::Recipe 2 | def build 3 | make 4 | 5 | inline_replace 'redis.conf' do |s| 6 | s.gsub! 'daemonize no', 'daemonize yes # non-default' 7 | end 8 | end 9 | 10 | def install 11 | # C'mon, redis, what's up with not respecting DESTDIR? 12 | make :install, 'PREFIX' => destdir / 'usr' 13 | 14 | var('lib/redis').mkdir 15 | 16 | %w(run log/redis).each {|p| var(p).mkdir } 17 | 18 | bin.install ['src/redis-server', 'src/redis-cli'] 19 | 20 | etc('redis').install 'redis.conf' 21 | etc('init.d').install workdir('redis-server.init.d') => 'redis-server' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /recipes/redis/redis-server.init.d: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: redis-server 4 | # Required-Start: $syslog 5 | # Required-Stop: $syslog 6 | # Should-Start: $local_fs 7 | # Should-Stop: $local_fs 8 | # Default-Start: 2 3 4 5 9 | # Default-Stop: 0 1 6 10 | # Short-Description: redis-server - Persistent key-value db 11 | # Description: redis-server - Persistent key-value db 12 | ### END INIT INFO 13 | 14 | 15 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 16 | DAEMON=/usr/bin/redis-server 17 | DAEMON_ARGS=/etc/redis/redis.conf 18 | NAME=redis-server 19 | DESC=redis-server 20 | PIDFILE=/var/run/redis.pid 21 | 22 | test -x $DAEMON || exit 0 23 | test -x $DAEMONBOOTSTRAP || exit 0 24 | 25 | set -e 26 | 27 | case "$1" in 28 | start) 29 | echo -n "Starting $DESC: " 30 | touch $PIDFILE 31 | chown redis:redis $PIDFILE 32 | chown redis:redis /var/log/redis 33 | chown redis:redis /var/lib/redis 34 | if start-stop-daemon --start --quiet --umask 007 --pidfile $PIDFILE --chuid redis:redis --exec $DAEMON -- $DAEMON_ARGS 35 | then 36 | echo "$NAME." 37 | else 38 | echo "failed" 39 | fi 40 | ;; 41 | stop) 42 | echo -n "Stopping $DESC: " 43 | if start-stop-daemon --stop --retry 10 --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON 44 | then 45 | echo "$NAME." 46 | else 47 | echo "failed" 48 | fi 49 | rm -f $PIDFILE 50 | ;; 51 | 52 | restart|force-reload) 53 | ${0} stop 54 | ${0} start 55 | ;; 56 | status) 57 | if [ -f $PIDFILE ] 58 | then 59 | PID=`cat $PIDFILE` 60 | echo -n "Redis (pid: $PID): " 61 | if ps aux | grep $PID > /dev/null 62 | then 63 | echo "running" 64 | exit 0 65 | else 66 | echo "failed" 67 | exit 3 68 | fi 69 | else 70 | echo "Redis not running" 71 | exit 3 72 | fi 73 | ;; 74 | *) 75 | echo "Usage: /etc/init.d/$NAME {start|stop|restart|force-reload}" >&2 76 | exit 1 77 | ;; 78 | esac 79 | 80 | exit 0 81 | -------------------------------------------------------------------------------- /spec/book_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | require 'fpm/cookery/book' 4 | 5 | describe "Book" do 6 | let(:book) { FPM::Cookery::Book.instance } 7 | let(:config) do 8 | attrs = { 9 | :debug => false, 10 | :hiera_config => { 11 | # Silence logging 12 | :logger => :noop 13 | } 14 | } 15 | 16 | double('Config', attrs).as_null_object 17 | end 18 | 19 | describe ".instance" do 20 | describe ".load_recipe" do 21 | context "given an existing file" do 22 | include_context "temporary recipe", "recipe.rb", <<-RECIPE 23 | class FakeRecipe < FPM::Cookery::Recipe 24 | print 'Hello, world!' 25 | end 26 | RECIPE 27 | 28 | it "loads the file" do 29 | expect { book.load_recipe('recipe.rb', config) { } }.to output('Hello, world!').to_stdout 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/config' 3 | require 'fpm/cookery/cli' 4 | 5 | describe 'Config' do 6 | let(:data) { {} } 7 | let(:default_config) { FPM::Cookery::Config.new } 8 | let(:config) { FPM::Cookery::Config.new(data) } 9 | 10 | def self.common_tests(name) 11 | it 'can be set on init' do 12 | data[name.to_sym] = '__set__' 13 | 14 | expect(config.__send__(name)).to eq('__set__') 15 | end 16 | 17 | it 'can be set' do 18 | config.__send__("#{name}=", '__SET__') 19 | 20 | expect(config.__send__(name)).to eq('__SET__') 21 | end 22 | 23 | it 'provides a ? method' do 24 | data[name.to_sym] = false 25 | 26 | expect(config.__send__("#{name}?")).to eq(false) 27 | end 28 | end 29 | 30 | describe '#color' do 31 | it 'defaults to true' do 32 | expect(default_config.color).to eq(true) 33 | end 34 | 35 | common_tests(:color) 36 | end 37 | 38 | describe '#debug' do 39 | it 'defaults to true' do 40 | expect(default_config.debug).to eq(false) 41 | end 42 | 43 | common_tests(:debug) 44 | end 45 | 46 | describe '#target' do 47 | it 'defaults to nil' do 48 | expect(default_config.target).to eq(nil) 49 | end 50 | 51 | common_tests(:target) 52 | end 53 | 54 | describe '#platform' do 55 | it 'defaults to nil' do 56 | expect(default_config.platform).to eq(nil) 57 | end 58 | 59 | common_tests(:platform) 60 | end 61 | 62 | describe '#maintainer' do 63 | it 'defaults to nil' do 64 | expect(default_config.maintainer).to eq(nil) 65 | end 66 | 67 | common_tests(:maintainer) 68 | end 69 | 70 | describe '#vendor' do 71 | it 'defaults to nil' do 72 | expect(default_config.vendor).to eq(nil) 73 | end 74 | 75 | common_tests(:vendor) 76 | end 77 | 78 | describe '#quiet' do 79 | it 'defaults to false' do 80 | expect(default_config.quiet).to eq(false) 81 | end 82 | 83 | common_tests(:quiet) 84 | end 85 | 86 | describe '#skip_package' do 87 | it 'defaults to false' do 88 | expect(default_config.skip_package).to eq(false) 89 | end 90 | 91 | common_tests(:skip_package) 92 | end 93 | 94 | describe '#keep_destdir' do 95 | it 'defaults to false' do 96 | expect(default_config.keep_destdir).to eq(false) 97 | end 98 | 99 | common_tests(:keep_destdir) 100 | end 101 | 102 | describe '#dependency_check' do 103 | it 'defaults to false' do 104 | expect(default_config.dependency_check).to eq(true) 105 | end 106 | 107 | common_tests(:dependency_check) 108 | end 109 | 110 | describe '#tmp_root' do 111 | it 'defaults to nil' do 112 | expect(default_config.tmp_root).to be_nil 113 | end 114 | 115 | common_tests(:tmp_root) 116 | end 117 | 118 | describe '#pkg_dir' do 119 | it 'defaults to nil' do 120 | expect(default_config.pkg_dir).to be_nil 121 | end 122 | 123 | common_tests(:pkg_dir) 124 | end 125 | 126 | describe '#cache_dir' do 127 | it 'defaults to nil' do 128 | expect(default_config.cache_dir).to be_nil 129 | end 130 | 131 | common_tests(:cache_dir) 132 | end 133 | 134 | describe '#data_dir' do 135 | it 'defaults to nil' do 136 | expect(default_config.data_dir).to be_nil 137 | end 138 | 139 | common_tests(:data_dir) 140 | end 141 | 142 | describe '#hiera_config' do 143 | it 'defaults to nil' do 144 | expect(default_config.hiera_config).to be_nil 145 | end 146 | 147 | common_tests(:hiera_config) 148 | end 149 | 150 | describe '#to_hash' do 151 | it 'returns a hash representation of the object' do 152 | expect(default_config.to_hash).to eq({ 153 | :color => true, 154 | :data_dir => nil, 155 | :hiera_config => nil, 156 | :debug => false, 157 | :target => nil, 158 | :platform => nil, 159 | :maintainer => nil, 160 | :tmp_root => nil, 161 | :pkg_dir => nil, 162 | :cache_dir => nil, 163 | :vendor => nil, 164 | :vendor_delimiter => nil, 165 | :skip_package => false, 166 | :keep_destdir => false, 167 | :dependency_check => true, 168 | :quiet => false, 169 | :docker => false, 170 | :docker_image => nil, 171 | :docker_keep_container => false, 172 | :docker_cache => nil, 173 | :docker_bin => 'docker', 174 | :dockerfile => 'Dockerfile' 175 | }) 176 | end 177 | end 178 | 179 | describe 'with an invalid config key' do 180 | it 'raises an error' do 181 | data[:__foo__] = true 182 | 183 | expect { config }.to raise_error(FPM::Cookery::Error::InvalidConfigKey) 184 | end 185 | 186 | it 'adds the invalid keys' do 187 | data[:__foo__] = true 188 | data[:__bar__] = true 189 | error = nil 190 | 191 | begin; config; rescue => e; error = e; end 192 | 193 | # Sort array for Ruby 1.8.7 compat. 194 | expect(error.invalid_keys.sort).to eq([:__bar__, :__foo__]) 195 | end 196 | 197 | it 'works with strings' do 198 | data['maintainer'] = 'John' 199 | 200 | expect(config.maintainer).to eq('John') 201 | end 202 | end 203 | 204 | describe '.load_file' do 205 | let(:paths) do 206 | [ 207 | '/tmp/__abc__', 208 | File.expand_path('../fixtures/test-config-1.yml', __FILE__) 209 | ] 210 | end 211 | 212 | it 'loads first found file' do 213 | config = FPM::Cookery::Config.load_file(paths) 214 | 215 | expect(config.maintainer).to eq('John Doe ') 216 | end 217 | end 218 | 219 | describe '.from_cli' do 220 | it 'loads the config from cli options' do 221 | cli = FPM::Cookery::CLI.new('path', {}) 222 | cli.parse(%w(-D -t rpm)) 223 | 224 | config = FPM::Cookery::Config.from_cli(cli) 225 | 226 | expect(config.debug).to eq(true) 227 | expect(config.target).to eq('rpm') 228 | end 229 | end 230 | end 231 | -------------------------------------------------------------------------------- /spec/environment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/environment' 3 | require 'pathname' 4 | 5 | describe FPM::Cookery::Environment do 6 | let(:env) { described_class.new } 7 | 8 | it 'behaves like a hash' do 9 | env['foo'] = 'bar' 10 | 11 | expect(env['foo']).to eq('bar') 12 | end 13 | 14 | it 'converts keys to strings on set' do 15 | env[:foo] = 'bar' 16 | 17 | expect(env['foo']).to eq('bar') 18 | end 19 | 20 | it 'converts keys to strings on get' do 21 | env['foo'] = 'bar' 22 | 23 | expect(env[:foo]).to eq('bar') 24 | end 25 | 26 | it 'converts values to string' do 27 | env['foo'] = 1 28 | env['bar'] = Pathname.new('/') 29 | 30 | expect(env['foo']).to eq('1') 31 | expect(env['bar']).to eq('/') 32 | end 33 | 34 | it 'deletes a key if the value is set to nil' do 35 | env['foo'] = 'bar' 36 | env['foo'] = nil 37 | 38 | expect(env.to_hash).to_not have_key('foo') 39 | end 40 | 41 | describe '.[]' do 42 | context 'given a Hash' do 43 | it 'returns an new instance with keys and values coerced to Strings' do 44 | expect(described_class[:this => :that]).to eq({"this" => "that"}) 45 | end 46 | end 47 | 48 | context 'given an array of two-member arrays' do 49 | it 'returns an new instance with keys and values coerced to Strings' do 50 | expected = {"this" => "that", "foo" => "bar"} 51 | expect(described_class[[[:this, :that], [:foo, :bar]]]).to eq(expected) 52 | end 53 | end 54 | end 55 | 56 | describe '#to_hash' do 57 | it 'returns the data as a hash' do 58 | env['foo'] = 'bar' 59 | 60 | expect(env.to_hash).to eq({'foo' => 'bar'}) 61 | # Note: not using +be_a+ because +Environment+ inherits from +Hash+, and 62 | # we want to test that +env.to_hash+ actually is an instance of +Hash+ 63 | # and not an instance of a subclass of +Hash+. 64 | expect(env.to_hash.class).to eq(Hash) 65 | end 66 | 67 | it 'returns a copy of the data' do 68 | env['foo'] = 'bar' 69 | 70 | data = env.to_hash 71 | 72 | env['foo'] = 'nope' 73 | 74 | expect(data).to eq({'foo' => 'bar'}) 75 | end 76 | end 77 | 78 | describe '#merge' do 79 | context 'given a Hash' do 80 | it 'returns a new instance with merged contents' do 81 | expect(env.merge({:this => :that})).to eq({"this" => "that"}) 82 | end 83 | end 84 | end 85 | 86 | describe '#merge!' do 87 | context 'given a Hash' do 88 | let(:env_dup) { env.dup } 89 | it 'merges their contents in-place' do 90 | env.merge!({:this => :that}) 91 | expect(env).to eq({"this" => "that"}) 92 | end 93 | end 94 | end 95 | 96 | describe '#with_clean' do 97 | it 'returns the return value of the given block' do 98 | expect(env.with_clean { 'nice' }).to eq('nice') 99 | end 100 | 101 | it 'removes BUNDLE_GEMFILE from env' do 102 | env.with_clean do 103 | expect(ENV).to_not have_key('BUNDLE_GEMFILE') 104 | end 105 | end 106 | 107 | it 'removes RUBYOPT from env' do 108 | env.with_clean do 109 | expect(ENV).to_not have_key('RUBYOPT') 110 | end 111 | end 112 | 113 | it 'removes BUNDLE_BIN_PATH from env' do 114 | env.with_clean do 115 | expect(ENV).to_not have_key('BUNDLE_BIN_PATH') 116 | end 117 | end 118 | 119 | it 'removes GEM_HOME from env' do 120 | env.with_clean do 121 | expect(ENV).to_not have_key('GEM_HOME') 122 | end 123 | end 124 | 125 | it 'removes GEM_PATH from env' do 126 | env.with_clean do 127 | expect(ENV).to_not have_key('GEM_PATH') 128 | end 129 | end 130 | 131 | it 'restores the old environment' do 132 | env.with_clean { } 133 | 134 | expect(ENV).to have_key('GEM_HOME') 135 | end 136 | 137 | it 'adds set environment variables' do 138 | env['GEM_PATH'] = '/custom/path' 139 | env['FOO'] = 'bar' 140 | 141 | env.with_clean do 142 | expect(ENV['GEM_PATH']).to eq('/custom/path') 143 | expect(ENV['FOO']).to eq('bar') 144 | end 145 | end 146 | 147 | it 'removes custom variables as well' do 148 | env['FOO'] = 'bar' 149 | 150 | env.with_clean { } 151 | 152 | expect(ENV).to_not have_key('FOO') 153 | end 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /spec/facts_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/facts' 3 | 4 | shared_context "mock facts" do |facts = {}| 5 | before do 6 | facts.each_pair do |k, v| 7 | allow(Facter).to receive(:value).with(k).and_return(v) 8 | end 9 | end 10 | end 11 | 12 | describe "Facts" do 13 | before do 14 | FPM::Cookery::Facts.reset! 15 | end 16 | 17 | describe "arch" do 18 | include_context "mock facts", { :architecture => 'x86_64' } 19 | 20 | it "returns the current architecture" do 21 | expect(FPM::Cookery::Facts.arch).to eq(:x86_64) 22 | end 23 | end 24 | 25 | describe "lsbcodename" do 26 | context "where lsbcodename is present" do 27 | include_context "mock facts", { :lsbcodename => 'trusty' } 28 | 29 | it "returns the current platforms codename" do 30 | expect(FPM::Cookery::Facts.lsbcodename).to eq :trusty 31 | end 32 | end 33 | 34 | context "where lsbcodename is not present but lsbdistcodename is" do 35 | include_context "mock facts", { :lsbcodename => nil, :lsbdistcodename => 'trusty' } 36 | 37 | it "returns nil" do 38 | expect(FPM::Cookery::Facts.lsbcodename).to eq :trusty 39 | end 40 | end 41 | end 42 | 43 | describe "platform" do 44 | include_context "mock facts", { :operatingsystem => 'CentOS' } 45 | 46 | it "is using Facter to autodetect the platform" do 47 | expect(FPM::Cookery::Facts.platform).to eq(:centos) 48 | end 49 | 50 | it "can be set" do 51 | FPM::Cookery::Facts.platform = 'CentOS' 52 | expect(FPM::Cookery::Facts.platform).to eq(:centos) 53 | end 54 | end 55 | 56 | describe "osrelease" do 57 | include_context "mock facts", { :operatingsystemrelease => '6.5' } 58 | 59 | it "returns the operating system release version" do 60 | expect(FPM::Cookery::Facts.osrelease).to eq('6.5') 61 | end 62 | end 63 | 64 | describe "osmajorrelease" do 65 | include_context "mock facts", { :operatingsystemmajrelease => '6' } 66 | 67 | it "returns the operating system major release version" do 68 | expect(FPM::Cookery::Facts.osmajorrelease).to eq('6') 69 | end 70 | end 71 | 72 | describe "osfamily" do 73 | include_context "mock facts", { :osfamily => 'RedHat' } 74 | 75 | it "is using Facter to autodetect the osfamily" do 76 | expect(FPM::Cookery::Facts.osfamily).to eq(:redhat) 77 | end 78 | 79 | it "can be set" do 80 | FPM::Cookery::Facts.platform = 'RedHat' 81 | expect(FPM::Cookery::Facts.osfamily).to eq(:redhat) 82 | end 83 | end 84 | 85 | describe "target" do 86 | 87 | describe "with os family RedHat" do 88 | it "returns rpm" do 89 | FPM::Cookery::Facts.osfamily = 'RedHat' 90 | expect(FPM::Cookery::Facts.target).to eq(:rpm) 91 | end 92 | end 93 | 94 | describe "with os family Suse" do 95 | it "returns rpm" do 96 | FPM::Cookery::Facts.osfamily = 'Suse' 97 | expect(FPM::Cookery::Facts.target).to eq(:rpm) 98 | end 99 | end 100 | 101 | describe "with os family Debian" do 102 | it "returns deb" do 103 | FPM::Cookery::Facts.osfamily = 'Debian' 104 | expect(FPM::Cookery::Facts.target).to eq(:deb) 105 | end 106 | end 107 | 108 | describe "with os family Darwin" do 109 | it "returns osxpkg" do 110 | FPM::Cookery::Facts.osfamily = 'Darwin' 111 | expect(FPM::Cookery::Facts.target).to eq(:osxpkg) 112 | end 113 | end 114 | 115 | describe "with os family Alpine" do 116 | it "returns apk" do 117 | FPM::Cookery::Facts.osfamily = 'Alpine' 118 | expect(FPM::Cookery::Facts.target).to eq(:apk) 119 | end 120 | end 121 | 122 | describe "with an unknown os family" do 123 | it "returns nil" do 124 | FPM::Cookery::Facts.osfamily = '___X___' 125 | expect(FPM::Cookery::Facts.target).to eq(nil) 126 | end 127 | end 128 | 129 | it "can be set" do 130 | FPM::Cookery::Facts.target = 'rpm' 131 | expect(FPM::Cookery::Facts.target).to eq(:rpm) 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /spec/fixtures/hiera_config/CentOS.yaml: -------------------------------------------------------------------------------- 1 | post_install: '%{scope("workdir")}/fix_ldconfig.sh' 2 | -------------------------------------------------------------------------------- /spec/fixtures/hiera_config/common.yaml: -------------------------------------------------------------------------------- 1 | name: fake_package 2 | description: > 3 | For testing purposes on %{scope("platform")} only - you've been warned ;) 4 | version: 1.0.2 5 | source: 6 | - http://www.example.com/archive/%{hiera("name")}-%{hiera("version")}.tar.gz 7 | - :with: :git 8 | 9 | environment: 10 | PREFIX: '/opt' 11 | 12 | post_install: '%{scope("workdir")}/default.sh' 13 | -------------------------------------------------------------------------------- /spec/fixtures/hiera_config/custom.yaml: -------------------------------------------------------------------------------- 1 | environment: 2 | PREFIX: '/usr/local' 3 | AUTOMATED_TESTING: 1 4 | -------------------------------------------------------------------------------- /spec/fixtures/hiera_config/rpm.yaml: -------------------------------------------------------------------------------- 1 | depends: 2 | - on 3 | - some 4 | - rhel 5 | - specific 6 | - packages 7 | 8 | build_depends: 9 | - on 10 | - having 11 | - devel-prefixed 12 | - libraries 13 | -------------------------------------------------------------------------------- /spec/fixtures/test-config-1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | maintainer: "John Doe " 3 | -------------------------------------------------------------------------------- /spec/fixtures/test-source-1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernd/fpm-cookery/d21562a7e8a4d207b2ff1e260f03a4ac3c3925e8/spec/fixtures/test-source-1.0.tar.gz -------------------------------------------------------------------------------- /spec/hiera_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/book' 3 | require 'fpm/cookery/book_hook' 4 | require 'fpm/cookery/facts' 5 | require 'fpm/cookery/hiera' 6 | require 'fpm/cookery/recipe' 7 | require 'hiera/fpm_cookery_logger' 8 | require 'facter' 9 | 10 | describe 'Hiera' do 11 | describe 'Defaults' do 12 | subject { FPM::Cookery::Hiera::Defaults } 13 | 14 | describe '.hiera_logger' do 15 | it 'provides a default logger key for Hiera' do 16 | expect(subject.hiera_logger).to eq 'fpm_cookery' 17 | end 18 | end 19 | 20 | describe '.hiera_hierarchy' do 21 | it 'provides a default lookup hierarchy for Hiera' do 22 | expect(subject.hiera_hierarchy).to contain_exactly('common') 23 | end 24 | end 25 | 26 | describe '.hiera_backends' do 27 | it 'provides a default backend list for Hiera' do 28 | expect(subject.hiera_backends).to contain_exactly(:yaml, :json) 29 | end 30 | end 31 | end 32 | 33 | describe 'Fpm_cookery_logger' do 34 | it 'aliases Hiera::Fpm_cookery_logger to FPM::Cookery::Log::Hiera' do 35 | expect(Hiera.const_get("Fpm_cookery_logger")).to be (FPM::Cookery::Log::Hiera) 36 | end 37 | end 38 | 39 | describe 'Scope' do 40 | let(:scope) { FPM::Cookery::Hiera::Scope.new(recipe) } 41 | include_context "recipe class", __FILE__, :platform => nil, :hiera_config => nil, 42 | :data_dir => fixture_path('hiera_config') 43 | 44 | describe '[]' do 45 | context 'given an argument corresponding to a recipe class method' do 46 | it 'returns the value returned by the class method' do 47 | expect(scope['workdir']).to eq(recipe.workdir) 48 | end 49 | end 50 | 51 | context 'given an argument corresponding to an FPM::Cookery::Fact class method' do 52 | it 'returns the value returned by the class method' do 53 | expect(scope['osmajorrelease']).to eq(FPM::Cookery::Facts.osmajorrelease) 54 | end 55 | end 56 | 57 | context 'given an otherwise unresolvable argument' do 58 | it 'consults Facter' do 59 | expect(scope['processorcount']).to eq(Facter['processorcount'].value) 60 | end 61 | end 62 | end 63 | end 64 | 65 | describe '#lookup' do 66 | include_context "recipe class", __FILE__, :platform => nil, :hiera_config => nil, 67 | :data_dir => fixture_path('hiera_config') 68 | 69 | context "given default options and unset `platform' fact" do 70 | it "returns values from `common.yaml' only" do 71 | expect(recipe.lookup('environment')).to eq('PREFIX' => '/opt') 72 | expect(recipe.lookup('version')).to eq('1.0.2') 73 | expect(recipe.lookup('post_install')).to eq((recipe.workdir / 'default.sh').to_s) 74 | end 75 | end 76 | 77 | context 'given a platform' do 78 | before(:each) { allow(config).to receive(:platform).and_return('CentOS') } 79 | 80 | it "prefers values from `\#{platform}.yaml'" do 81 | expect(recipe.lookup('post_install')).to eq((recipe.workdir / 'fix_ldconfig.sh').to_s) 82 | end 83 | end 84 | 85 | context 'given a value for ENV["FPM_HIERARCHY"]' do 86 | around(:each) do |example| 87 | ENV['FPM_HIERARCHY'] = env 88 | example.run 89 | ENV.delete('FPM_HIERARCHY') 90 | end 91 | 92 | context 'that does not correspond to an existing data file' do 93 | let(:env) { 'fake' } 94 | 95 | it 'uses data from the default data file' do 96 | expect(recipe.lookup('environment')).to eq('PREFIX' => '/opt') 97 | end 98 | end 99 | 100 | context 'that corresponds to an existing data file' do 101 | let(:env) { 'custom' } 102 | 103 | it 'prefers data from that file' do 104 | expect(recipe.lookup('environment')).to eq('PREFIX' => '/usr/local', 'AUTOMATED_TESTING' => 1) 105 | end 106 | end 107 | end 108 | end 109 | 110 | describe "#new" do 111 | context "given a string as the option to `:config'" do 112 | let(:hiera) { FPM::Cookery::Hiera::Instance.new(recipe, :config => filename) } 113 | 114 | let(:recipe) do 115 | double('Recipe').as_null_object 116 | end 117 | 118 | context "that does not refer to an existing hiera.yaml file" do 119 | let(:filename) { "/probably/does/not/exist/pretty/sure/anyway" } 120 | 121 | it "raises an error when Hiera config file does not exist" do 122 | expect { hiera }.to raise_error(RuntimeError) 123 | end 124 | end 125 | 126 | context "that refers to an existing hiera.yaml file" do 127 | let(:filename) { 'hiera.yaml' } 128 | 129 | around(:each) do |example| 130 | Dir.mktmpdir do |dir| 131 | Dir.chdir(dir) do 132 | File.open(filename, 'w') do |config_file| 133 | config_file.print <<-CONFIG_FILE 134 | :backends: 135 | - json 136 | :json: 137 | :datadir: '/hey/i/am/a/datadir' 138 | :hierarchy: 139 | - yakko 140 | - wakko 141 | - dot 142 | CONFIG_FILE 143 | end 144 | 145 | example.run 146 | end 147 | end 148 | end 149 | 150 | it "loads settings from that file" do 151 | expect(hiera.config[:backends]).to contain_exactly('json') 152 | expect(hiera.config[:json]).to eq(:datadir => '/hey/i/am/a/datadir') 153 | expect(hiera.config[:hierarchy]).to contain_exactly(*%w{yakko wakko dot}) 154 | end 155 | end 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /spec/inheritable_attr_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/inheritable_attr' 3 | require 'fpm/cookery/path' 4 | 5 | def dsl_klass(name = nil) 6 | (klass = Class.new).send(:extend, FPM::Cookery::InheritableAttr) 7 | klass.class_eval(&Proc.new) if block_given? 8 | klass 9 | end 10 | 11 | shared_examples 'attribute registration' do |attr_method, registry_method| 12 | context "given an attribute created with .#{attr_method}" do 13 | it "registers the attribute in the .#{registry_method} list" do 14 | klass = Class.new do 15 | extend FPM::Cookery::InheritableAttr 16 | 17 | instance_eval %Q{ 18 | #{attr_method} :example_attr 19 | } 20 | end 21 | 22 | expect(klass).to respond_to(registry_method) 23 | expect(klass.send(registry_method)).to include(:example_attr) 24 | end 25 | end 26 | end 27 | 28 | shared_context 'class inheritance' do 29 | let(:superklass) { dsl_klass } 30 | let(:subklass) { Class.new(superklass) } 31 | end 32 | 33 | shared_examples 'attribute inheritance' do |attr_method, default_value, attr_name = :example_attr| 34 | # A default implementation. Useful for +.attr_rw+, but should probably be 35 | # overridden for the other DSL methods. 36 | let(:attr_setter) { 37 | Class.new do 38 | def self.call(k, m, v) 39 | k.send(m, v) 40 | end 41 | end 42 | } 43 | 44 | describe 'created attribute' do 45 | it 'is defined on class' do 46 | expect(superklass).to respond_to(attr_name) 47 | end 48 | 49 | it "is #{default_value.inspect} by default" do 50 | expect(superklass.send(attr_name)).to eq(default_value) 51 | end 52 | 53 | it 'sets the attribute' do 54 | attr_setter.(superklass, attr_name, value) 55 | expect(superklass.send(attr_name)).to eq value 56 | end 57 | end 58 | 59 | describe 'child class' do 60 | describe 'inherited attribute' do 61 | it 'inherits its value from the superclass' do 62 | attr_setter.(superklass, attr_name, value) 63 | expect(subklass.send(attr_name)).to eq value 64 | end 65 | 66 | context 'when altered' do 67 | it 'does not propagate to the superclass' do 68 | attr_setter.(superklass, attr_name, value) 69 | attr_setter.(subklass, attr_name, child_value) 70 | expect(superklass.send(attr_name)).to eq value 71 | end 72 | end 73 | end 74 | end 75 | end 76 | 77 | shared_context 'inheritable attributes' do |attr_method, default_value, attr_name = :example_attr| 78 | include_context 'class inheritance' 79 | include_examples 'attribute inheritance', attr_method, default_value 80 | 81 | before(:example) do 82 | missing = [:value, :child_value].reject { |m| respond_to?(m) } 83 | raise "Missing required methods: #{missing.join(', ')}" unless missing.empty? 84 | 85 | superklass.instance_eval do 86 | send(attr_method, attr_name) 87 | end 88 | end 89 | end 90 | 91 | describe FPM::Cookery::InheritableAttr do 92 | describe '.register_attrs' do 93 | describe '.attr_rw' do 94 | include_examples 'attribute registration', 'attr_rw', 'scalar_attrs' 95 | end 96 | 97 | describe '.attr_rw_list' do 98 | include_examples 'attribute registration', 'attr_rw_list', 'list_attrs' 99 | end 100 | 101 | describe '.attr_rw_hash' do 102 | include_examples 'attribute registration', 'attr_rw_hash', 'hash_attrs' 103 | end 104 | 105 | describe '.attr_rw_path' do 106 | include_examples 'attribute registration', 'attr_rw_path', 'path_attrs' 107 | end 108 | end 109 | 110 | describe '.attr_rw' do 111 | include_context 'inheritable attributes', 'attr_rw', nil do 112 | let(:value) { 'that' } 113 | let(:child_value) { 'the other' } 114 | end 115 | end 116 | 117 | describe '.attr_rw_list' do 118 | include_context 'inheritable attributes', 'attr_rw_list', [] do 119 | let(:attr_setter) { 120 | Class.new do 121 | def self.call(k, m, v) 122 | k.send(m, *v) 123 | end 124 | end 125 | } 126 | 127 | let(:value) { %w{so la ti do} } 128 | let(:child_value) { %w{fee fi fo fum} } 129 | end 130 | end 131 | 132 | describe '.attr_rw_hash' do 133 | include_context 'inheritable attributes', 'attr_rw_hash', {} do 134 | let(:value) { {:name => 'mike', :rank => 'colonel' } } 135 | let(:child_value) { {:name => 'mike', :favorite_color => 'puce' } } 136 | end 137 | 138 | describe 'created attribute' do 139 | before do 140 | superklass.instance_eval do 141 | attr_rw_hash :metadata 142 | end 143 | 144 | superklass.metadata({ :radius => 4.172, :weight => 4 }) 145 | end 146 | 147 | it 'merges its argument into the existing attribute value' do 148 | superklass.metadata({ :weight => 7, :height => 12.3 }) 149 | expect(superklass.metadata).to eq({ 150 | :height => 12.3, 151 | :weight => 7, 152 | :radius => 4.172 153 | }) 154 | end 155 | 156 | it 'supports []= assignment' do 157 | expect { superklass.metadata[:age] = 10000 }.not_to raise_error 158 | expect(superklass.metadata).to include(:age => 10000) 159 | end 160 | 161 | context 'from child class' do 162 | it 'does not propagate changes to the parent class' do 163 | superklass.metadata[:tags] = %w{a b c} 164 | subklass.metadata[:tags] << 'd' 165 | expect(superklass.metadata[:tags]).not_to include('d') 166 | end 167 | end 168 | end 169 | end 170 | 171 | describe '.attr_rw_path' do 172 | include_context 'inheritable attributes', 'attr_rw_path', nil do 173 | let(:attr_setter) { 174 | Class.new do 175 | def self.call(k, m, v) 176 | k.send(:"#{m}=", v) 177 | end 178 | end 179 | } 180 | 181 | let(:value) { FPM::Cookery::Path.new('/var/spool') } 182 | let(:child_value) { FPM::Cookery::Path.new('/proc/self') } 183 | end 184 | 185 | it 'returns an FPM::Cookery::Path object' do 186 | superklass.attr_rw_path :home 187 | superklass.home = "/where/the/heart/is" 188 | expect(superklass.home).to be_an(FPM::Cookery::Path) 189 | end 190 | end 191 | 192 | describe '.inherit_for' do 193 | include_context 'class inheritance' 194 | 195 | context 'given a method name that the superclass does not respond to' do 196 | it 'returns nil' do 197 | expect(superklass).not_to respond_to(:blech) 198 | expect(FPM::Cookery::InheritableAttr.inherit_for(subklass, :blech)).to be nil 199 | end 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /spec/package_dir_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/package/dir' 3 | require 'fpm/cookery/recipe' 4 | 5 | describe FPM::Cookery::Package::Dir do 6 | let(:config) { { :input => input } } 7 | let(:input) { %w{foo bar baz} } 8 | 9 | let(:recipe) do 10 | Class.new(FPM::Cookery::Recipe) do 11 | description 'a test package' 12 | name 'boo' 13 | version '8.5' 14 | end 15 | end 16 | 17 | let(:package) do 18 | described_class.new(recipe, config) 19 | end 20 | 21 | let(:fpm) do 22 | double('FPM').as_null_object 23 | end 24 | 25 | before do 26 | allow(recipe).to receive(:config).and_return(double('Config').as_null_object) 27 | allow(FPM::Package::Dir).to receive(:new).and_return(fpm) 28 | end 29 | 30 | context '#package_input' do 31 | it 'calls the fpm package creation with a clean environment' do 32 | expect(fpm).to receive(:input).exactly(input.length).times 33 | 34 | package # Trigger object creation and package_input call. 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/package_gem_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/package/gem' 3 | require 'fpm/cookery/recipe' 4 | 5 | describe FPM::Cookery::Package::Gem do 6 | let(:config) { {} } 7 | 8 | let(:recipe) do 9 | Class.new(FPM::Cookery::RubyGemRecipe) do 10 | description 'a test package' 11 | name 'foo' 12 | version '1.1.1' 13 | end 14 | end 15 | 16 | let(:package) do 17 | described_class.new(recipe, config) 18 | end 19 | 20 | let(:fpm) do 21 | double('FPM').as_null_object 22 | end 23 | 24 | before do 25 | allow(FPM::Package::Gem).to receive(:new).and_return(fpm) 26 | end 27 | 28 | context '#package_input' do 29 | it 'calls the fpm package creation with a clean environment' do 30 | expect(fpm).to receive(:input) do |*args| 31 | expect(ENV).to_not have_key('GEM_HOME') 32 | end 33 | 34 | package # Trigger object creation and package_input call. 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/package_maintainer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/package/maintainer' 3 | require 'ostruct' 4 | 5 | describe 'Maintainer' do 6 | let(:klass) { FPM::Cookery::Package::Maintainer } 7 | 8 | let(:recipe) { OpenStruct.new } 9 | let(:config) { {} } 10 | 11 | let(:maintainer) { klass.new(recipe, config) } 12 | 13 | def with_env_stub(env) 14 | old_env = ENV.to_hash 15 | env.each do |key, value| 16 | ENV[key] = value 17 | end 18 | yield 19 | ensure 20 | ENV.replace(old_env) 21 | end 22 | 23 | before do 24 | allow(FPM::Cookery::Shellout).to receive(:git_config_get).with('user.name').and_return('John Doe') 25 | allow(FPM::Cookery::Shellout).to receive(:git_config_get).with('user.email').and_return('john@example.com') 26 | end 27 | 28 | describe '#to_s' do 29 | context 'with maintainer set in recipe' do 30 | it 'returns the recipe maintainer' do 31 | recipe.maintainer = 'Foo ' 32 | expect(maintainer.to_s).to eq('Foo ') 33 | end 34 | end 35 | 36 | context 'with maintainer set in config' do 37 | it 'returns the config maintainer' do 38 | config[:maintainer] = 'Foo ' 39 | 40 | expect(maintainer.to_s).to eq('Foo ') 41 | end 42 | end 43 | 44 | context 'with maintainer in config and recipe' do 45 | it 'returns the config maintainer' do 46 | recipe.maintainer = 'Foo ' 47 | config[:maintainer] = 'Jane Doe ' 48 | 49 | expect(maintainer.to_s).to eq('Jane Doe ') 50 | end 51 | end 52 | 53 | context 'without any maintainer set' do 54 | it 'returns the maintainer from git' do 55 | expect(maintainer.to_s).to eq('John Doe ') 56 | end 57 | end 58 | 59 | context 'without valid git data' do 60 | before do 61 | allow(FPM::Cookery::Shellout).to receive(:git_config_get).and_raise( 62 | FPM::Cookery::Shellout::CommandFailed, 'whoops!' 63 | ) 64 | 65 | allow(Socket).to receive(:gethostname).and_return('hostname') 66 | end 67 | 68 | it 'returns a default maintainer' do 69 | with_env_stub('USER' => 'john') do 70 | expect(maintainer.to_s).to eq('') 71 | end 72 | end 73 | end 74 | end 75 | 76 | describe '#to_str' do 77 | it 'converts the maintainer object to a string' do 78 | recipe.maintainer = 'Foo ' 79 | 80 | expect(maintainer.to_str).to eq('Foo ') 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/package_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/package/package' 3 | 4 | require 'fpm/package/dir' 5 | require 'fpm/cookery/recipe' 6 | 7 | describe 'Package' do 8 | let(:config) { {} } 9 | 10 | let(:package_bare_class) do 11 | Class.new(FPM::Cookery::Package::Package) 12 | end 13 | 14 | let(:package_bare) do 15 | package_bare_class.new(recipe, config) 16 | end 17 | 18 | let(:package_class) do 19 | Class.new(FPM::Cookery::Package::Package) { 20 | attr_reader :test_package_setup_run, :test_package_input_run 21 | 22 | def fpm_object 23 | FPM::Package::Dir.new 24 | end 25 | 26 | def package_setup 27 | @test_package_setup_run = true 28 | end 29 | 30 | def package_input 31 | @test_package_input_run = true 32 | end 33 | } 34 | end 35 | 36 | let(:package) do 37 | package_class.new(recipe, config) 38 | end 39 | 40 | let(:recipe) do 41 | Class.new(FPM::Cookery::Recipe) do 42 | description 'a test package' 43 | name 'foo' 44 | homepage 'http://example.com' 45 | section 'langs' 46 | arch 'all' 47 | depends 'ab', 'c' 48 | conflicts 'xz', 'y' 49 | provides 'foo-package' 50 | replaces 'foo-old' 51 | config_files '/etc/foo.conf' 52 | directories '/var/lib/foo', '/var/cache/foo' 53 | end 54 | end 55 | 56 | describe 'fpm package object initialization' do 57 | it 'sets name' do 58 | expect(package.fpm.name).to eq('foo') 59 | end 60 | 61 | it 'sets url' do 62 | expect(package.fpm.url).to eq('http://example.com') 63 | end 64 | 65 | it 'sets category' do 66 | expect(package.fpm.category).to eq('langs') 67 | end 68 | 69 | context 'without section set' do 70 | it 'sets category to "optional"' do 71 | recipe.instance_variable_set(:@section, nil) 72 | 73 | expect(package.fpm.category).to eq('optional') 74 | end 75 | end 76 | 77 | it 'sets the description' do 78 | expect(package.fpm.description).to eq('a test package') 79 | end 80 | 81 | it 'sets the architecture' do 82 | expect(package.fpm.architecture).to eq('all') 83 | end 84 | 85 | it 'sets the dependencies' do 86 | expect(package.fpm.dependencies).to eq(['ab', 'c']) 87 | end 88 | 89 | it 'sets the conflicts' do 90 | expect(package.fpm.conflicts).to eq(['xz', 'y']) 91 | end 92 | 93 | it 'sets the provides' do 94 | expect(package.fpm.provides).to eq(['foo-package']) 95 | end 96 | 97 | it 'sets the replaces' do 98 | expect(package.fpm.replaces).to eq(['foo-old']) 99 | end 100 | 101 | it 'sets the config_files' do 102 | expect(package.fpm.config_files).to eq(['/etc/foo.conf']) 103 | end 104 | 105 | it 'sets the directories' do 106 | expect(package.fpm.directories).to eq(['/var/lib/foo', '/var/cache/foo']) 107 | end 108 | 109 | describe 'attributes' do 110 | let(:attributes) { package.fpm.attributes } 111 | 112 | it 'sets deb_compression' do 113 | expect(attributes[:deb_compression]).to eq('gz') 114 | end 115 | 116 | it 'sets deb_user' do 117 | expect(attributes[:deb_user]).to eq('root') 118 | end 119 | 120 | it 'sets deb_group' do 121 | expect(attributes[:deb_group]).to eq('root') 122 | end 123 | 124 | it 'sets rpm_compression' do 125 | expect(attributes[:rpm_compression]).to eq('gzip') 126 | end 127 | 128 | it 'sets rpm_digest' do 129 | expect(attributes[:rpm_digest]).to eq('md5') 130 | end 131 | 132 | it 'sets rpm_user' do 133 | expect(attributes[:rpm_user]).to eq('root') 134 | end 135 | 136 | it 'sets rpm_group' do 137 | expect(attributes[:rpm_group]).to eq('root') 138 | end 139 | 140 | it 'sets rpm_defattrfile' do 141 | expect(attributes[:rpm_defattrfile]).to eq('-') 142 | end 143 | 144 | it 'sets rpm_defattrdir' do 145 | expect(attributes[:rpm_defattrdir]).to eq('-') 146 | end 147 | 148 | it 'sets excludes' do 149 | expect(attributes[:excludes]).to eq([]) 150 | end 151 | end 152 | 153 | describe '.fpm_attributes' do 154 | let(:recipe) do 155 | # Ensure Recipe.inherited() to be called before handling fpm_attributes. 156 | recipe_class = Class.new(FPM::Cookery::Recipe) 157 | recipe_class.instance_eval do 158 | fpm_attributes :deb_user=>'deb_user', :rpm_user=>'rpm_user' 159 | end 160 | recipe_class 161 | end 162 | 163 | it 'overwrites default fpm attributes in Package class' do 164 | expect(package.fpm.attributes).to include({:deb_user=>'deb_user', :rpm_user=>'rpm_user'}) 165 | end 166 | end 167 | 168 | it 'calls the package_setup method' do 169 | expect(package.test_package_setup_run).to eq(true) 170 | end 171 | 172 | it 'calls the package_input method' do 173 | expect(package.test_package_input_run).to eq(true) 174 | end 175 | 176 | context 'without package_input method defined' do 177 | before do 178 | package_bare_class.class_eval do 179 | def fpm_class 180 | end 181 | end 182 | end 183 | 184 | it 'raises a MethodNotImplemented error' do 185 | expect { 186 | package_bare 187 | }.to raise_error(FPM::Cookery::Error::MethodNotImplemented) 188 | end 189 | end 190 | 191 | context 'without fpm_object method defined' do 192 | before do 193 | package_bare_class.class_eval do 194 | def package_input 195 | end 196 | end 197 | end 198 | 199 | it 'raises a MethodNotImplemented error' do 200 | expect { 201 | package_bare 202 | }.to raise_error(FPM::Cookery::Error::MethodNotImplemented) 203 | end 204 | end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /spec/package_version_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/package/version' 3 | require 'ostruct' 4 | 5 | describe 'Version' do 6 | let(:target) { 'deb' } 7 | 8 | let(:klass) { FPM::Cookery::Package::Version } 9 | 10 | let(:recipe) { OpenStruct.new(:version => '1.2.0') } 11 | let(:config) { {} } 12 | 13 | let(:version) { klass.new(recipe, target, config) } 14 | 15 | describe '#vendor_delimiter' do 16 | context 'with target deb' do 17 | it 'returns "+"' do 18 | expect(version.vendor_delimiter).to eq('+') 19 | end 20 | end 21 | 22 | context 'with target rpm' do 23 | let(:target) { 'rpm' } 24 | 25 | it 'returns "."' do 26 | expect(version.vendor_delimiter).to eq('.') 27 | end 28 | end 29 | 30 | context 'with unknown target' do 31 | let(:target) { '_foo_' } 32 | 33 | it 'returns "-"' do 34 | expect(version.vendor_delimiter).to eq('-') 35 | end 36 | end 37 | end 38 | 39 | describe '#vendor' do 40 | context 'with config.vendor set' do 41 | it 'returns the config.vendor value' do 42 | config[:vendor] = 'foo' 43 | 44 | expect(version.vendor).to eq('foo') 45 | end 46 | end 47 | 48 | context 'with recipe.vendor set' do 49 | it 'returns the recipe.vendor value' do 50 | recipe.vendor = 'bar' 51 | 52 | expect(version.vendor).to eq('bar') 53 | end 54 | end 55 | 56 | context 'with config.vendor and recipe.vendor set' do 57 | it 'returns the config.vendor value' do 58 | config[:vendor] = 'foo' 59 | recipe.vendor = 'bar' 60 | 61 | expect(version.vendor).to eq('foo') 62 | end 63 | end 64 | end 65 | 66 | describe '#revision' do 67 | it 'returns the recipe.revision value' do 68 | recipe.revision = 24 69 | 70 | expect(version.revision).to eq(24) 71 | end 72 | end 73 | 74 | describe '#epoch' do 75 | it 'returns the version epoch' do 76 | recipe.version = '4:1.2.3' 77 | 78 | expect(version.epoch).to eq('4') 79 | end 80 | 81 | context 'without epoch' do 82 | it 'returns nil' do 83 | recipe.version = '1.2.3' 84 | 85 | expect(version.epoch).to eq(nil) 86 | end 87 | end 88 | end 89 | 90 | describe '#to_s' do 91 | it 'returns a string representation of the version' do 92 | recipe.version = '2.1.3' 93 | recipe.vendor = 'testing1' 94 | recipe.revision = 5 95 | 96 | expect(version.to_s).to eq('2.1.3-5+testing1') 97 | end 98 | 99 | context 'with target rpm' do 100 | let(:target) { 'rpm' } 101 | 102 | it 'returns a string representation of the version' do 103 | recipe.version = '2.1.3' 104 | recipe.vendor = 'testing1' 105 | recipe.revision = 5 106 | 107 | expect(version.to_s).to eq('2.1.3-5.testing1') 108 | end 109 | end 110 | 111 | context 'without vendor' do 112 | it 'returns a string representation' do 113 | recipe.version = '2.1.3' 114 | recipe.revision = 5 115 | 116 | expect(version.to_s).to eq('2.1.3-5') 117 | end 118 | 119 | context 'with target rpm' do 120 | let(:target) { 'rpm' } 121 | 122 | it 'returns a string representation of the version' do 123 | recipe.version = '2.1.3' 124 | recipe.revision = 5 125 | 126 | expect(version.to_s).to eq('2.1.3-5') 127 | end 128 | end 129 | end 130 | end 131 | 132 | describe '#to_str' do 133 | let(:target) { 'rpm' } 134 | 135 | it 'returns a string representation of the version' do 136 | recipe.version = '1.3' 137 | recipe.vendor = 'foo' 138 | recipe.revision = 1 139 | 140 | expect(version.to_str).to eq('1.3-1.foo') 141 | end 142 | end 143 | 144 | describe '#version' do 145 | context 'given a colon-delimited string in recipe.version' do 146 | context 'where version and epoch are defined' do 147 | before(:each) { recipe.version = '8675309:3.1.1'} 148 | 149 | it 'returns the version and epoch' do 150 | expect(version.version).to eq('3.1.1') 151 | expect(version.epoch).to eq('8675309') 152 | end 153 | end 154 | 155 | context 'where only version is defined' do 156 | before(:each) { recipe.version = ':2.1' } 157 | 158 | it 'returns the version and sets epoch to nil' do 159 | expect(version.version).to eq('2.1') 160 | expect(version.epoch).to be nil 161 | end 162 | end 163 | 164 | context 'where only epoch is defined' do 165 | before(:each) { recipe.version = '12345:' } 166 | 167 | it 'returns the epoch as version and sets epoch to nil' do 168 | expect(version.version).to eq('12345') 169 | expect(version.epoch).to be nil 170 | end 171 | end 172 | 173 | end 174 | 175 | context 'given a nil recipe.version' do 176 | before(:each) { recipe.version = nil } 177 | 178 | it 'returns the default version' do 179 | expect(version.version).to eq(klass::DEFAULT_VERSION) 180 | expect(version.epoch).to be nil 181 | end 182 | end 183 | 184 | context 'given a recipe.version containing the empty string' do 185 | before(:each) { recipe.version = '' } 186 | 187 | it 'returns the default version' do 188 | expect(version.version).to eq(klass::DEFAULT_VERSION) 189 | expect(version.epoch).to be nil 190 | end 191 | end 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /spec/path_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/path_helper' 3 | 4 | describe "PathHelper" do 5 | let(:helper) do 6 | Class.new { 7 | include FPM::Cookery::PathHelper 8 | 9 | def default_prefix; '/usr' end 10 | def destdir; FPM::Cookery::Path.new('/tmp/dest') end 11 | }.new 12 | end 13 | 14 | describe "path helper methods" do 15 | [ ['prefix', '/usr'], 16 | ['root_prefix', '/'], 17 | ['root', '/'], 18 | ['etc', '/etc'], 19 | ['opt', '/opt'], 20 | ['var', '/var'], 21 | ['bin', '/usr/bin'], 22 | ['doc', '/usr/share/doc'], 23 | ['include', '/usr/include'], 24 | ['info', '/usr/share/info'], 25 | ['lib', '/usr/lib'], 26 | ['libexec', '/usr/libexec'], 27 | ['man', '/usr/share/man'], 28 | ['man1', '/usr/share/man/man1'], 29 | ['man2', '/usr/share/man/man2'], 30 | ['man3', '/usr/share/man/man3'], 31 | ['man4', '/usr/share/man/man4'], 32 | ['man5', '/usr/share/man/man5'], 33 | ['man6', '/usr/share/man/man6'], 34 | ['man7', '/usr/share/man/man7'], 35 | ['man8', '/usr/share/man/man8'], 36 | ['sbin', '/usr/sbin'], 37 | ['share', '/usr/share'] ].each do |m| 38 | 39 | name, path = m 40 | 41 | describe "##{name}" do 42 | def c(path); path.gsub(%r{//}, '/'); end 43 | 44 | context "without an argument" do 45 | it "returns #{path}" do 46 | expect(helper.send(name).to_s).to eq(path) 47 | end 48 | end 49 | 50 | context "with an argument" do 51 | it "adds the argument to the path" do 52 | expect(helper.send(name, 'foo/bar').to_s).to eq(c("#{path}/foo/bar")) 53 | end 54 | end 55 | 56 | context "with a nil argument" do 57 | it "does not add anything to the path" do 58 | expect(helper.send(name, nil).to_s).to eq(path) 59 | end 60 | end 61 | 62 | context "with installing set to true" do 63 | before { helper.installing = true} 64 | 65 | it "adds the destdir as prefix" do 66 | expect(helper.send(name, 'blah').to_s).to eq(c("#{helper.destdir}#{path}/blah")) 67 | end 68 | end 69 | 70 | context "with omnibus_installing set to true" do 71 | before { helper.omnibus_installing = true } 72 | 73 | it "does not add anything to the path" do 74 | expect(helper.send(name, 'blah').to_s).to eq(c("#{path}/blah")) 75 | end 76 | end 77 | 78 | context "with omnibus_installing and installing set to true" do 79 | before { helper.omnibus_installing = true ; helper.installing = true } 80 | 81 | it "does not add anything to the path" do 82 | expect(helper.send(name, 'blah').to_s).to eq(c("#{path}/blah")) 83 | end 84 | end 85 | end 86 | end 87 | end 88 | 89 | describe "#installing?" do 90 | context "with installing set to true" do 91 | before { helper.installing = true} 92 | 93 | it "returns true" do 94 | expect(helper.installing?).to eq(true) 95 | end 96 | end 97 | 98 | context "with installing set to false" do 99 | before { helper.installing = false } 100 | 101 | it "returns true" do 102 | expect(helper.installing?).to eq(false) 103 | end 104 | end 105 | end 106 | 107 | describe "#omnibus_installing?" do 108 | context "with omnibus_installing set to true" do 109 | before { helper.omnibus_installing = true } 110 | 111 | it "returns true" do 112 | expect(helper.omnibus_installing?).to eq(true) 113 | end 114 | end 115 | 116 | context "with omnibus_installing set to false" do 117 | before { helper.omnibus_installing = false } 118 | 119 | it "returns false" do 120 | expect(helper.omnibus_installing?).to eq(false) 121 | end 122 | end 123 | end 124 | 125 | describe "#with_trueprefix" do 126 | context "with installing set to true" do 127 | before { helper.installing = true } 128 | 129 | specify "prefix returns /" do 130 | helper.with_trueprefix do 131 | expect(helper.prefix.to_s).to eq('/usr') 132 | end 133 | end 134 | 135 | it "will restore the previous installing value" do 136 | helper.with_trueprefix {} 137 | expect(helper.installing).to eq(true) 138 | end 139 | end 140 | 141 | context "with installing set to false" do 142 | before { helper.installing = false} 143 | 144 | specify "prefix returns /" do 145 | helper.with_trueprefix do 146 | expect(helper.prefix.to_s).to eq('/usr') 147 | end 148 | end 149 | 150 | it "will restore the previous installing value" do 151 | helper.with_trueprefix {} 152 | expect(helper.installing).to eq(false) 153 | end 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /spec/path_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/path' 3 | require 'tmpdir' 4 | 5 | describe "Path" do 6 | describe ".pwd" do 7 | it "returns the current dir" do 8 | Dir.chdir('/tmp') do 9 | expect(FPM::Cookery::Path.pwd.to_s).to match(%r{/tmp|/private/tmp}) 10 | end 11 | end 12 | 13 | it "adds the given path to the current dir" do 14 | Dir.chdir('/tmp') do 15 | expect(FPM::Cookery::Path.pwd('foo').to_s).to match(%r{/tmp|/private/tmp}) 16 | end 17 | end 18 | end 19 | 20 | describe "#+" do 21 | let(:path) { FPM::Cookery::Path.new('/foo') } 22 | 23 | describe "with a path fragmet" do 24 | it "returns a new concatenated path object" do 25 | expect((path + 'bar').to_s).to eq('/foo/bar') 26 | end 27 | end 28 | 29 | describe "with an absolute path" do 30 | it "overwrites the old path" do 31 | expect((path + '/bar').to_s).to eq('/bar') 32 | end 33 | end 34 | 35 | describe "with an empty fragment" do 36 | it "does't modify the path" do 37 | expect((path + '').to_s).to eq('/foo') 38 | end 39 | end 40 | end 41 | 42 | describe "#/" do 43 | let(:path) { FPM::Cookery::Path.new('/foo') } 44 | 45 | describe "with a path fragment" do 46 | it "returns a new concatenated path object" do 47 | expect((path/'bar').to_s).to eq('/foo/bar') 48 | end 49 | end 50 | 51 | describe "with an absolute path" do 52 | it "returns a new concatenated path object" do 53 | expect((path/'/baz').to_s).to eq('/foo/baz') 54 | end 55 | end 56 | 57 | describe "with a Path argument" do 58 | it "returns a new concatenated path object" do 59 | expect((path/path).to_s).to eq('/foo/foo') 60 | end 61 | end 62 | 63 | describe "with a nil argument" do 64 | it "does not modify the path" do 65 | expect((path/nil).to_s).to eq('/foo') 66 | end 67 | end 68 | end 69 | 70 | describe "#=~" do 71 | let(:path) { FPM::Cookery::Path.new('/bar/baz/quux.txt') } 72 | 73 | context "given a Regexp matching the path" do 74 | it "returns the index of the beginning of the the match" do 75 | expect(path =~ %r[/\w{4}\.\w{3}]).to eq(8) 76 | end 77 | end 78 | 79 | context "given a non-Regexp argument" do 80 | it "raises TypeError" do 81 | expect { path =~ 'this' }.to raise_error do |error| 82 | expect(error).to be_a(TypeError) 83 | expect(error.message).to match(/type\s+mismatch/) 84 | end 85 | end 86 | end 87 | 88 | end 89 | 90 | describe "#mkdir" do 91 | it "creates the directory" do 92 | dir = Dir.mktmpdir 93 | FileUtils.rm_rf(dir) 94 | expect(File.exist?(dir)).to eq(false) 95 | 96 | FPM::Cookery::Path.new(dir).mkdir 97 | expect(File.exist?(dir)).to eq(true) 98 | 99 | FileUtils.rm_rf(dir) 100 | end 101 | 102 | describe "directory exists" do 103 | it "does not throw an error" do 104 | dir = Dir.mktmpdir 105 | expect(File.exist?(dir)).to eq(true) 106 | 107 | expect(FPM::Cookery::Path.new(dir).mkdir).to eq([dir]) 108 | 109 | FileUtils.rm_rf(dir) 110 | end 111 | end 112 | end 113 | 114 | describe "#install" do 115 | describe "with an array as src" do 116 | it "installs every file in the list" do 117 | Dir.mktmpdir do |dir| 118 | path = FPM::Cookery::Path.new(dir) 119 | path.install([__FILE__, File.expand_path('../spec_helper.rb', __FILE__)]) 120 | 121 | expect(File.exist?(path/File.basename(__FILE__))).to eq(true) 122 | expect(File.exist?(path/'spec_helper.rb')).to eq(true) 123 | end 124 | end 125 | end 126 | 127 | describe "with a hash as src" do 128 | it "installs the file with a new basename" do 129 | Dir.mktmpdir do |dir| 130 | path = FPM::Cookery::Path.new(dir) 131 | path.install(File.expand_path('../spec_helper.rb', __FILE__) => 'foo.rb') 132 | 133 | expect(File.exist?(path/'foo.rb')).to eq(true) 134 | end 135 | end 136 | end 137 | 138 | describe "with a string as src" do 139 | it "installs the file" do 140 | Dir.mktmpdir do |dir| 141 | path = FPM::Cookery::Path.new(dir) 142 | path.install(File.expand_path('../spec_helper.rb', __FILE__)) 143 | 144 | expect(File.exist?(path/'spec_helper.rb')).to eq(true) 145 | end 146 | end 147 | end 148 | 149 | describe "with a new basename argument" do 150 | it "installs the file with a new basename" do 151 | Dir.mktmpdir do |dir| 152 | path = FPM::Cookery::Path.new(dir) 153 | path.install(File.expand_path('../spec_helper.rb', __FILE__), 'foo.rb') 154 | 155 | expect(File.exist?(path/'foo.rb')).to eq(true) 156 | end 157 | end 158 | end 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /spec/source_handler_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/source' 3 | require 'fpm/cookery/source_handler' 4 | 5 | describe 'SourceHandler' do 6 | let(:cachedir) { FPM::Cookery::Path.new('/tmp/cache') } 7 | let(:builddir) { FPM::Cookery::Path.new('/tmp/build') } 8 | 9 | describe "#fetchable?" do 10 | context "when using the noop source handler" do 11 | it "always returns true" do 12 | nil_source = FPM::Cookery::Source.new(nil, :with => :noop) 13 | empty_source = FPM::Cookery::Source.new('', :with => :noop) 14 | 15 | nil_handler = FPM::Cookery::SourceHandler.new(nil_source, cachedir, builddir) 16 | empty_handler = FPM::Cookery::SourceHandler.new(empty_source, cachedir, builddir) 17 | 18 | expect(nil_handler.fetchable?).to eq(true) 19 | expect(empty_handler.fetchable?).to eq(true) 20 | end 21 | end 22 | 23 | context "otherwise" do 24 | it "returns true when the source URI is non-empty" do 25 | fetchable_sources = [ 26 | ['https://somedomain.io/project-1.2-3.tar.xz'], 27 | ['https://git.mysite.cat/atonic.git', :with => :git], 28 | ['/var/src', :with => :directory], 29 | ].map { |s| FPM::Cookery::Source.new(*s) } 30 | 31 | unfetchable_sources = [ 32 | [''], 33 | [nil, :with => :git], 34 | ['', :with => :directory], 35 | ].map { |s| FPM::Cookery::Source.new(*s) } 36 | 37 | fetchable_handlers = fetchable_sources.map { |fs| FPM::Cookery::SourceHandler.new(fs, cachedir, builddir) } 38 | unfetchable_handlers = unfetchable_sources.map { |us| FPM::Cookery::SourceHandler.new(us, cachedir, builddir) } 39 | 40 | expect(fetchable_handlers.map(&:fetchable?)).to all(eq(true)) 41 | expect(unfetchable_handlers.map(&:fetchable?)).to all(eq(false)) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/source_integrity_check_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/source_integrity_check' 3 | require 'fpm/cookery/recipe' 4 | 5 | describe "SourceIntegrityCheck" do 6 | config_options = { :hiera_config => nil } 7 | include_context "recipe class", __FILE__, config_options do 8 | let(:klass) do 9 | Class.new(FPM::Cookery::Recipe) { 10 | source 'http://example.com/foo.tar.gz' 11 | } 12 | end 13 | end 14 | 15 | let(:check) { FPM::Cookery::SourceIntegrityCheck.new(recipe) } 16 | 17 | before do 18 | allow(recipe).to receive(:local_path).and_return(fixture_path('test-source-1.0.tar.gz')) 19 | end 20 | 21 | describe "without any checksum defined" do 22 | describe "#error?" do 23 | it "returns true" do 24 | expect(check.error?).to eq(true) 25 | end 26 | end 27 | 28 | describe "#checksum_missing?" do 29 | it "returns true" do 30 | expect(check.checksum_missing?).to eq(true) 31 | end 32 | end 33 | 34 | it "has checksum_expected set to nil" do 35 | expect(check.checksum_expected).to eq(nil) 36 | end 37 | 38 | it "has checksum_actual set to the sha256 checksum" do 39 | expect(check.checksum_actual).to eq('285a6b8098ecc9040ece8f621e37c20edba39545c5d195c4894f410ed9d44b22') 40 | end 41 | 42 | it "has filename set" do 43 | expect(check.filename).to eq(fixture_path('test-source-1.0.tar.gz')) 44 | end 45 | 46 | it "has digest set to nil" do 47 | expect(check.digest).to eq(:sha256) 48 | end 49 | end 50 | 51 | describe "with a correct sha512 checksum defined" do 52 | describe "#error?" do 53 | it "returns false" do 54 | allow(recipe).to receive(:sha512).and_return('488f02db910d6a12f194ed62d7e6a89d9e408c8354acb875cf81c5fb284022cd320d4803a968f8754f5cc53797d515994619f669d359e3b4f989801a39ee6ffd') 55 | 56 | expect(check.error?).to eq(false) 57 | end 58 | end 59 | end 60 | 61 | describe "with a wrong sha512 checksum defined" do 62 | before do 63 | allow(recipe).to receive(:sha512).and_return('xxxx02db910d6a12f194ed62d7e6a89d9e408c8354acb875cf81c5fb284022cd320d4803a968f8754f5cc53797d515994619f669d359e3b4f989801a39ee6ffd') 64 | end 65 | 66 | describe "#error?" do 67 | it "returns true" do 68 | expect(check.error?).to eq(true) 69 | end 70 | end 71 | 72 | it "has checksum_expected set to the expected checksum" do 73 | expect(check.checksum_expected).to eq('xxxx02db910d6a12f194ed62d7e6a89d9e408c8354acb875cf81c5fb284022cd320d4803a968f8754f5cc53797d515994619f669d359e3b4f989801a39ee6ffd') 74 | end 75 | 76 | it "has checksum_actual set to the actual checksum" do 77 | expect(check.checksum_actual).to eq('488f02db910d6a12f194ed62d7e6a89d9e408c8354acb875cf81c5fb284022cd320d4803a968f8754f5cc53797d515994619f669d359e3b4f989801a39ee6ffd') 78 | end 79 | 80 | it "has filename set" do 81 | expect(check.filename).to eq(fixture_path('test-source-1.0.tar.gz')) 82 | end 83 | 84 | it "has digest set to :sha512" do 85 | expect(check.digest).to eq(:sha512) 86 | end 87 | end 88 | 89 | describe "with a correct sha256 checksum defined" do 90 | describe "#error?" do 91 | it "returns false" do 92 | allow(recipe).to receive(:sha256).and_return('285a6b8098ecc9040ece8f621e37c20edba39545c5d195c4894f410ed9d44b22') 93 | 94 | expect(check.error?).to eq(false) 95 | end 96 | end 97 | end 98 | 99 | describe "with a wrong sha256 checksum defined" do 100 | before do 101 | allow(recipe).to receive(:sha256).and_return('xxxx6b8098ecc9040ece8f621e37c20edba39545c5d195c4894f410ed9d44b22') 102 | end 103 | 104 | describe "#error?" do 105 | it "returns true" do 106 | expect(check.error?).to eq(true) 107 | end 108 | end 109 | 110 | it "has checksum_expected set to the expected checksum" do 111 | expect(check.checksum_expected).to eq('xxxx6b8098ecc9040ece8f621e37c20edba39545c5d195c4894f410ed9d44b22') 112 | end 113 | 114 | it "has checksum_actual set to the actual checksum" do 115 | expect(check.checksum_actual).to eq('285a6b8098ecc9040ece8f621e37c20edba39545c5d195c4894f410ed9d44b22') 116 | end 117 | 118 | it "has filename set" do 119 | expect(check.filename).to eq(fixture_path('test-source-1.0.tar.gz')) 120 | end 121 | 122 | it "has digest set to :sha256" do 123 | expect(check.digest).to eq(:sha256) 124 | end 125 | end 126 | 127 | describe "with a correct sha1 checksum defined" do 128 | describe "#error?" do 129 | it "returns false" do 130 | allow(recipe).to receive(:sha1).and_return('dd4a8575c60f8122c30d21dfeed9f23f64948bf7') 131 | 132 | expect(check.error?).to eq(false) 133 | end 134 | end 135 | end 136 | 137 | describe "with a wrong sha1 checksum defined" do 138 | before do 139 | allow(recipe).to receive(:sha1).and_return('xxxx8575c60f8122c30d21dfeed9f23f64948bf7') 140 | end 141 | 142 | describe "#error?" do 143 | it "returns true" do 144 | expect(check.error?).to eq(true) 145 | end 146 | end 147 | 148 | it "has checksum_expected set to the expected checksum" do 149 | expect(check.checksum_expected).to eq('xxxx8575c60f8122c30d21dfeed9f23f64948bf7') 150 | end 151 | 152 | it "has checksum_actual set to the actual checksum" do 153 | expect(check.checksum_actual).to eq('dd4a8575c60f8122c30d21dfeed9f23f64948bf7') 154 | end 155 | 156 | it "has filename set" do 157 | expect(check.filename).to eq(fixture_path('test-source-1.0.tar.gz')) 158 | end 159 | 160 | it "has digest set to :sha1" do 161 | expect(check.digest).to eq(:sha1) 162 | end 163 | end 164 | 165 | describe "with a correct md5 checksum defined" do 166 | describe "#error?" do 167 | it "returns false" do 168 | allow(recipe).to receive(:md5).and_return('d8f1330c3d1cec72287b88b2a6c1bc91') 169 | 170 | expect(check.error?).to eq(false) 171 | end 172 | end 173 | end 174 | 175 | describe "with a wrong md5 checksum defined" do 176 | before do 177 | allow(recipe).to receive(:md5).and_return('xxxx330c3d1cec72287b88b2a6c1bc91') 178 | end 179 | 180 | describe "#error?" do 181 | it "returns true" do 182 | expect(check.error?).to eq(true) 183 | end 184 | end 185 | 186 | it "has checksum_expected set to the expected checksum" do 187 | expect(check.checksum_expected).to eq('xxxx330c3d1cec72287b88b2a6c1bc91') 188 | end 189 | 190 | it "has checksum_actual set to the actual checksum" do 191 | expect(check.checksum_actual).to eq('d8f1330c3d1cec72287b88b2a6c1bc91') 192 | end 193 | 194 | it "has filename set" do 195 | expect(check.filename).to eq(fixture_path('test-source-1.0.tar.gz')) 196 | end 197 | 198 | it "has digest set to :md5" do 199 | expect(check.digest).to eq(:md5) 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /spec/source_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/source' 3 | 4 | describe "Source" do 5 | describe "#provider?" do 6 | context "with a provider set" do 7 | it "returns true" do 8 | source = FPM::Cookery::Source.new('http://example.com/', :with => :git) 9 | 10 | expect(source.provider?).to eq(true) 11 | end 12 | end 13 | 14 | context "without a provider set" do 15 | it "returns false" do 16 | source = FPM::Cookery::Source.new('http://example.com/') 17 | 18 | expect(source.provider?).to eq(false) 19 | end 20 | end 21 | end 22 | 23 | describe "#local?" do 24 | context "with a file:// url" do 25 | it "returns true" do 26 | source = FPM::Cookery::Source.new('file:///tmp') 27 | 28 | expect(source.local?).to eq(true) 29 | end 30 | end 31 | 32 | context "with no file:// url" do 33 | it "returns false" do 34 | source = FPM::Cookery::Source.new('https://www.example.com/') 35 | 36 | expect(source.local?).to eq(false) 37 | end 38 | end 39 | end 40 | 41 | describe "#path" do 42 | it "returns the url path" do 43 | source = FPM::Cookery::Source.new('file:///opt/src/foo') 44 | 45 | expect(source.path).to eq('/opt/src/foo') 46 | end 47 | end 48 | 49 | context "with a private GitHub URL" do 50 | it "can handle it" do 51 | source = FPM::Cookery::Source.new('git@github.com:foo/bar.git') 52 | 53 | expect(source.url).to eq('git@github.com:foo/bar.git') 54 | expect(source.path).to eq('foo/bar.git') 55 | expect(source.local?).to eq(false) 56 | expect(source.fetchable?).to eq(true) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | unless RUBY_ENGINE == "rbx" 2 | require "simplecov" 3 | 4 | formatters = [SimpleCov::Formatter::HTMLFormatter] 5 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(formatters) 6 | SimpleCov.start do 7 | minimum_coverage 75 8 | add_group "Sources", "lib" 9 | add_group "Tests", "spec" 10 | end 11 | end 12 | 13 | RSpec.configure do |config| 14 | config.run_all_when_everything_filtered = true 15 | config.filter_run :focus 16 | 17 | # Run specs in random order to surface order dependencies. If you find an 18 | # order dependency and want to debug it, you can fix the order by providing 19 | # the seed, which is printed after each run. 20 | # --seed 1234 21 | config.order = 'random' 22 | 23 | config.raise_errors_for_deprecations! 24 | end 25 | 26 | require_relative "support/shared_context" 27 | 28 | def fixture_path(file) 29 | File.expand_path("../fixtures/#{file}", __FILE__) 30 | end 31 | -------------------------------------------------------------------------------- /spec/support/shared_context.rb: -------------------------------------------------------------------------------- 1 | require 'fpm/cookery/book' 2 | require 'fpm/cookery/book_hook' 3 | require 'fpm/cookery/path' 4 | require 'fpm/cookery/recipe' 5 | 6 | shared_context "temporary recipe" do |caller_filename = "recipe.rb", content = nil| 7 | let(:recipe_filename) { caller_filename } 8 | 9 | around do |example| 10 | %w{recipe book book_hook}.each { |m| require "fpm/cookery/#{m}" } 11 | 12 | FPM::Cookery::BaseRecipe.send(:include, FPM::Cookery::BookHook) 13 | 14 | Dir.mktmpdir do |tmpdir| 15 | Dir.chdir tmpdir do 16 | # Always open in order to ensure that the file exists, but only write 17 | # if we were actually given some content. 18 | begin 19 | File.open(recipe_filename, File::WRONLY | File::CREAT | File::EXCL) do |file| 20 | file.print content unless content.nil? 21 | end 22 | 23 | example.run 24 | rescue Errno::EEXIST => e 25 | skip e.message 26 | end 27 | end 28 | end 29 | end 30 | end 31 | 32 | # For setting up the proper framework to instantiate a recipe 33 | shared_context "recipe class" do |caller_filename, caller_config_options = {}| 34 | def stub_dir(dir, path) 35 | value = path.nil? ? nil : FPM::Cookery::Path.new(path) 36 | allow(config).to receive(dir).and_return(value) 37 | end 38 | 39 | before(:all) do 40 | FPM::Cookery::BaseRecipe.send(:include, FPM::Cookery::BookHook) 41 | end 42 | 43 | before(:each) do 44 | FPM::Cookery::Book.instance.filename = filename 45 | FPM::Cookery::Book.instance.config = config 46 | end 47 | 48 | let(:recipe_klass) do 49 | Class.new(FPM::Cookery::Recipe) 50 | end 51 | 52 | let(:filename) do 53 | FPM::Cookery::Path.new(caller_filename).realpath 54 | end 55 | 56 | let(:config) do 57 | #double('Config', caller_config_options).as_null_object 58 | require 'fpm/cookery/config' 59 | FPM::Cookery::Config.new(caller_config_options) 60 | end 61 | 62 | let(:recipe) do 63 | recipe_klass.new 64 | end 65 | end 66 | 67 | # Instantiate a recipe and build a package 68 | shared_context "temporary recipe class" do |caller_filename = "recipe.rb", caller_config_options = {}| 69 | include_context "recipe class", caller_filename, caller_config_options 70 | include_context "temporary recipe", caller_filename 71 | end 72 | -------------------------------------------------------------------------------- /spec/utils_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fpm/cookery/utils' 3 | 4 | describe FPM::Cookery::Utils do 5 | class TestUtils 6 | include FPM::Cookery::Utils 7 | 8 | def run_configure_no_arg 9 | configure 10 | end 11 | 12 | def run_configure 13 | configure '--prefix=/usr', '--test=yo' 14 | end 15 | 16 | def run_configure_hash 17 | configure :hello_world => true, :prefix => '/usr', 'a-dash' => 1 18 | end 19 | 20 | def run_configure_mix 21 | configure '--first=okay', '--second=okay', :hello_world => true, :prefix => '/usr', 'a-dash' => 1 22 | end 23 | 24 | def run_go_build 25 | go 'build', '-mod=vendor', '-v' 26 | end 27 | 28 | def run_go_build_hash 29 | go :build, :mod => 'vendor', :v => true 30 | end 31 | end 32 | 33 | let(:test) { TestUtils.new } 34 | 35 | before do 36 | # Avoid shellout. 37 | allow(test).to receive(:system).and_return('success') 38 | end 39 | 40 | describe '#configure' do 41 | context 'with a list of string arguments' do 42 | it 'calls ./configure with the correct arguments' do 43 | expect(test).to receive(:system).with('./configure', '--prefix=/usr', '--test=yo') 44 | test.run_configure 45 | end 46 | end 47 | 48 | context 'with hash arguments' do 49 | it 'calls ./configure with the correct arguments' do 50 | expect(test).to receive(:system).with('./configure', '--hello-world', '--prefix=/usr', '--a-dash=1') 51 | test.run_configure_hash 52 | end 53 | end 54 | 55 | context 'with string and hash arguments' do 56 | it 'calls ./configure with the correct arguments' do 57 | expect(test).to receive(:system).with('./configure', '--first=okay', '--second=okay', '--hello-world', '--prefix=/usr', '--a-dash=1') 58 | test.run_configure_mix 59 | end 60 | end 61 | 62 | context 'without any arguments' do 63 | it 'calls ./configure without any arguments' do 64 | expect(test).to receive(:system).with('./configure') 65 | test.run_configure_no_arg 66 | end 67 | end 68 | end 69 | describe '#go' do 70 | context 'with a list of string arguments' do 71 | it 'calls go with the correct arguments' do 72 | expect(test).to receive(:system).with('go', 'build', '-mod=vendor', '-v') 73 | test.run_go_build 74 | end 75 | end 76 | 77 | context 'with hash arguments' do 78 | it 'calls go with the correct arguments' do 79 | expect(test).to receive(:system).with('go', 'build', '--mod=vendor', '--v') 80 | test.run_go_build_hash 81 | end 82 | end 83 | end 84 | end 85 | --------------------------------------------------------------------------------