cany 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 259ed776e530912657f05fd1f8ec41ef3de1d667
4
- data.tar.gz: 6c9441bcd31cec929af0d129206495cb54ab5f34
3
+ metadata.gz: 689bd4e09a4407570532a6ec0679c9d75c67f020
4
+ data.tar.gz: fda3db01834b24bb64435e07f1c12cd37463e551
5
5
  SHA512:
6
- metadata.gz: 28292d32841cc8e2842207dfb71d9d0b90cf8367ab42f1fb8d6adc85dd398527a9b2d8bf5183b367d48c906d6e9d24eb6e497ceaa56d9d3e37e9aeaf405bac7b
7
- data.tar.gz: 68462bee6203f87debebae7d4e9dbbcef6f6aa858da5904ed51502852ff596b7d5cb15d927cfb8b1f36be61bfbf907e7c696068722e30f60f530ba04733965c3
6
+ metadata.gz: be07431ce48ede3893ebb6b3cfcbdd45ac97313cc8239852239a9560bc054e42c7cb468193678a656e7735a7523b95b7ffeb4d4ee855b1e6707c8c04ccdf35d0
7
+ data.tar.gz: da2e5f74092021b94313860e4bf32ad9142b5fd16fea636e07563c3e6d8633bbe4776c16c21465b5b27bacd7af064ca14ba5014f85bd9f66d16c62179f9c5f5d
data/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ 0.1.0 / 2013-09-23
2
+ ==================
3
+
4
+ Features:
5
+
6
+ + Support recipe hooks
7
+ + Support recipe (configuration) option
8
+ + Rewritten and extended dependency management
9
+ + Support (source) package generation adjustments by recipes
10
+ + canspec: new require_cany directive
11
+ + Support per rails configuration
12
+ + Refactored init script management (supports multiple services per package)
13
+ + sidekiq recipe
14
+ + unicorn recipe
15
+
16
+ Bug fixes:
17
+
18
+ * rails: export RAILS_ENV in wrapper script
19
+ * bundler: add some more gem dependencies
20
+ * recipe: exit on failed command (like bundle install)
21
+ * dpkg: create compat 9 packages
22
+
23
+
24
+ 0.0.2 / 2013-09-13
25
+ ==================
26
+
27
+ Bug fixes:
28
+
29
+ * rails: run asset compilation inside assets environment
30
+ * dpkg: replace hard coded package name
31
+ * rails: run rake inside bundle
32
+ * recipe: use same ruby intepreter as for cany
33
+
34
+
35
+ 0.0.1 / 2013-09-12
36
+ ==================
37
+
38
+ * Basic support to pack a rails application
data/Gemfile CHANGED
@@ -3,6 +3,18 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in cany.gemspec
4
4
  gemspec
5
5
 
6
+ group :development do
7
+ gem 'yard', '~> 0.8.6'
8
+ gem 'guard'
9
+ gem 'libnotify', :require => false
10
+ gem 'rb-inotify', :require => false
11
+ gem 'rb-fsevent', :require => false
12
+ gem 'rb-fchange', :require => false
13
+ gem 'guard-rspec'
14
+ gem 'guard-yard'
15
+ gem 'redcarpet', platform: :ruby
16
+ end
17
+
6
18
  group :test do
7
19
  gem 'deb_control', '~> 0.0.1'
8
20
  gem 'coveralls', require: false
data/Guardfile ADDED
@@ -0,0 +1,20 @@
1
+ #
2
+ # Guardfile -- for RSpec
3
+ #
4
+
5
+ guard 'rspec', all_after_pass: true, all_on_start: true do
6
+ watch(%r{^spec/(.+)_spec\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
9
+ watch('spec/spec_helper.rb') { "spec" }
10
+ end
11
+
12
+ guard 'yard' do
13
+ watch(%r{lib/.+\.rb})
14
+ end
15
+
16
+ # load Guardfile.local
17
+ local_guardfile = File.dirname(__FILE__) + "/Guardfile.local"
18
+ if File.file?(local_guardfile)
19
+ self.instance_eval(Bundler.read_file(local_guardfile))
20
+ end
data/Rakefile CHANGED
@@ -3,3 +3,14 @@ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
  task default: :spec
6
+
7
+ begin
8
+ require 'yard'
9
+ require 'yard/rake/yardoc_task'
10
+
11
+ YARD::Rake::YardocTask.new do |t|
12
+ t.files = %w(lib/**/*.rb)
13
+ t.options = %w(--output-dir doc/)
14
+ end
15
+ rescue LoadError
16
+ end
data/lib/cany.rb CHANGED
@@ -1,29 +1,98 @@
1
- require 'cany/version'
2
- require 'cany/specification'
3
- require 'cany/recipe'
4
- require 'cany/recipes/bundler'
5
- require 'cany/recipes/rails'
6
- require 'cany/recipes/web_server'
7
- require 'cany/recipes/thin'
8
- require 'cany/dpkg'
9
- require 'cany/dpkg/creator'
10
- require 'cany/dpkg/builder'
11
- require 'cany/dpkg/deb_helper_recipe'
1
+ require 'logger'
12
2
 
13
3
  module Cany
14
- class MissingSpecification < Exception
15
- end
16
-
17
- class MultipleSpecifications < Exception
18
- end
19
-
4
+ # @raise [MissingSpecification] if no canspec is found in the directory
5
+ # @raise [MultipleSpecifications] if multiple canspec files are found inside
6
+ # the directory
20
7
  def self.setup(directory='.')
21
8
  specs = Dir[directory + '/*.' + Specification::EXT]
22
- raise MissingSpecification, "No #{Specification::EXT} found in #{directory}" if specs.size == 0
23
- raise MultipleSpecifications, "Multiple #{Specification::EXT} found in #{directory}" if specs.size > 1
9
+ raise MissingSpecification.new(directory) if specs.size == 0
10
+ raise MultipleSpecifications.new(directory) if specs.size > 1
24
11
  file = specs.first
25
12
  spec = eval File::read(file), binding, file
26
13
  spec.base_dir = directory
27
14
  spec
28
15
  end
16
+
17
+ # This methods creates a hash that returns an array as default value and also
18
+ # stores it directly inside the hash, so that the return value can be changed
19
+ # without additional actions.
20
+ # @example
21
+ # hash = hash_with_array_as_default
22
+ # hash[:hans] << 'otto'
23
+ # hash[:hash] == ['otto']
24
+ def self.hash_with_array_as_default
25
+ {}.tap do |hash|
26
+ hash.default_proc = Proc.new do |_, key|
27
+ hash[key] = []
28
+ end
29
+ end
30
+ end
31
+
32
+ # @api public
33
+ # @return [Logger]
34
+ def self.logger
35
+ @logger ||= create_logger
36
+ end
37
+
38
+ def self.create_logger
39
+ logger = Logger.new(STDOUT)
40
+ logger.level = Logger::INFO
41
+ org_formatter = Logger::Formatter.new
42
+ logger.formatter = proc do |severity, datetime, progname, msg|
43
+ if severity == "INFO"
44
+ " #{msg}\n"
45
+ else
46
+ org_formatter.call severity, datetime, progname, msg
47
+ end
48
+ end
49
+ logger
50
+ end
51
+
52
+ require 'cany/version'
53
+ require 'cany/errors'
54
+ # This module contains ruby mixins that are used within multiple classes to share code.
55
+ module Mixins
56
+ require 'cany/mixins/depend_mixin'
57
+ end
58
+ require 'cany/dependency'
59
+ require 'cany/specification'
60
+ require 'cany/recipe'
61
+
62
+
63
+ # Applications using common libraries to concentrate on things that are new
64
+ # and no solved by existing software. Therefore there are similar deploy
65
+ # tasks that are needed for applications.
66
+ #
67
+ # Cany groups common deploy aspects in recipes. This recipes can be included
68
+ # and used by the application. Normally there exists one recipe for every
69
+ # important software that is used by the application and influences directly
70
+ # the way the applications needs to be installed.
71
+ #
72
+ # Central recipes are bundler as gem package manager and rails as popular
73
+ # web framework.
74
+ #
75
+ # To support starting the applications there is also a collection of recipes
76
+ # deploying ruby web server or background services.
77
+ module Recipes
78
+ require 'cany/recipes/bundler'
79
+ require 'cany/recipes/bundler/gem'
80
+ require 'cany/recipes/bundler/gem_db'
81
+ require 'cany/recipes/rails'
82
+ require 'cany/recipes/web_server'
83
+ require 'cany/recipes/thin'
84
+ require 'cany/recipes/unicorn'
85
+ require 'cany/recipes/sidekiq'
86
+ end
87
+
88
+
89
+ # Cany is designed to be able to pack applications for multiple package
90
+ # managers. Although there is currently only support for debian/ubuntu.
91
+ # All DPKG specific things are group into the Dpkg namespace.
92
+ module Dpkg
93
+ require 'cany/dpkg'
94
+ require 'cany/dpkg/creator'
95
+ require 'cany/dpkg/builder'
96
+ require 'cany/dpkg/deb_helper_recipe'
97
+ end
29
98
  end
@@ -0,0 +1,70 @@
1
+ module Cany
2
+ # This class representing a dependency on an abstract object like a gem or a external software.
3
+ #
4
+ # Depending on the platform different packages are needed to satisfy the dependency. This class
5
+ # stores which packages (and optional a version constraint) is needed for every platform,
6
+ # distribution or distribution release.
7
+ #
8
+ # This class differs between two different situation where dependencies are needed: to build
9
+ # the package (:build) or to run/use the packages (:runtime).
10
+ #
11
+ # A dependency has also a priority:
12
+ # - :required: The dependency have to be fulfilled in every situation otherwise it is not
13
+ # possible to build or run the application.
14
+ # Other priorities are planed but currently not implemented.
15
+ class Dependency
16
+ def initialize(opts={})
17
+ opts = { situations: [:runtime] }.merge opts
18
+ @default = []
19
+ @distros = Cany.hash_with_array_as_default
20
+ @distro_releases ||= Cany.hash_with_array_as_default
21
+ self.situations = opts[:situations]
22
+ end
23
+
24
+ attr_reader :situations
25
+ def situations=(value)
26
+ @situations = value.kind_of?(Array) ? value : [value]
27
+ end
28
+ def runtime?; @situations.include? :runtime; end
29
+ def build?; @situations.include? :build; end
30
+
31
+ # Define the default package name and an optional version constraint for all
32
+ # @param name[String] A package name
33
+ # @param version[String, nil] A version constraint
34
+ def define_default(name, version=nil)
35
+ default << [name, version]
36
+ end
37
+
38
+ # Define the default package name and an optional version constraint for a distribution
39
+ # @param distro[Symbol] The distribution name like :ubuntu, :debian ...
40
+ # @param name[String] A package name
41
+ # @param version[String, nil] A version constraint
42
+ def define_on_distro(distro, name, version=nil)
43
+ distros[distro] << [name, version]
44
+ end
45
+
46
+ # Define the package name and an optional version constraint for a distribution release
47
+ # @param distro[Symbol] The distribution name like :ubuntu, :debian ...
48
+ # @param release[Symbol] The distribution release like :precise for ubuntu 12.04
49
+ # @param name[String] A package name
50
+ # @param version[String, nil] A version constraint
51
+ def define_on_distro_release(distro, release, name, version=nil)
52
+ distro_releases[[distro, release]] << [name, version]
53
+ end
54
+
55
+ # Evaluation which packages (with version constraint) are needed for the given distrobution
56
+ # release.
57
+ # @param distro[Symbol] The distribution name like :ubuntu, :debian ...
58
+ # @param release[Symbol] The distribution release like :precise for ubuntu 12.04
59
+ # @return [Array<String, String>]
60
+ def determine(distro, release)
61
+ return distro_releases[[distro, release]] if distro_releases.has_key? [distro, release]
62
+ return distros[distro] if distros.has_key? distro
63
+ default
64
+ end
65
+
66
+ protected
67
+
68
+ attr_reader :default, :distros, :distro_releases
69
+ end
70
+ end
@@ -11,26 +11,14 @@ module Cany
11
11
  @spec = spec
12
12
  end
13
13
 
14
- def debian(*args)
15
- File.join spec.base_dir, 'debian', *args
16
- end
17
-
18
14
  # This method is called to do the actual work
19
15
  # @api public
20
16
  # @param [String] build_step_name The name of the dpkg build step (clean, build, binary)
21
17
  def run(build_step_name)
22
- setup_recipes
23
- @recipes.first.send build_step_name.to_s
24
- end
25
-
26
- # @api private
27
- # This method creates recipe instances for all required recipes from the given spec.
28
- def setup_recipes
29
- @recipes = []
30
- @recipes << DebHelperRecipe.new(spec, nil)
31
- spec.recipes.reverse.each do |name|
32
- @recipes.unshift Recipe.from_name(name).new(spec, @recipes.first)
33
- end
18
+ spec.system_recipe = DebHelperRecipe.new spec
19
+ spec.setup_recipes
20
+ spec.system_recipe.exec 'dh_prep' if build_step_name.to_s == 'binary'
21
+ spec.recipes.first.send build_step_name.to_s
34
22
  end
35
23
  end
36
24
  end
@@ -48,6 +48,12 @@ module Cany
48
48
  def run(*args)
49
49
  parse_opts *args
50
50
 
51
+ # let recipes influence package creating
52
+ @spec.system_recipe = DebHelperRecipe.new(spec)
53
+ @spec.recipes.each do |recipe|
54
+ recipe.create(self)
55
+ end
56
+
51
57
  Dir.mkdir debian
52
58
  create_compact
53
59
  create_source_format
@@ -64,29 +70,11 @@ module Cany
64
70
 
65
71
  def create_compact
66
72
  File.open debian('compat'), 'w' do |f|
67
- f.write '8'
73
+ f.write '9'
68
74
  end
69
75
  end
70
76
 
71
77
  def create_source_control
72
- require 'bundler'
73
- lock_path = File.join(spec.base_dir, 'Gemfile.lock')
74
- extra_libs = {
75
- pg: ['libpq-dev', 'libpq5'],
76
- ethon: ['libcurl3 | libcurl3-gnutls | libcurl3-nss', 'libcurl3 | libcurl3-gnutls | libcurl3-nss']
77
- }
78
- src_deps = ['debhelper (>= 7.0.50~)', ruby_deb, ruby_deb + '-dev']
79
- bin_deps = ['${shlibs:Depends}', '${misc:Depends}', ruby_deb]
80
- if File.exists? lock_path
81
- lock = Bundler::LockfileParser.new File.read lock_path
82
- lock.specs.each do |spec|
83
- if extra_libs.has_key? spec.name.to_sym
84
- src, bin = extra_libs[spec.name.to_sym]
85
- src_deps << src
86
- bin_deps << bin
87
- end
88
- end
89
- end
90
78
  File.open debian('control'), 'w' do |f|
91
79
  # write source package fields:
92
80
  f.write("Source: #{spec.name}\n")
@@ -95,13 +83,13 @@ module Cany
95
83
  f.write("Maintainer: #{spec.maintainer_name} <#{spec.maintainer_email}>\n")
96
84
  f.write("Standards-Version: 3.9.2\n")
97
85
  f.write("Homepage: #{spec.website}\n")
98
- f.write("Build-Depends: #{src_deps.join(', ')}\n")
86
+ f.write("Build-Depends: #{resolve_dependencies(@spec.build_dependencies)}\n")
99
87
 
100
88
  # write binary package fields:
101
89
  f.write("\n")
102
90
  f.write("Package: #{spec.name}\n")
103
91
  f.write("Architecture: any\n")
104
- f.write("Depends: #{bin_deps.join(', ')}\n")
92
+ f.write("Depends: #{resolve_dependencies(@spec.runtime_dependencies)}\n")
105
93
  f.write("Description: #{spec.description}\n")
106
94
  end
107
95
  end
@@ -119,14 +107,22 @@ module Cany
119
107
 
120
108
  def create_rules
121
109
  File.open debian('rules'), 'w' do |f|
122
- f.write("#!/usr/bin/make -f\n")
123
- f.write("export PATH := debian/bin:${PATH}\n")
124
- f.write("export GEM_PATH := debian/gems:${GEM_PATH}\n")
125
- # call cany for every target:
126
- f.write("%:\n")
127
- f.write("\t#{ruby_exe} -cS cany >/dev/null || #{ruby_exe} -S gem install --no-ri --no-rdoc --install-dir debian/gems --bindir debian/bin $${CANY_GEM:-cany}\n")
128
- f.write("\t#{ruby_exe} -S cany dpkg-builder $@\n")
129
- f.write("\noverride_dh_prep:\n")
110
+ unless @spec.cany_version_constraint
111
+ gem_version = ''
112
+ else
113
+ gem_version = " --version \"#{@spec.cany_version_constraint}\""
114
+ gem_version += ' --prerelease' if @spec.cany_version_constraint.match /[a-zA-Z]/
115
+ end
116
+
117
+ f.write <<EOM.gsub /^ /, ''
118
+ #!/usr/bin/make -f
119
+ export PATH := debian/bin:${PATH}
120
+ export GEM_PATH := debian/gems:${GEM_PATH}
121
+ %:
122
+ \t#{ruby_exe} -cS cany >/dev/null || #{ruby_exe} -S gem install --no-ri --no-rdoc --install-dir debian/gems --bindir debian/bin $${CANY_GEM:-cany}#{gem_version}
123
+ \t#{ruby_exe} -S cany dpkg-builder $@\n
124
+ override_dh_prep:
125
+ EOM
130
126
 
131
127
  f.chmod(0755)
132
128
  end
@@ -141,6 +137,20 @@ module Cany
141
137
  f.write " -- #{spec.maintainer_name} <#{spec.maintainer_email}> #{Time.now.strftime "%a, %d %b %Y %H:%M:%S %z" }"
142
138
  end
143
139
  end
140
+
141
+ private
142
+
143
+ # Converts the given array of dependencies objects into a dependency string used inside
144
+ # debians source control files
145
+ # @param dependencies[Array<Dependency>]
146
+ # @return [String] A dependency string
147
+ def resolve_dependencies(dependencies)
148
+ dependencies.inject([]) do |deps, dep|
149
+ deps + dep.determine(:ubuntu, :precise).map do |pkg, version|
150
+ !version.nil? ? "#{pkg} (#{version})" : pkg
151
+ end
152
+ end.join(', ')
153
+ end
144
154
  end
145
155
  end
146
156
  end
@@ -1,28 +1,113 @@
1
- module Cany
2
- module Dpkg
3
- class DebHelperRecipe < Cany::Recipe
4
- register_as :deb_helper
5
-
6
- def initialize(*args)
7
- super *args
8
- @log = File.read('debian/xikolo-account.debhelper.log') if File.exists? 'debian/xikolo-account.debhelper.log'
9
- exec 'dh_prep'
10
- end
1
+ require 'erb'
11
2
 
12
- def clean
13
- exec %w(dh clean)
14
- end
3
+ module Cany::Dpkg
4
+ class DebHelperRecipe < Cany::Recipe
5
+ register_as :deb_helper
6
+
7
+ option :service_pre_scripts
8
+
9
+ def initialize(*args)
10
+ super *args
11
+ @services = {}
12
+ @pre_scripts = []
13
+ @log = File.read debhelper_log if File.exists? debhelper_log
14
+ end
15
+
16
+ def create(creator)
17
+ depend 'debhelper', version: '>= 7.0.50~', situation: :build
18
+ depend '${shlibs:Depends}'
19
+ depend '${misc:Depends}'
20
+ depend creator.ruby_deb, situation: [:build, :runtime]
21
+ depend creator.ruby_deb + '-dev', situation: :build
22
+ end
23
+
24
+ def clean
25
+ exec %w(dh clean)
26
+ end
27
+
28
+ def build
29
+ instance_eval &spec.build if spec.build
30
+ exec %w(dh build)
31
+ end
32
+
33
+ def binary
34
+ instance_eval &spec.binary if spec.binary
35
+ install_base_service
36
+ install_services
37
+ File.write debhelper_log, @log if File.exists? debhelper_log
38
+ exec %w(dh binary)
39
+ end
40
+
41
+ def install_service(name, command, opts={})
42
+ opts = {user: 'root', group: 'root'}.merge opts
43
+ opts[:command] = command
44
+ @services[name] = opts
45
+ end
46
+
47
+ def install_services
48
+ @services.each do |name, opts|
49
+ File.write debian("#{spec.name}.#{spec.name}-#{name}.upstart"), render(<<-EOF.gsub(/^ {10}/, ''), opts.merge(name: name))
50
+ description "<%= spec.description %> - <%= name %>"
15
51
 
16
- def build
17
- instance_eval &spec.build if spec.build
18
- exec %w(dh build)
52
+ start on started <%= spec.name %>
53
+ stop on stopping <%= spec.name %>
54
+
55
+ respawn
56
+ respawn limit 10 5
57
+ umask 022
58
+
59
+ chdir /usr/share/<%= spec.name %>
60
+
61
+ <% if user %>
62
+ setuid <%= user %>
63
+ <% end %>
64
+ <% if group %>
65
+ setgid <%= group %>
66
+ <% end %>
67
+
68
+ exec <%= command.join(' ') %>
69
+ EOF
70
+ exec %w{dh_installinit --name}, "#{spec.name}-#{name}"
19
71
  end
72
+ end
73
+
74
+ def install_base_service
75
+ File.write debian("#{spec.name}.upstart"), render(<<-EOF.gsub(/^ {8}/, ''))
76
+ description "#{spec.description}"
77
+
78
+ start on filesystem or runlevel [2345]
79
+ stop on runlevel [!2345]
80
+
81
+ respawn
82
+ respawn limit 10 5
83
+ umask 022
20
84
 
21
- def binary
22
- instance_eval &spec.binary if spec.binary
23
- File.write("debian/#{spec.name}.debhelper.log", @log)
24
- exec %w(dh binary)
85
+ pre-start script
86
+ <% pre_scripts.each do |k, command| %>
87
+ <%= command %>
88
+ <% end %>
89
+ end script
90
+
91
+ exec sleep 1d
92
+ EOF
93
+ end
94
+
95
+ private
96
+ def render(template, opts={})
97
+ def opts.method_missing(name)
98
+ self[name]
25
99
  end
100
+ opts[:spec] = spec
101
+ opts[:pre_scripts] = option :service_pre_scripts
102
+ ERB.new(template, nil, '<>').result opts.instance_eval { binding }
103
+ end
104
+
105
+ def debian(*args)
106
+ File.join('debian', *args)
107
+ end
108
+
109
+ def debhelper_log
110
+ debian("#{spec.name}.debhelper.log")
26
111
  end
27
112
  end
28
113
  end