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 +4 -4
- data/CHANGELOG.md +38 -0
- data/Gemfile +12 -0
- data/Guardfile +20 -0
- data/Rakefile +11 -0
- data/lib/cany.rb +88 -19
- data/lib/cany/dependency.rb +70 -0
- data/lib/cany/dpkg/builder.rb +4 -16
- data/lib/cany/dpkg/creator.rb +39 -29
- data/lib/cany/dpkg/deb_helper_recipe.rb +105 -20
- data/lib/cany/errors.rb +62 -0
- data/lib/cany/mixins/depend_mixin.rb +22 -0
- data/lib/cany/recipe.rb +182 -11
- data/lib/cany/recipes/bundler.rb +28 -5
- data/lib/cany/recipes/bundler/gem.rb +50 -0
- data/lib/cany/recipes/bundler/gem_db.rb +27 -0
- data/lib/cany/recipes/rails.rb +19 -2
- data/lib/cany/recipes/sidekiq.rb +38 -0
- data/lib/cany/recipes/thin.rb +24 -18
- data/lib/cany/recipes/unicorn.rb +16 -0
- data/lib/cany/recipes/web_server.rb +5 -19
- data/lib/cany/specification.rb +49 -1
- data/lib/cany/specification/dsl.rb +23 -3
- data/lib/cany/version.rb +2 -2
- data/spec/cany/dependency_spec.rb +120 -0
- data/spec/cany/dpkg/builder_spec.rb +38 -0
- data/spec/{dpkg → cany/dpkg}/creator_spec.rb +26 -2
- data/spec/cany/dpkg/deb_helper_recipe_spec.rb +91 -0
- data/spec/cany/recipe_spec.rb +213 -0
- data/spec/cany/recipes/bundler/gem_spec.rb +46 -0
- data/spec/cany/recipes/bundler_spec.rb +29 -0
- data/spec/cany/recipes/rails_spec.rb +28 -0
- data/spec/cany/recipes/sidekiq_spec.rb +52 -0
- data/spec/cany/specification_spec.rb +113 -1
- data/spec/spec_helper.rb +31 -4
- metadata +29 -6
- data/spec/dpkg/builder_spec.rb +0 -53
data/lib/cany/recipes/rails.rb
CHANGED
@@ -2,20 +2,37 @@ module Cany
|
|
2
2
|
module Recipes
|
3
3
|
class Rails < Recipe
|
4
4
|
register_as :rails
|
5
|
+
hook :env
|
6
|
+
|
7
|
+
class DSL < Recipe::DSL
|
8
|
+
delegate :compile_assets
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :compile_assets
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
@compile_assets = true
|
15
|
+
super
|
16
|
+
end
|
5
17
|
|
6
18
|
def clean
|
7
19
|
rmtree 'tmp', 'public/assets'
|
8
20
|
inner.clean
|
9
21
|
end
|
10
22
|
|
23
|
+
def prepare
|
24
|
+
recipe(:bundler).configure :env_vars, RAILS_ENV: 'production'
|
25
|
+
end
|
26
|
+
|
11
27
|
def build
|
12
|
-
|
13
|
-
ruby_bin 'bundle', 'exec', 'rake', 'assets:precompile'
|
28
|
+
run_hook :env, :before
|
14
29
|
ENV['RAILS_ENV'] = 'production'
|
30
|
+
ruby_bin 'bundle', 'exec', 'rake', 'assets:precompile' if compile_assets
|
15
31
|
inner.build
|
16
32
|
end
|
17
33
|
|
18
34
|
def binary
|
35
|
+
run_hook :env, :after
|
19
36
|
%w(app config.ru db Gemfile Gemfile.lock lib public Rakefile vendor).each do |item|
|
20
37
|
install item, "/usr/share/#{spec.name}" if File.exists? item
|
21
38
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Cany::Recipes
|
2
|
+
# This recipes installs Sidekiq
|
3
|
+
# "Simple, efficient, background processing in Ruby"
|
4
|
+
# Sidekiq is registered as service and so automatically started.
|
5
|
+
# @see http://sidekiq.org/ The official website for more information.
|
6
|
+
# @node The recipe relies that 'sidekiq' is listed inside your Gemfile and
|
7
|
+
# therefore installed via the 'bundler' recipe.
|
8
|
+
class Sidekiq < Cany::Recipe
|
9
|
+
register_as :sidekiq
|
10
|
+
|
11
|
+
attr_accessor :queues
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
@queues = []
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
class DSL < Cany::Recipe::DSL
|
19
|
+
def queue(name)
|
20
|
+
@recipe.queues << name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def binary
|
25
|
+
install_service name, %W(/usr/bin/#{spec.name} sidekiq) + sidekiq_args, user: 'www-data', group: 'www-data'
|
26
|
+
inner.binary
|
27
|
+
end
|
28
|
+
|
29
|
+
def sidekiq_args
|
30
|
+
args = %w(--environment production)
|
31
|
+
if queues.any?
|
32
|
+
args << '--queue'
|
33
|
+
args << queues.join(',')
|
34
|
+
end
|
35
|
+
args
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/cany/recipes/thin.rb
CHANGED
@@ -1,25 +1,31 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
|
-
module Cany
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
module Cany::Recipes
|
4
|
+
# This recipes install the thin ruby web server. It is registered
|
5
|
+
# and started as service.
|
6
|
+
#
|
7
|
+
# A simple thin configuration file (/etc/<application name>/thin.yml) is
|
8
|
+
# created and can be adjusted to the user needs.
|
9
|
+
#
|
10
|
+
# @see http://code.macournoyer.com/thin/ The project website for more
|
11
|
+
# information about the project
|
12
|
+
# @note The receives relies that the 'thin' gem is included in your Gemfile
|
13
|
+
# and therefore installed via bundler (and the bundler recipe).
|
14
|
+
class Thin < WebServer
|
15
|
+
register_as :thin
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
|
17
|
+
def launch_command
|
18
|
+
%W(thin start -C /etc/#{spec.name}/thin.yml")
|
19
|
+
end
|
13
20
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
21
|
+
def binary
|
22
|
+
default_options = {
|
23
|
+
'environment' => 'production',
|
24
|
+
'socket' => "/var/run/#{spec.name}/sock",
|
25
|
+
'pid' => "/var/run/#{spec.name}/thin.pid"
|
26
|
+
}
|
27
|
+
install_content "/etc/#{spec.name}/thin.yml", default_options.to_yaml
|
28
|
+
super
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Cany::Recipes
|
2
|
+
# This recipes install the Unicorn Rack HTTP server. It is registered
|
3
|
+
# and started as service.
|
4
|
+
#
|
5
|
+
# @see http://unicorn.bogomips.org/ The project website for more
|
6
|
+
# information about the project
|
7
|
+
# @note The receives relies that the 'unicorn' gem is included in your
|
8
|
+
# Gemfile and therefore installed via bundler (and the bundler recipe).
|
9
|
+
class Unicorn < WebServer
|
10
|
+
register_as :unicorn
|
11
|
+
|
12
|
+
def launch_command
|
13
|
+
%W(unicorn start -c /etc/#{spec.name}/unicorn.rb -E production)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -2,25 +2,11 @@ module Cany
|
|
2
2
|
module Recipes
|
3
3
|
class WebServer < Recipe
|
4
4
|
def binary
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
5
|
+
recipe(:system).configure :service_pre_scripts, {
|
6
|
+
mkdir_run: "mkdir /var/run/#{spec.name}",
|
7
|
+
chown_run: "chown /var/run/#{spec.name}"
|
8
|
+
}
|
9
|
+
install_service name, ["/usr/bin/#{spec.name}"] + launch_command, user: 'www-data', group: 'www-data'
|
24
10
|
inner.binary
|
25
11
|
end
|
26
12
|
end
|
data/lib/cany/specification.rb
CHANGED
@@ -6,16 +6,64 @@ module Cany
|
|
6
6
|
EXT = 'canspec'
|
7
7
|
|
8
8
|
attr_accessor :name, :description, :maintainer_name, :maintainer_email, :website, :licence, :version
|
9
|
-
attr_accessor :base_dir, :recipes
|
9
|
+
attr_accessor :base_dir, :recipes, :dependencies, :cany_version_constraint
|
10
10
|
attr_accessor :build, :binary
|
11
11
|
|
12
12
|
def initialize(dsl=Cany::Specification::DSL, &block)
|
13
13
|
@recipes = []
|
14
|
+
@dependencies = []
|
14
15
|
setup dsl, &block
|
15
16
|
end
|
16
17
|
|
17
18
|
def setup(dsl=Cany::Specification::DSL, &block)
|
18
19
|
dsl.new(self).exec(&block)
|
19
20
|
end
|
21
|
+
|
22
|
+
# @api public
|
23
|
+
# Return all dependencies needed to build this package
|
24
|
+
# @return [Array<Dependency>] A list of dependencies.
|
25
|
+
def build_dependencies
|
26
|
+
dependencies.select(&:build?)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api public
|
30
|
+
# Return all dependencies needed to build this package
|
31
|
+
# @return [Array<Dependency>] A list of dependencies.
|
32
|
+
def runtime_dependencies
|
33
|
+
dependencies.select(&:runtime?)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Define the given recipe instance as system recipe. It can be accessed by other recipes under
|
37
|
+
# the system name and it is added as last recipe and do system specific things.
|
38
|
+
# At the moment the only recipe designed to work as system recipe is the DebHelperRecipe
|
39
|
+
# The attribute should be set after all recipe changes are over.
|
40
|
+
# @param recipe[Recipe] the system recipe instance
|
41
|
+
def system_recipe=(recipe)
|
42
|
+
@system_recipe = recipe
|
43
|
+
@recipes << recipe
|
44
|
+
end
|
45
|
+
# Return the system recipe for this specification.
|
46
|
+
# @return [Recipe] The system reciep
|
47
|
+
# @raise [NoSystemRecipe] if no system recipe was specified.
|
48
|
+
def system_recipe
|
49
|
+
raise NoSystemRecipe.new unless @system_recipe
|
50
|
+
@system_recipe
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
# This method creates recipe instances for all required recipes from the given spec.
|
55
|
+
def setup_recipes
|
56
|
+
last_recipe = nil
|
57
|
+
recipes.reverse.each do |recipe|
|
58
|
+
recipe.inner = last_recipe unless last_recipe.nil?
|
59
|
+
last_recipe = recipe
|
60
|
+
end
|
61
|
+
recipes.map(&:prepare)
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
"Cany::Specification<#{object_id}>(name=#{name}, recipes=[#{recipes.map(&:name).join(', ')}]"
|
66
|
+
end
|
67
|
+
alias_method :inspect, :to_s
|
20
68
|
end
|
21
69
|
end
|
@@ -6,7 +6,7 @@ module Cany
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def exec(&block)
|
9
|
-
instance_eval
|
9
|
+
instance_eval(&block)
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.delegate(*methods)
|
@@ -21,10 +21,21 @@ module Cany
|
|
21
21
|
|
22
22
|
delegate :name, :description, :maintainer_name, :maintainer_email, :website, :licence, :version
|
23
23
|
|
24
|
+
# This directive ensures that Cany is used in a specific version. It will pass if the version
|
25
|
+
# match or raise an exception if it is an unsupported version.
|
26
|
+
# @param version [String] The version constrain the must be satisfied by Cany.
|
27
|
+
# @raise Cany::UnsupportedVersion on version mismatch
|
28
|
+
def require_cany(version)
|
29
|
+
unless Gem::Requirement.create(version).satisfied_by? Gem::Version.new(Cany::VERSION)
|
30
|
+
raise UnsupportedVersion.new version
|
31
|
+
end
|
32
|
+
@specification.cany_version_constraint = version
|
33
|
+
end
|
34
|
+
|
24
35
|
# This include the given recipe into the build process.
|
25
36
|
# @param [Symbol] name The name of the recipe as symbol.
|
26
|
-
def use(name)
|
27
|
-
@specification.recipes << name
|
37
|
+
def use(name, &block)
|
38
|
+
@specification.recipes << Cany::Recipe.from_name(name).new(@specification, &block)
|
28
39
|
end
|
29
40
|
|
30
41
|
def build(&block)
|
@@ -34,6 +45,15 @@ module Cany
|
|
34
45
|
def binary(&block)
|
35
46
|
@specification.binary = block
|
36
47
|
end
|
48
|
+
|
49
|
+
# @api public
|
50
|
+
# Adds a new dependency for the software. See Cany::Dependency for a more
|
51
|
+
# abstract description about dependencies
|
52
|
+
# See Cany::Mixins::DependMixin for parameter description
|
53
|
+
include Mixins::DependMixin
|
54
|
+
def depend(*args)
|
55
|
+
@specification.dependencies << create_dep(*args)
|
56
|
+
end
|
37
57
|
end
|
38
58
|
end
|
39
59
|
end
|
data/lib/cany/version.rb
CHANGED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cany::Dependency do
|
4
|
+
let(:dep) { Cany::Dependency.new }
|
5
|
+
subject { dep.determine(distro, release) }
|
6
|
+
let(:distro) { :ubuntu }
|
7
|
+
let(:release) { :precise }
|
8
|
+
|
9
|
+
context 'without defined packages' do
|
10
|
+
it 'should return a empty list' do
|
11
|
+
expect(subject).to match_array []
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'without distro information' do
|
16
|
+
context 'and a simple default package' do
|
17
|
+
before { dep.define_default('example') }
|
18
|
+
it 'should return the packages and nil as version' do
|
19
|
+
expect(subject).to match_array [['example', nil]]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'and a default package with version' do
|
24
|
+
before { dep.define_default('example', '>= 1.7') }
|
25
|
+
it 'should return the packages and its version' do
|
26
|
+
expect(subject).to match_array [['example', '>= 1.7']]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with multiple default packages' do
|
31
|
+
before do
|
32
|
+
dep.define_default('example')
|
33
|
+
dep.define_default('otto')
|
34
|
+
end
|
35
|
+
it 'should return all default packages' do
|
36
|
+
expect(subject).to match_array [['otto', nil], ['example', nil]]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'with distribution default packages' do
|
42
|
+
before { dep.define_on_distro(distro, 'hans') }
|
43
|
+
|
44
|
+
it 'should return distro packages' do
|
45
|
+
expect(subject).to match_array [['hans', nil]]
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'and with default packages' do
|
49
|
+
before { dep.define_default('default') }
|
50
|
+
it 'should only return distro package' do
|
51
|
+
expect(subject).to match_array [['hans', nil]]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'and a second distro package' do
|
56
|
+
before { dep.define_on_distro(distro, 'otto') }
|
57
|
+
it 'should return both packages' do
|
58
|
+
expect(subject).to match_array [['hans', nil], ['otto', nil]]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with distribution release information' do
|
64
|
+
before { dep.define_on_distro_release(distro, release, 'distrorelease') }
|
65
|
+
it 'should return distro release packages' do
|
66
|
+
expect(subject).to match_array [['distrorelease', nil]]
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with additional distrorelease packages with version' do
|
70
|
+
before { dep.define_on_distro_release(distro, release, 'add', '!= 2.8') }
|
71
|
+
it 'should return both packages with its version or nil' do
|
72
|
+
expect(subject).to match_array [['distrorelease', nil], ['add', '!= 2.8']]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with distro packages' do
|
77
|
+
before { dep.define_on_distro(distro, 'other') }
|
78
|
+
it 'should return only distrorelease packages' do
|
79
|
+
expect(subject).to match_array [['distrorelease', nil]]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'situations property' do
|
85
|
+
subject { dep.situations }
|
86
|
+
it 'should be runtime per default' do
|
87
|
+
should match_array [:runtime]
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'set to single value' do
|
91
|
+
before { dep.situations = :build }
|
92
|
+
it 'should convert them to an array' do
|
93
|
+
should match_array [:build]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'set to multiple values' do
|
98
|
+
before { dep.situations = [:build, :runtime] }
|
99
|
+
it 'should remain the array' do
|
100
|
+
should match_array [:build, :runtime]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'accessabled via shortcut properties' do
|
105
|
+
subject { dep }
|
106
|
+
it 'should be runtime and not build per default' do
|
107
|
+
should be_runtime
|
108
|
+
should_not be_build
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'with changed value' do
|
112
|
+
before { dep.situations = [:build, :hans] }
|
113
|
+
it 'should behave accordingly' do
|
114
|
+
should_not be_runtime
|
115
|
+
should be_build
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cany::Dpkg::Builder do
|
4
|
+
let!(:dir) { Dir.mktmpdir }
|
5
|
+
after { FileUtils.remove_entry dir}
|
6
|
+
let(:spec) do
|
7
|
+
s = Cany::Specification.new do
|
8
|
+
name 'dpkg-creator-test'
|
9
|
+
version '0.1'
|
10
|
+
description 'Test Project'
|
11
|
+
maintainer_name 'Hans Otto'
|
12
|
+
maintainer_email 'hans.otto@example.org'
|
13
|
+
website 'http://example.org'
|
14
|
+
licence 'GPL-2+'
|
15
|
+
end
|
16
|
+
s.base_dir = dir
|
17
|
+
s
|
18
|
+
end
|
19
|
+
let(:builder) { Cany::Dpkg::Builder.new(spec) }
|
20
|
+
|
21
|
+
describe '#run' do
|
22
|
+
subject { builder.run 'clean' }
|
23
|
+
it 'should set system_recipe' do
|
24
|
+
expect(spec).to receive(:system_recipe=).with(kind_of(Cany::Dpkg::DebHelperRecipe)).and_call_original
|
25
|
+
subject
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should setup recipes' do
|
29
|
+
expect(spec).to receive(:setup_recipes).and_call_original
|
30
|
+
subject
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should call delegate the clean action to the loaded recipes' do
|
34
|
+
expect_any_instance_of(Cany::Dpkg::DebHelperRecipe).to receive(:clean)
|
35
|
+
subject
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|