cany 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
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
11
+
12
+ def clean
13
+ exec %w(dh clean)
14
+ end
15
+
16
+ def build
17
+ instance_eval &spec.build if spec.build
18
+ exec %w(dh build)
19
+ end
20
+
21
+ def binary
22
+ instance_eval &spec.binary if spec.binary
23
+ File.write('debian/xikolo-account.debhelper.log', @log)
24
+ exec %w(dh binary)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,128 @@
1
+ require 'fileutils'
2
+
3
+ module Cany
4
+ class Recipe
5
+
6
+ # @api public
7
+ # This method should be call in subclasses to register new recipe instances. Cany ignores any
8
+ # recipe subclasses which does not call register_as. If multiple recipes register on the same
9
+ # name the later one will overwrite the earlier one and therefore used by Cany.
10
+ # @param [Symbol] name A ruby symbol as name for the recipe. It should be short and recognizable
11
+ def self.register_as(name)
12
+ @@recipes ||= {}
13
+ @@recipes[name] = self
14
+ end
15
+
16
+ # @api public
17
+ # Looks for the class registered for the given name
18
+ # @param [Symbol] name the name the class is search for
19
+ # @return [Cany::Recipe, nil] Returns the found class or nil if no class is registered on this
20
+ # name
21
+ def self.from_name(name)
22
+ @@recipes[name]
23
+ end
24
+
25
+ # Creates a new instance of this recipe
26
+ # @param [Cany::Specification] spec Specification object
27
+ # @param [Cany::Recipe, nil] inner Inner recipes should should be call between the pre and post
28
+ # actions of this class. Nil means most inner recipes.
29
+ def initialize(spec, inner)
30
+ @spec = spec
31
+ @inner = inner
32
+ end
33
+
34
+ attr_reader :spec, :inner
35
+
36
+ # API to use inside the recipe
37
+ ##############################
38
+
39
+ # @api public
40
+ # Run a command inside the build directory. In most cases it is not needed to call this method
41
+ # directly. Look at the other helper methods.
42
+ #
43
+ # The method expects as arguments the program name and additional parameters for the program.
44
+ # The arguments can be group with arguments, but must not:
45
+ # @example
46
+ # exec 'echo', %w(a b)
47
+ # exec ['echo', 'a', 'b']
48
+ # exec 'echo', 'a', 'b'
49
+ def exec(*args)
50
+ puts " #{args.flatten.join(' ')}"
51
+ system *args.flatten
52
+ end
53
+
54
+ # @api public
55
+ # Run a ruby task (like gem, bundler, rake ...)
56
+ #
57
+ # The method expects as arguments the program name and additional parameters for the program.
58
+ # See exec for more examples
59
+ def ruby_bin(*args)
60
+ exec 'ruby', '-S', *args
61
+ end
62
+
63
+ # @api public
64
+ # Install files or directory from the build directory
65
+ # @param [String] source The relative file name to a filename or directory inside the build
66
+ # directory that should be installed/copied into the destination package
67
+ # @param [String] destination The diretory name into that the file or directory should be
68
+ # installed
69
+ def install(src, dest_dir)
70
+ exec 'dh_install', src, dest_dir
71
+ end
72
+
73
+ # @api public
74
+ # Install a file. The content is passed as argument. This method is designed to be used by
75
+ # recipes to create files dynamically.
76
+ # @param [String] filename The absolute file name for the file inside the package.
77
+ # @param [String] content The file content
78
+ def install_content(filename, content)
79
+ FileUtils.mkdir_p File.dirname File.join('debian', spec.name, filename)
80
+ File.open File.join('debian', spec.name, filename), 'w' do |f|
81
+ f.write content
82
+ end
83
+ end
84
+
85
+ # @api public
86
+ # Installs/creates an empty directory
87
+ # @param [String] path The path name
88
+ def install_dir(path)
89
+ exec 'dh_installdirs', path
90
+ end
91
+
92
+ # @api public
93
+ # Create a file named destination as a link to a file named source
94
+ def install_link(source, destination)
95
+ exec 'dh_link', source, destination
96
+ end
97
+
98
+ # @api public
99
+ # Ensure that the given files or directories are no present. Directories are removed
100
+ # recursively.
101
+ def rmtree(*args)
102
+ args.flatten.each do |path|
103
+ ::FileUtils.remove_entry path if File.exists? path
104
+ end
105
+ end
106
+
107
+ # default implementation:
108
+ #########################
109
+
110
+ # @api public
111
+ # clean the build directory from all temporary and created files
112
+ def clean
113
+ inner.clean
114
+ end
115
+
116
+ # @api public
117
+ # build the program (means ./configure and make)
118
+ def build
119
+ inner.build
120
+ end
121
+
122
+ # @api public
123
+ # create binary (package) version of this file (means make install)
124
+ def binary
125
+ inner.binary
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,33 @@
1
+ module Cany
2
+ module Recipes
3
+ class Bundler < Cany::Recipe
4
+ register_as :bundler
5
+
6
+ def clean
7
+ rmtree 'bundler'
8
+ # rmtree 'vendor/bundle' -- do not remove gems, increase testing time
9
+ inner.clean
10
+ end
11
+
12
+ def build
13
+ ENV['GEM_PATH'] = 'bundler'
14
+ ENV['PATH'] = 'bundler/bin:' + ENV['PATH']
15
+ ruby_bin 'gem', %w(install bundler --no-ri --no-rdoc --install-dir bundler --bindir bundler/bin)
16
+ ruby_bin 'bundle', %w(install --deployment --without development test)
17
+ inner.build
18
+ end
19
+
20
+ def binary
21
+ install 'bundler', "/usr/share/#{spec.name}"
22
+ install '.bundle', "/usr/share/#{spec.name}"
23
+ install 'vendor/bundle', "/usr/share/#{spec.name}/vendor"
24
+ install_content "/usr/bin/#{spec.name}", "#!/bin/sh
25
+ cd /usr/share/#{spec.name}
26
+ export GEM_PATH=/usr/share/#{spec.name}/bundler
27
+ exec /usr/share/#{spec.name}/bundler/bin/bundle exec \"$@\"
28
+ "
29
+ inner.binary
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ module Cany
2
+ module Recipes
3
+ class Rails < Recipe
4
+ register_as :rails
5
+
6
+ def clean
7
+ rmtree 'tmp', 'public/assets'
8
+ inner.clean
9
+ end
10
+
11
+ def build
12
+ ruby_bin 'rake', 'assets:precompile'
13
+ inner.build
14
+ end
15
+
16
+ def binary
17
+ %w(app config.ru db Gemfile Gemfile.lock lib public Rakefile vendor).each do |item|
18
+ install item, "/usr/share/#{spec.name}" if File.exists? item
19
+ end
20
+
21
+ Dir.foreach('config') do |entry|
22
+ next if %w(. ..).include? entry
23
+ install File.join('config', entry), "/etc/#{spec.name}"
24
+ end
25
+ install_link "/etc/#{spec.name}", "/usr/share/#{spec.name}/config"
26
+
27
+ install_dir "/etc/#{spec.name}"
28
+ install_dir "/var/tmp/#{spec.name}"
29
+ install_dir "/var/log/#{spec.name}"
30
+
31
+ install_link "/var/log/#{spec.name}", "/usr/share/#{spec.name}/log"
32
+ install_link "/var/tmp/#{spec.name}", "/usr/share/#{spec.name}/tmp"
33
+ inner.binary
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ require 'yaml'
2
+
3
+ module Cany
4
+ module Recipes
5
+ # This recipes runs the application with the thin web server. At the moment it installs only a
6
+ # upstart start script. Other init scripts are planed (e.g. systemd for debian).
7
+ class Thin < WebServer
8
+ register_as :thin
9
+
10
+ def launch_command
11
+ "thin start -C /etc/#{spec.name}/thin.yml"
12
+ end
13
+
14
+ def binary
15
+ default_options = {
16
+ 'environment' => 'production',
17
+ 'socket' => "/var/run/#{spec.name}/sock",
18
+ 'pid' => "/var/run/#{spec.name}/thin.pid"
19
+ }
20
+ install_content "/etc/#{spec.name}/thin.yml", default_options.to_yaml
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ module Cany
2
+ module Recipes
3
+ class WebServer < Recipe
4
+ def binary
5
+ File.open File.join('debian', "#{spec.name}.upstart"), 'w' do |f|
6
+ f.write "description \"#{spec.description}\"\n"
7
+
8
+ f.write "start on filesystem or runlevel [2345]\n"
9
+ f.write "stop on runlevel [!2345]\n"
10
+
11
+ f.write "respawn\n"
12
+ f.write "respawn limit 10 5\n"
13
+ f.write "umask 022\n"
14
+
15
+ f.write "chdir /usr/share/#{spec.name}\n"
16
+
17
+ f.write "pre-start script\n"
18
+ f.write "\tmkdir -p /var/run/#{spec.name}\n"
19
+ f.write "\tchown www-data /var/run/#{spec.name}\n"
20
+ f.write "end script\n"
21
+
22
+ f.write "exec su www-data --shell /usr/bin/#{spec.name} -- #{launch_command}\n"
23
+ end
24
+ inner.binary
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ module Cany
2
+
3
+ require 'cany/specification/dsl'
4
+
5
+ class Specification
6
+ EXT = 'canspec'
7
+
8
+ attr_accessor :name, :description, :maintainer_name, :maintainer_email, :website, :licence, :version
9
+ attr_accessor :base_dir, :recipes
10
+ attr_accessor :build, :binary
11
+
12
+ def initialize(dsl=Cany::Specification::DSL, &block)
13
+ @recipes = []
14
+ setup dsl, &block
15
+ end
16
+
17
+ def setup(dsl=Cany::Specification::DSL, &block)
18
+ dsl.new(self).exec(&block)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ module Cany
2
+ class Specification
3
+ class DSL
4
+ def initialize(specification)
5
+ @specification = specification
6
+ end
7
+
8
+ def exec(&block)
9
+ instance_eval &block
10
+ end
11
+
12
+ def self.delegate(*methods)
13
+ methods.each do |method|
14
+ module_eval(<<-EOS, __FILE__, __LINE__)
15
+ def #{method}(*args, &block)
16
+ @specification.send :'#{method}=', *args, &block
17
+ end
18
+ EOS
19
+ end
20
+ end
21
+
22
+ delegate :name, :description, :maintainer_name, :maintainer_email, :website, :licence, :version
23
+
24
+ # This include the given recipe into the build process.
25
+ # @param [Symbol] name The name of the recipe as symbol.
26
+ def use(name)
27
+ @specification.recipes << name
28
+ end
29
+
30
+ def build(&block)
31
+ @specification.build = block
32
+ end
33
+
34
+ def binary(&block)
35
+ @specification.binary = block
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ module Cany
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ PATCH = 1
6
+ STAGE = nil
7
+ STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.')
8
+ def self.to_s; STRING end
9
+ end
10
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cany::Specification do
4
+ describe '#new' do
5
+ it 'should have a name' do
6
+ spec = Cany::Specification.new do
7
+ name 'example'
8
+ end
9
+ expect(spec.name).to eq 'example'
10
+ end
11
+
12
+ it 'should have a description' do
13
+ spec = Cany::Specification.new do
14
+ description 'Test hans otto project'
15
+ end
16
+ expect(spec.description).to eq 'Test hans otto project'
17
+ end
18
+
19
+ it 'should have a maintainer name' do
20
+ spec = Cany::Specification.new do
21
+ maintainer_name 'Otto Hans'
22
+ end
23
+ expect(spec.maintainer_name).to eq 'Otto Hans'
24
+ end
25
+
26
+ it 'should have a maintainer email' do
27
+ spec = Cany::Specification.new do
28
+ maintainer_email 'hans.otto@example.org'
29
+ end
30
+ expect(spec.maintainer_email).to eq 'hans.otto@example.org'
31
+ end
32
+
33
+ it 'should have a website' do
34
+ spec = Cany::Specification.new do
35
+ website 'http://example.org/~hans.otto/'
36
+ end
37
+ expect(spec.website).to eq 'http://example.org/~hans.otto/'
38
+ end
39
+
40
+ it 'should have a licence' do
41
+ spec = Cany::Specification.new do
42
+ licence 'MIT'
43
+ end
44
+ expect(spec.licence).to eq 'MIT'
45
+ end
46
+
47
+ it 'should have a version' do
48
+ spec = Cany::Specification.new do
49
+ version '0.1'
50
+ end
51
+ expect(spec.version).to eq '0.1'
52
+ end
53
+
54
+ it 'should be able to include recipes' do
55
+ spec = Cany::Specification.new do
56
+ use :bundler
57
+ end
58
+ expect(spec.recipes).to eq [:bundler]
59
+ end
60
+
61
+ it 'should accept a block for own build steps' do
62
+ spec = Cany::Specification.new do
63
+ build do
64
+ install 'hans', 'otto'
65
+ end
66
+ end
67
+ expect(spec.build).to be_a_kind_of Proc
68
+ end
69
+
70
+ it 'should accept a block for own build steps' do
71
+ spec = Cany::Specification.new do
72
+ binary do
73
+ install 'hans', 'otto'
74
+ end
75
+ end
76
+ expect(spec.binary).to be_a_kind_of Proc
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cany do
4
+ context '#setup' do
5
+ it 'should require a .canspec file' do
6
+ expect { Cany::setup File.expand_path('../fixtures/empty', __FILE__) }.to raise_error(Cany::MissingSpecification)
7
+ end
8
+ end
9
+
10
+ it 'should stop on multiple .canspec files' do
11
+ expect { Cany::setup File.expand_path('../fixtures/multiple', __FILE__) }.to raise_error Cany::MultipleSpecifications, /Multiple canspec found in .*/
12
+ end
13
+
14
+ it 'should load a .canspec file' do
15
+ spec = Cany::setup File.expand_path('../fixtures/single', __FILE__)
16
+ expect(spec).to be_an_instance_of Cany::Specification
17
+ expect(spec.name).to eq 'single-test'
18
+ expect(spec.base_dir).to eq File.expand_path('../fixtures/single', __FILE__)
19
+ end
20
+ end