cany 0.0.1
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 +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
|