cany 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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