pkgr 0.3.4 → 1.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.md +68 -463
  2. data/bin/pkgr +4 -52
  3. data/lib/pkgr.rb +13 -29
  4. data/lib/pkgr/app.rb +19 -4
  5. data/lib/pkgr/builder.rb +207 -0
  6. data/lib/pkgr/buildpack.rb +80 -0
  7. data/lib/pkgr/cli.rb +34 -158
  8. data/lib/pkgr/config.rb +65 -0
  9. data/lib/pkgr/data/distributions/debian/build_dependencies.yml +14 -0
  10. data/lib/pkgr/data/{debian → distributions/debian}/cron.d +0 -0
  11. data/lib/pkgr/data/distributions/debian/default.erb +12 -0
  12. data/lib/pkgr/data/distributions/debian/dependencies.yml +17 -0
  13. data/lib/pkgr/data/distributions/debian/hooks/postinstall.sh +27 -0
  14. data/lib/pkgr/data/distributions/debian/hooks/preinstall.sh +9 -0
  15. data/lib/pkgr/data/{debian → distributions/debian}/logrotate.erb +0 -0
  16. data/lib/pkgr/data/distributions/debian/runner.erb +122 -0
  17. data/lib/pkgr/data/distributions/debian/upstart/master.conf.erb +7 -0
  18. data/lib/pkgr/data/distributions/debian/upstart/process.conf.erb +7 -0
  19. data/lib/pkgr/data/distributions/debian/upstart/process_master.conf.erb +2 -0
  20. data/lib/pkgr/dispatcher.rb +51 -0
  21. data/lib/pkgr/distributions.rb +18 -0
  22. data/lib/pkgr/distributions/debian.rb +157 -0
  23. data/lib/pkgr/git.rb +24 -0
  24. data/lib/pkgr/process.rb +18 -0
  25. data/lib/pkgr/templates/dir_template.rb +14 -0
  26. data/lib/pkgr/templates/file_template.rb +38 -0
  27. data/lib/pkgr/version.rb +1 -1
  28. metadata +93 -26
  29. data/lib/pkgr/data/debian/changelog +0 -0
  30. data/lib/pkgr/data/debian/compat.erb +0 -1
  31. data/lib/pkgr/data/debian/control.erb +0 -12
  32. data/lib/pkgr/data/debian/copyright.erb +0 -17
  33. data/lib/pkgr/data/debian/default.erb +0 -11
  34. data/lib/pkgr/data/debian/dirs.erb +0 -2
  35. data/lib/pkgr/data/debian/docs.erb +0 -0
  36. data/lib/pkgr/data/debian/init.d.erb +0 -155
  37. data/lib/pkgr/data/debian/install.erb +0 -16
  38. data/lib/pkgr/data/debian/links.erb +0 -5
  39. data/lib/pkgr/data/debian/postinst.erb +0 -59
  40. data/lib/pkgr/data/debian/prerm.erb +0 -57
  41. data/lib/pkgr/data/debian/rules.erb +0 -7
  42. data/lib/pkgr/pkgr.rake +0 -50
  43. data/lib/pkgr/railtie.rb +0 -7
@@ -0,0 +1,65 @@
1
+ require 'ostruct'
2
+
3
+ module Pkgr
4
+ class Config < OpenStruct
5
+ def sesame
6
+ binding
7
+ end
8
+
9
+ def home
10
+ "/opt/#{name}"
11
+ end
12
+
13
+ def user
14
+ @table[:user] || name
15
+ end
16
+
17
+ def group
18
+ @table[:group] || user
19
+ end
20
+
21
+ def architecture
22
+ @table[:architecture] || "x86_64"
23
+ end
24
+
25
+ def homepage
26
+ @table[:homepage] || "http://example.com/no-uri-given"
27
+ end
28
+
29
+ def valid?
30
+ @errors = []
31
+ @errors.push("name can't be blank") if name.nil? || name.empty?
32
+ @errors.push("version can't be blank") if version.nil? || version.empty?
33
+ @errors.push("iteration can't be blank") if iteration.nil? || iteration.empty?
34
+ @errors.push("user can't be blank") if user.nil? || user.empty?
35
+ @errors.push("group can't be blank") if group.nil? || group.empty?
36
+ @errors.empty?
37
+ end
38
+
39
+ def errors
40
+ @errors ||= []
41
+ end
42
+
43
+ def to_args
44
+ args = [
45
+ "--name \"#{name}\"",
46
+ "--version \"#{version}\"",
47
+ "--user \"#{user}\"",
48
+ "--group \"#{group}\"",
49
+ "--iteration \"#{iteration}\"",
50
+ "--homepage \"#{homepage}\"",
51
+ "--architecture \"#{architecture}\"",
52
+ "--target \"#{target}\"",
53
+ "--description \"#{description}\"",
54
+ ]
55
+ args.push "--dependencies #{dependencies.map{|d| "\"#{d}\""}.join("")}" unless dependencies.nil? || dependencies.empty?
56
+ args.push "--build-dependencies #{build_dependencies.map{|d| "\"#{d}\""}.join("")}" unless build_dependencies.nil? || build_dependencies.empty?
57
+ args.push "--compile-cache-dir \"#{compile_cache_dir}\"" unless compile_cache_dir.nil? || compile_cache_dir.empty?
58
+ args.push "--before-precompile \"#{before_precompile}\"" unless compile_cache_dir.nil? || compile_cache_dir.empty?
59
+ args.push "--auto" if auto
60
+ args.push "--verbose" if verbose
61
+ args.push "--debug" if debug
62
+ args
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,14 @@
1
+ default:
2
+ - curl
3
+ - libssl0.9.8
4
+ - libmysqlclient-dev
5
+ - libpq-dev
6
+ - libsqlite3-0
7
+ - libevent-dev
8
+ - libssl-dev
9
+ - libxml2-dev
10
+ - libxslt1-dev
11
+ - libreadline-dev
12
+ - build-essential
13
+ squeeze:
14
+ wheezy:
@@ -0,0 +1,12 @@
1
+ HOME="<%= home %>"
2
+ APP_NAME="<%= name %>"
3
+ APP_GROUP="<%= group %>"
4
+ APP_USER="<%= user %>"
5
+
6
+ for file in /etc/${APP_NAME}/conf.d/*; do
7
+ if [ -f $file ]; then . $file; fi
8
+ done
9
+
10
+ for file in ${HOME}/.profile.d/*.sh; do
11
+ if [ -f $file ]; then . $file; fi
12
+ done
@@ -0,0 +1,17 @@
1
+ default:
2
+ - upstart
3
+ - mysql-common
4
+ - libpq5
5
+ - libsqlite3-0
6
+ - libevent-1.4-2
7
+ - libevent-core-1.4-2
8
+ - libevent-extra-1.4-2
9
+ - libssl0.9.8
10
+ - openssl
11
+ - libxml2
12
+ - libxslt1.1
13
+ - libreadline6
14
+ squeeze:
15
+ - libmysqlclient16
16
+ wheezy:
17
+ - libmysqlclient18
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ APP_NAME="<%= name %>"
6
+ APP_USER="<%= user %>"
7
+ APP_GROUP="<%= group %>"
8
+ HOME="/opt/${APP_NAME}"
9
+ HOME_LOGS="${HOME}/log"
10
+ LOGS="/var/log/${APP_NAME}"
11
+
12
+ chown -R ${APP_USER}.${APP_GROUP} ${HOME}
13
+
14
+ # link app log directory to /var/log/NAME
15
+ rm -rf ${HOME_LOGS}
16
+ ln -fs ${LOGS} ${HOME_LOGS}
17
+ chown -R ${APP_USER}.${APP_GROUP} ${LOGS}
18
+
19
+ # Add default conf.d file
20
+ [ -f /etc/${APP_NAME}/conf.d/other ] || cat > /etc/${APP_NAME}/conf.d/other <<CONF
21
+ # This file contains variables set via \`${APP_NAME} config:set\`
22
+ # Database URL. E.g. : mysql2://root:pass@127.0.0.1/my-app-db
23
+ export DATABASE_URL=db_adapter://db_user:db_password@db_host/db_name
24
+ export PORT=\${PORT:=6000}
25
+ CONF
26
+
27
+ chmod -R 0600 /etc/${APP_NAME}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ APP_USER="<%= user %>"
6
+
7
+ if ! getent passwd ${APP_USER} > /dev/null; then
8
+ adduser ${APP_USER} --disabled-login --group --system --quiet --shell /bin/bash
9
+ fi
@@ -0,0 +1,122 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ EXECUTABLE=$(basename $0)
6
+
7
+ function usage {
8
+ echo "Usage:"
9
+ echo " $EXECUTABLE run COMMAND [options]"
10
+ echo " $EXECUTABLE scale TYPE=NUM"
11
+ echo " $EXECUTABLE config:get VAR"
12
+ echo " $EXECUTABLE config:set VAR=VALUE"
13
+ exit 1
14
+ }
15
+
16
+ if [ ! 0 -eq ${EUID} ]; then
17
+ echo "You must be executing with root privileges to launch commands. Either log in as root, use sudo, or add sudo privileges for running ${EXECUTABLE} with your user."
18
+ exit 1
19
+ fi
20
+
21
+ DEFAULT_FILE=/etc/default/<%= name %>
22
+
23
+ . ${DEFAULT_FILE}
24
+
25
+ while : ; do
26
+ case "$1" in
27
+ run)
28
+ [ $# -lt 2 ] && usage
29
+ COMMAND="$2"
30
+ shift 2
31
+
32
+ exec su -m - "${APP_USER}" -c ". ${DEFAULT_FILE} && cd ${HOME} && vendor/pkgr/processes/${COMMAND} $@"
33
+ break ;;
34
+
35
+ scale)
36
+ shift
37
+ while : ; do
38
+ case "$1" in
39
+ *)
40
+ [ "$1" = "" ] && usage
41
+
42
+ PROCESS=(${1//=/ })
43
+
44
+ PROCESS_NAME=${PROCESS[0]}
45
+ NEW_SCALE=${PROCESS[1]}
46
+ CURRENT_SCALE=$(ls -rv1 /etc/init/${APP_NAME}-${PROCESS_NAME}-*.conf 2>/dev/null | head -1 | sed -r 's/.*\-([0-9]+)\.conf/\1/g')
47
+ CURRENT_SCALE=${CURRENT_SCALE:="0"}
48
+ SCALE_DELTA=$(expr ${NEW_SCALE} - ${CURRENT_SCALE})
49
+
50
+ if [ $NEW_SCALE -eq 0 ]; then
51
+ echo "Stopping all ${PROCESS_NAME} processes... "
52
+ for file in `ls -1 /etc/init/${APP_NAME}-${PROCESS_NAME}-*.conf`; do
53
+ service "$(basename ${file} .conf)" stop || true
54
+ rm "${file}"
55
+ done
56
+ # Finally, remove master process
57
+ rm "/etc/init/${APP_NAME}-${PROCESS_NAME}.conf"
58
+ echo "--> done."
59
+ exit 0
60
+ fi
61
+
62
+ if [ $SCALE_DELTA -gt 0 ]; then
63
+ echo "Scaling up..."
64
+ cp "${HOME}/vendor/pkgr/scaling/${APP_NAME}-${PROCESS_NAME}.conf" /etc/init/
65
+ for i in $(seq ${SCALE_DELTA}); do
66
+ PROCESS_ID="${APP_NAME}-${PROCESS_NAME}-${i}"
67
+ i=$(expr ${i} + ${CURRENT_SCALE})
68
+ cp "${HOME}/vendor/pkgr/scaling/${APP_NAME}-${PROCESS_NAME}-PROCESS_NUM.conf" "/etc/init/${PROCESS_ID}.conf"
69
+ sed -i "s/PROCESS_NUM/${i}/g" "/etc/init/${PROCESS_ID}.conf"
70
+ if [ "${PROCESS_NAME}" = "web" ]; then
71
+ port=$(expr ${PORT} + ${i} - 1)
72
+ sed -i "s/PORT_NUM/${port}/g" "/etc/init/${PROCESS_ID}.conf"
73
+ else
74
+ sed -i "s/^env .*PORT_NUM.*$//g" "/etc/init/${PROCESS_ID}.conf"
75
+ fi
76
+ service "${PROCESS_ID}" start
77
+ done
78
+ echo "--> done."
79
+ elif [ $SCALE_DELTA -lt 0 ]; then
80
+ echo "Scaling down..."
81
+ for i in $(seq $(expr $SCALE_DELTA \* -1)); do
82
+ PROCESS_ID="${APP_NAME}-${PROCESS_NAME}-${i}"
83
+ i=$(expr ${i} + ${NEW_SCALE})
84
+ service "${PROCESS_ID}" stop || true
85
+ rm -f "/etc/init/${PROCESS_ID}.conf"
86
+ done
87
+ echo "--> done."
88
+ else
89
+ echo "Nothing to do."
90
+ fi
91
+ break ;;
92
+ esac
93
+ done
94
+ break ;;
95
+
96
+ config:set)
97
+ [ $# -lt 2 ] && usage
98
+
99
+ CONFIG=(${2//=/ })
100
+
101
+ VAR=${CONFIG[0]:?"Invalid variable name"}
102
+ VALUE=${CONFIG[1]:?"Invalid value"}
103
+
104
+ DEFAULT_CONF_FILE="/etc/${APP_NAME}/conf.d/other"
105
+
106
+ sed -i -r "s/^.*${VAR}.*$//g" /etc/${APP_NAME}/conf.d/*
107
+
108
+ echo "export ${VAR}=${VALUE}" >> "${DEFAULT_CONF_FILE}"
109
+
110
+ break;;
111
+
112
+ config:get)
113
+ [ $# -lt 2 ] && usage
114
+ result=$(grep --no-filename "${2}" ${DEFAULT_FILE} /etc/${APP_NAME}/conf.d/*)
115
+ echo "${result}" | sed -r "s/^export\s+${2}=//g"
116
+ break;;
117
+
118
+ *)
119
+ usage
120
+ break ;;
121
+ esac
122
+ done
@@ -0,0 +1,7 @@
1
+ start on (started network-interface
2
+ or started network-manager
3
+ or started networking)
4
+
5
+ stop on (stopping network-interface
6
+ or stopping network-manager
7
+ or stopping networking)
@@ -0,0 +1,7 @@
1
+ start on starting <%= name %>-<%= process_name %>
2
+ stop on stopping <%= name %>-<%= process_name %>
3
+ respawn
4
+
5
+ env PORT=PORT_NUM
6
+
7
+ exec <%= name %> run <%= process_name %> >> /var/log/<%= name %>/<%= process_name %>-PROCESS_NUM.log 2>&1
@@ -0,0 +1,2 @@
1
+ start on starting <%= name %>
2
+ stop on stopping <%= name %>
@@ -0,0 +1,51 @@
1
+ require 'pkgr/builder'
2
+ require 'pkgr/git'
3
+
4
+ module Pkgr
5
+ class Dispatcher
6
+ attr_reader :path, :host, :config
7
+
8
+ def initialize(path, opts = {})
9
+ opts = opts.dup
10
+ @path = path
11
+ @host = opts.delete(:host)
12
+ @config = Config.new(opts)
13
+ end
14
+
15
+ def setup
16
+ tarify if File.directory?(path)
17
+ end
18
+
19
+ def call
20
+ setup
21
+
22
+ if remote?
23
+ command = %{ ( cat "#{path}" | ssh "#{host}" pkgr package - #{config.to_args.join(" ")} ) && rsync "#{host}":~/*.deb .}
24
+ Pkgr.debug command
25
+ IO.popen(command) do |io|
26
+ until io.eof?
27
+ data = io.gets
28
+ print data
29
+ end
30
+ end
31
+ raise "Error when running remote packaging command. Please make sure to run `sudo apt-get install -y ruby1.9.1-full build-essential git-core && sudo gem install pkgr --version #{Pkgr::VERSION}`" unless $?.exitstatus.zero?
32
+ else
33
+ Builder.new(path, config).call
34
+ end
35
+ end
36
+
37
+ def tarify
38
+ tmpfile = Tempfile.new(["pkgr-tarball", ".tar.gz"])
39
+ system("tar czf #{tmpfile.path} --exclude .git --exclude .svn -C \"#{path}\" .") || raise(Pkgr::Errors::Base, "Can't compress input directory")
40
+ # Remove any non-digit characters that may be before the version number
41
+ config.version ||= (Git.new(path).latest_tag || "").gsub(/^[^\d](\d.*)/, '\1')
42
+ config.compile_cache_dir ||= File.join(path, ".git", "cache")
43
+ config.name ||= File.basename(path)
44
+ @path = tmpfile.path
45
+ end
46
+
47
+ def remote?
48
+ !host.nil?
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,18 @@
1
+ require 'pkgr/templates/file_template'
2
+ require 'pkgr/templates/dir_template'
3
+ require 'pkgr/distributions/debian'
4
+
5
+
6
+ module Pkgr
7
+ module Distributions
8
+ def current
9
+ if File.exist?("/etc/debian_version")
10
+ distro = File.read("/etc/debian_version").split("/")[0]
11
+ Debian.new(distro)
12
+ else
13
+ raise "Don't know about the current distribution you're on"
14
+ end
15
+ end
16
+ module_function :current
17
+ end
18
+ end
@@ -0,0 +1,157 @@
1
+ require 'pkgr/buildpack'
2
+ require 'pkgr/process'
3
+ require 'yaml'
4
+ require 'erb'
5
+
6
+ module Pkgr
7
+ module Distributions
8
+ class Debian
9
+
10
+ attr_reader :version
11
+ def initialize(version)
12
+ @version = version
13
+ end
14
+
15
+ def templates(app_name)
16
+ list = []
17
+
18
+ # directories
19
+ [
20
+ "usr/local/bin",
21
+ "opt/#{app_name}",
22
+ "etc/#{app_name}/conf.d",
23
+ "etc/default",
24
+ "etc/init",
25
+ "var/log/#{app_name}"
26
+ ].each{|dir| list.push Templates::DirTemplate.new(dir) }
27
+
28
+ # default
29
+ list.push Templates::FileTemplate.new("etc/default/#{app_name}", File.new(File.join(data_dir, "default.erb")))
30
+ # upstart master
31
+ list.push Templates::FileTemplate.new("etc/init/#{app_name}.conf", data_file("upstart/master.conf.erb"))
32
+ # executable
33
+ list.push Templates::FileTemplate.new("usr/local/bin/#{app_name}", File.new(File.join(data_dir, "runner.erb")), mode: 0755)
34
+ # logrotate
35
+ list.push Templates::FileTemplate.new("etc/logrotate.d/#{app_name}", File.new(File.join(data_dir, "logrotate.erb")))
36
+
37
+ # NOTE: conf.d files are no longer installed here, since we don't want to overwrite any pre-existing config.
38
+ # They're now installed in the postinstall script.
39
+
40
+ list
41
+ end
42
+
43
+ def initializers_for(app_name, procfile_entries)
44
+ list = []
45
+ procfile_entries.select(&:daemon?).each do |process|
46
+ Pkgr.debug "Adding #{process.inspect} to initialization scripts"
47
+ list.push [process, Templates::FileTemplate.new("#{app_name}-#{process.name}.conf", data_file("upstart/process_master.conf.erb"))]
48
+ list.push [process, Templates::FileTemplate.new("#{app_name}-#{process.name}-PROCESS_NUM.conf", data_file("upstart/process.conf.erb"))]
49
+ end
50
+ list
51
+ end
52
+
53
+ def check(config)
54
+ missing_packages = (build_dependencies(config.build_dependencies) || []).select do |package|
55
+ test_command = "dpkg -s '#{package}' > /dev/null 2>&1"
56
+ Pkgr.debug "Running #{test_command}"
57
+ ! system(test_command)
58
+ end
59
+
60
+ unless missing_packages.empty?
61
+ package_install_command = "sudo apt-get install -y #{missing_packages.map{|package| "\"#{package}\""}.join(" ")}"
62
+ if config.auto
63
+ Pkgr.debug "Running command: #{package_install_command}"
64
+ package_install = Mixlib::ShellOut.new(package_install_command)
65
+ package_install.run_command
66
+ package_install.error!
67
+ else
68
+ Pkgr.warn("Missing build dependencies detected. Run the following to fix: #{package_install_command}")
69
+ end
70
+ end
71
+ end
72
+
73
+ def fpm_command(build_dir, config)
74
+ %{
75
+ fpm -t deb -s dir --verbose --force \
76
+ -C "#{build_dir}" \
77
+ -n "#{config.name}" \
78
+ --version "#{config.version}" \
79
+ --iteration "#{config.iteration}" \
80
+ --url "#{config.homepage}" \
81
+ --provides "#{config.name}" \
82
+ --deb-user "root" \
83
+ --deb-group "root" \
84
+ -a "#{config.architecture}" \
85
+ --template-scripts \
86
+ --before-install #{preinstall_file(config)} \
87
+ --after-install #{postinstall_file(config)} \
88
+ #{dependencies(config.dependencies).map{|d| "-d '#{d}'"}.join(" ")} \
89
+ .
90
+ }
91
+ end
92
+
93
+ def buildpacks
94
+ case version
95
+ when "wheezy"
96
+ %w{
97
+ https://github.com/heroku/heroku-buildpack-ruby.git
98
+ https://github.com/heroku/heroku-buildpack-nodejs.git
99
+ https://github.com/heroku/heroku-buildpack-java.git
100
+ https://github.com/heroku/heroku-buildpack-play.git
101
+ https://github.com/heroku/heroku-buildpack-python.git
102
+ https://github.com/heroku/heroku-buildpack-php.git
103
+ https://github.com/heroku/heroku-buildpack-clojure.git
104
+ https://github.com/kr/heroku-buildpack-go.git
105
+ https://github.com/miyagawa/heroku-buildpack-perl.git
106
+ https://github.com/heroku/heroku-buildpack-scala
107
+ https://github.com/igrigorik/heroku-buildpack-dart.git
108
+ https://github.com/rhy-jot/buildpack-nginx.git
109
+ https://github.com/Kloadut/heroku-buildpack-static-apache.git
110
+ }.map{|url| Buildpack.new(url)}
111
+ end
112
+ end
113
+
114
+ def preinstall_file(config)
115
+ @preinstall_file ||= begin
116
+ source = File.join(data_dir, "hooks", "preinstall.sh")
117
+ file = Tempfile.new("preinstall")
118
+ file.write ERB.new(File.read(source)).result(config.sesame)
119
+ file.rewind
120
+ file
121
+ end
122
+
123
+ @preinstall_file.path
124
+ end
125
+
126
+ def postinstall_file(config)
127
+ @postinstall_file ||= begin
128
+ source = File.join(data_dir, "hooks", "postinstall.sh")
129
+ file = Tempfile.new("postinstall")
130
+ file.write ERB.new(File.read(source)).result(config.sesame)
131
+ file.rewind
132
+ file
133
+ end
134
+
135
+ @postinstall_file.path
136
+ end
137
+
138
+ def dependencies(other_dependencies = nil)
139
+ deps = YAML.load_file(File.join(data_dir, "dependencies.yml"))
140
+ (deps["default"] || []) | (deps[version] || []) | (other_dependencies || [])
141
+ end
142
+
143
+ def build_dependencies(other_dependencies = nil)
144
+ deps = YAML.load_file(File.join(data_dir, "build_dependencies.yml"))
145
+ (deps["default"] || []) | (deps[version] || []) | (other_dependencies || [])
146
+ end
147
+
148
+ def data_file(name)
149
+ File.new(File.join(data_dir, name))
150
+ end
151
+
152
+ def data_dir
153
+ File.join(Pkgr.data_dir, "distributions", "debian")
154
+ end
155
+ end
156
+ end
157
+ end