cany 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +115 -0
- data/Rakefile +5 -0
- data/bin/cany +19 -0
- data/cany.gemspec +24 -0
- data/lib/cany.rb +29 -0
- data/lib/cany/dpkg.rb +4 -0
- data/lib/cany/dpkg/builder.rb +37 -0
- data/lib/cany/dpkg/creator.rb +145 -0
- data/lib/cany/dpkg/deb_helper_recipe.rb +28 -0
- data/lib/cany/recipe.rb +128 -0
- data/lib/cany/recipes/bundler.rb +33 -0
- data/lib/cany/recipes/rails.rb +37 -0
- data/lib/cany/recipes/thin.rb +25 -0
- data/lib/cany/recipes/web_server.rb +28 -0
- data/lib/cany/specification.rb +21 -0
- data/lib/cany/specification/dsl.rb +39 -0
- data/lib/cany/version.rb +10 -0
- data/spec/cany/specification_spec.rb +79 -0
- data/spec/cany_spec.rb +20 -0
- data/spec/dpkg/builder_spec.rb +53 -0
- data/spec/dpkg/creator_spec.rb +221 -0
- data/spec/fixtures/multiple/one.canspec +0 -0
- data/spec/fixtures/multiple/two.canspec +0 -0
- data/spec/fixtures/single/single.canspec +3 -0
- data/spec/fixtures/testgem.lock +10 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/file_matchers.rb +23 -0
- metadata +128 -0
@@ -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
|
data/lib/cany/recipe.rb
ADDED
@@ -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
|
data/lib/cany/version.rb
ADDED
@@ -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
|
data/spec/cany_spec.rb
ADDED
@@ -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
|