webbynode-blueprint 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Webbynode
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,81 @@
1
+ = blueprint
2
+
3
+ Blueprint is a DSL that allows creating MyStacks and CommunityStacks to be deployed on Webbynode.
4
+
5
+ The current incarnation is to be considered early alpha, and only builds the Hash required to describe the components of the Stack.
6
+
7
+ As code advances, this will become a full API for interacting with Webbynode and maintaining stacks.
8
+
9
+ Before upcoming sections, kudos to Sean O'Halpin, the guy behind the ruby lib/gem {Doodle}[http://doodle.rubyforge.org/] for allowing me to use his Doodle::Util module on this. Thanks Sean!
10
+
11
+ == Example
12
+
13
+ Code of a blueprint will be something like this:
14
+
15
+ rails_rs = blueprint(:type => "readystack", :name => "rs.rails") do |f|
16
+ # this blueprint delivers "rs.rails"
17
+ f.provides "rs.rails"
18
+
19
+ # dependencies and order of execution -- top => bottom
20
+ # apache2/ngnix, mysql/postgre, passenger/mongrel
21
+ f.requires :group => "webservers",
22
+ :with => ["apache2", "nginx"]
23
+ f.requires :group => "database",
24
+ :with => ["mysql-server", "postgresqlserver"]
25
+ f.requires "rails"
26
+ f.requires :group => "proxy",
27
+ :with => ["passenger", "mongrel_cluster"]
28
+
29
+ # attributes
30
+ f.attributes :required => 'y'
31
+ f.outputs :installed_gems => "^Installed following gems: (.*)",
32
+ :success_indicator => "^SUCCESS: (.*)"
33
+
34
+ # always installs rails
35
+ f.dependency "rails", :render_as => "hidden"
36
+
37
+ # gives two webserver/proxy combo options:
38
+ # apache2 + passenger or nginx + mongrel
39
+ f.parameter "webserver-proxy", :label => "WebServer and Proxy" do |p|
40
+ p.attributes :required => "y"
41
+
42
+ p.aggregate "Apache2 and Passenger", :render_as => "radio" do |agr|
43
+ agr.dependency "apache2"
44
+ agr.dependency "passenger"
45
+ end
46
+
47
+ p.aggregate "Nginx and Mongrel Cluster", :render_as => "radio" do |agr|
48
+ agr.dependency "nginx"
49
+ agr.dependency "mongrel_cluster"
50
+ end
51
+
52
+ # doesn't install webserver/proxy
53
+ p.aggregate "No webserver & proxy", :render_as => "radio" do |agr|
54
+ agr.parameter "-"
55
+ end
56
+ end
57
+
58
+ # database options: mysql or postgresql
59
+ f.parameter "database-server", :label => "Database Server" do |p|
60
+ p.attributes :required => "y"
61
+
62
+ p.dependency "mysql-server", :label => "MySQL", :render_as => "radio"
63
+ p.dependency "postgresqlserver", :label => "PostgreSQL", :render_as => "radio"
64
+ p.no_op "No database server", :render_as => "radio"
65
+ end
66
+
67
+ # list of gems
68
+ f.aggregate "Additional Gems" do |agr|
69
+ %w{will_paginate thoughtbot-paperclip
70
+ rspec authlogic hpricot capistrano}.each do |gem|
71
+
72
+ agr.parameter "gem_#{gem}", :label => gem, :render_as => "checkbox"
73
+
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+ == Copyright
80
+
81
+ Copyright (c) 2009 Webbynode. See LICENSE for details.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 0
3
+ :patch: 1
4
+ :major: 0
@@ -0,0 +1,54 @@
1
+ require "blueprint/components"
2
+ require "blueprint/utils"
3
+ require "yaml"
4
+
5
+ class Blueprint
6
+ include Utils
7
+ include BlueprintComponents
8
+
9
+ def initialize(blueprint_def)
10
+ @def = blueprint_def
11
+ end
12
+
13
+ def provides(*args)
14
+ opts = args.last.is_a?(Hash) ? args.pop : nil
15
+ s = (@def[:content] = args.pop)
16
+
17
+ if opts
18
+ @def.merge!(opts)
19
+
20
+ else
21
+ @def[:script] = "#{s}.sh"
22
+ @def[:email] = "#{s}.markdown"
23
+
24
+ end
25
+
26
+ (errors ||= []) << "provides requires an argument" unless @def.has_key?(:email)
27
+ (errors ||= []) << "no email template found for #{@def[:content]}" unless @def.has_key?(:email)
28
+ (errors ||= []) << "no script found for #{@def[:content]}" unless @def.has_key?(:script)
29
+
30
+ raise "Errors: #{errors * ", "}" if errors
31
+ end
32
+
33
+ def requires(req)
34
+ if req.is_a?(Hash)
35
+ requirement_def = { :group => req[:group], :contains => req[:with] }
36
+ else
37
+ requirement_def = { :group => req, :contains => req }
38
+ end
39
+
40
+ (@def[:dependencies] ||= []) << requirement_def
41
+ end
42
+
43
+ def attributes(attrs)
44
+ @def[:attributes] = attrs
45
+ end
46
+
47
+ def outputs(attrs)
48
+ @def[:output_params] = attrs
49
+ end
50
+
51
+ def to_yaml
52
+ stringify_keys(@def, true).to_yaml
53
+ end
54
+ end
@@ -0,0 +1,104 @@
1
+ require "blueprint/utils"
2
+
3
+ module BlueprintComponents
4
+ class Component
5
+ class << self
6
+ attr_accessor :item_type
7
+ attr_accessor :block
8
+ end
9
+
10
+ def self.creates(*args, &block)
11
+ self.item_type = args.pop
12
+ if block_given?
13
+ self.block = block
14
+ end
15
+ end
16
+
17
+ def attributes(*args)
18
+ opts = args.last.is_a?(Hash) ? args.pop : {}
19
+ @def.merge!(opts)
20
+ end
21
+
22
+ def initialize(*args)
23
+ opts = args.last.is_a?(Hash) ? args.pop : {}
24
+ @def = { :item_type => self.class.item_type }.merge(opts)
25
+ if self.class.block
26
+ @def.merge!(self.class.block.call(args, opts))
27
+ end
28
+ end
29
+ end
30
+
31
+ class Value < Component
32
+ include BlueprintComponents
33
+
34
+ creates "value" do |args, opts|
35
+ { :content => args.pop }.merge(opts)
36
+ end
37
+ end
38
+
39
+ class Parameter < Component
40
+ include BlueprintComponents
41
+
42
+ def value(*args)
43
+ val = Value.new(*args)
44
+ unless val.definition[:label]
45
+ val.definition[:label] = val.definition[:content]
46
+ end
47
+ add_child val
48
+ end
49
+
50
+ creates "param" do |args, opts|
51
+ { :content => args.pop }.merge(opts)
52
+ end
53
+ end
54
+
55
+ class Aggregate < Component
56
+ include BlueprintComponents
57
+
58
+ creates "aggregate" do |args, opts|
59
+ { :label => args.pop }.merge(opts)
60
+ end
61
+ end
62
+
63
+ class Dependency < Component
64
+ include BlueprintComponents
65
+
66
+ creates "dependency" do |args, opts|
67
+ { :content => args.pop }.merge(opts)
68
+ end
69
+ end
70
+
71
+ def parameter(*args, &block)
72
+ param = Parameter.new(*args)
73
+ yield param if block_given?
74
+ add_child param
75
+ end
76
+
77
+ def dependency(*args, &block)
78
+ dep = Dependency.new(*args)
79
+ yield dep if block_given?
80
+ add_child dep
81
+ end
82
+
83
+ def no_op(*args, &block)
84
+ opts = args.last.is_a?(Hash) ? args.pop : {}
85
+ dependency nil, { :label => args.pop }.merge(opts)
86
+ end
87
+
88
+ def aggregate(*args, &block)
89
+ aggregate = Aggregate.new(*args)
90
+ yield aggregate if block_given?
91
+ add_child aggregate
92
+ end
93
+
94
+ def definition
95
+ @def
96
+ end
97
+
98
+ module_function
99
+
100
+ def add_child(control)
101
+ (@def[:children] ||= []) << control.definition
102
+ control
103
+ end
104
+ end
@@ -0,0 +1,172 @@
1
+ # Shameless copy of Doodle::Utils (by Sean O'Halpin - http://doodle.rubyforge.org/)
2
+ module Utils
3
+ module ClassMethods
4
+
5
+ # unnest arrays by one level of nesting, e.g. [1, [[2], 3]] =>
6
+ # [1, [2], 3].
7
+ def flatten_first_level(enum)
8
+ enum.inject([]) {|arr, i|
9
+ if i.kind_of?(Array)
10
+ arr.push(*i)
11
+ else
12
+ arr.push(i)
13
+ end
14
+ }
15
+ end
16
+
17
+ # convert a CamelCasedWord to a snake_cased_word
18
+ # based on version in facets/string/case.rb, line 80
19
+ def snake_case(camel_cased_word)
20
+ # if all caps, just downcase it
21
+ if camel_cased_word =~ /^[A-Z]+$/
22
+ camel_cased_word.downcase
23
+ else
24
+ camel_cased_word.to_s.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
25
+ end
26
+ end
27
+ alias :snakecase :snake_case
28
+
29
+ # resolve a constant of the form Some::Class::Or::Module -
30
+ # doesn't work with constants defined in anonymous
31
+ # classes/modules
32
+ def const_resolve(constant)
33
+ constant.to_s.split(/::/).reject{|x| x.empty?}.inject(Object) { |prev, this| prev.const_get(this) }
34
+ end
35
+
36
+ # deep copy of object (unlike shallow copy dup or clone)
37
+ def deep_copy(obj)
38
+ Marshal.load(Marshal.dump(obj))
39
+ end
40
+
41
+ # normalize hash keys using method (e.g. +:to_sym+, +:to_s+)
42
+ #
43
+ # [+hash+] target hash to update
44
+ # [+recursive+] recurse into child hashes if +true+ (default is not to recurse)
45
+ # [+method+] method to apply to key (default is +:to_sym+)
46
+ def normalize_keys!(hash, recursive = false, method = :to_sym)
47
+ if hash.kind_of?(Hash)
48
+ hash.keys.each do |key|
49
+ normalized_key = key.respond_to?(method) ? key.send(method) : key
50
+ v = hash.delete(key)
51
+ if recursive
52
+ if v.kind_of?(Hash)
53
+ v = normalize_keys!(v, recursive, method)
54
+ elsif v.kind_of?(Array)
55
+ v = v.map{ |x| normalize_keys!(x, recursive, method) }
56
+ end
57
+ end
58
+ hash[normalized_key] = v
59
+ end
60
+ end
61
+ hash
62
+ end
63
+
64
+ # normalize hash keys using method (e.g. :to_sym, :to_s)
65
+ # - returns copy of hash
66
+ # - optionally recurse into child hashes
67
+ # see #normalize_keys! for details
68
+ def normalize_keys(hash, recursive = false, method = :to_sym)
69
+ if recursive
70
+ h = deep_copy(hash)
71
+ else
72
+ h = hash.dup
73
+ end
74
+ normalize_keys!(h, recursive, method)
75
+ end
76
+
77
+ # convert keys to symbols
78
+ # - updates target hash in place
79
+ # - optionally recurse into child hashes
80
+ def symbolize_keys!(hash, recursive = false)
81
+ normalize_keys!(hash, recursive, :to_sym)
82
+ end
83
+
84
+ # convert keys to symbols
85
+ # - returns copy of hash
86
+ # - optionally recurse into child hashes
87
+ def symbolize_keys(hash, recursive = false)
88
+ normalize_keys(hash, recursive, :to_sym)
89
+ end
90
+
91
+ # convert keys to strings
92
+ # - updates target hash in place
93
+ # - optionally recurse into child hashes
94
+ def stringify_keys!(hash, recursive = false)
95
+ normalize_keys!(hash, recursive, :to_s)
96
+ end
97
+
98
+ # convert keys to strings
99
+ # - returns copy of hash
100
+ # - optionally recurse into child hashes
101
+ def stringify_keys(hash, recursive = false)
102
+ normalize_keys(hash, recursive, :to_s)
103
+ end
104
+
105
+ # simple (!) pluralization - if you want fancier, override this method
106
+ def pluralize(string)
107
+ s = string.to_s
108
+ if s =~ /s$/
109
+ s + 'es'
110
+ else
111
+ s + 's'
112
+ end
113
+ end
114
+
115
+ # caller
116
+ def doodle_caller
117
+ if $DEBUG
118
+ caller
119
+ else
120
+ [caller[-1]]
121
+ end
122
+ end
123
+
124
+ # execute block - catch any exceptions and return as value
125
+ def try(&block)
126
+ begin
127
+ block.call
128
+ rescue Exception => e
129
+ e
130
+ end
131
+ end
132
+
133
+ # normalize a name to contain only those characters which are
134
+ # valid for a Ruby constant
135
+ def normalize_const(const)
136
+ const.to_s.gsub(/[^A-Za-z_0-9]/, '')
137
+ end
138
+
139
+ # lookup a constant along the module nesting path
140
+ def const_lookup(const, context = self)
141
+ #p [:const_lookup, const, context]
142
+ const = Utils.normalize_const(const)
143
+ result = nil
144
+ if !context.kind_of?(Module)
145
+ context = context.class
146
+ end
147
+ klasses = context.to_s.split(/::/)
148
+ #p klasses
149
+
150
+ path = []
151
+ 0.upto(klasses.size - 1) do |i|
152
+ path << Doodle::Utils.const_resolve(klasses[0..i].join('::'))
153
+ end
154
+ path = (path.reverse + context.ancestors).flatten
155
+ #p [:const, context, path]
156
+ path.each do |ctx|
157
+ #p [:checking, ctx]
158
+ if ctx.const_defined?(const)
159
+ result = ctx.const_get(const)
160
+ break
161
+ end
162
+ end
163
+ if result.nil?
164
+ raise NameError, "Uninitialized constant #{const} in context #{context}"
165
+ else
166
+ result
167
+ end
168
+ end
169
+ end
170
+ extend ClassMethods
171
+ include ClassMethods
172
+ end
data/lib/blueprint.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'blueprint/blueprint'
2
+ require 'blueprint/components'
3
+ require 'blueprint/utils'
4
+
5
+ def blueprint(*args, &block)
6
+ blueprint_def = args.last.is_a?(Hash) ? args.pop : {}
7
+ blueprint = Blueprint.new(blueprint_def)
8
+
9
+ if block_given?
10
+ yield blueprint
11
+ end
12
+
13
+ blueprint
14
+ end
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+
3
+ class BlueprintTest < Test::Unit::TestCase
4
+ def rails_blueprint
5
+ blueprint(:type => "stack", :name => "rails") do |f|
6
+ f.provides "rails"
7
+
8
+ f.parameter "version", :label => "Rails Version", :render_as => "combobox" do |p|
9
+ p.value "2.3.2"
10
+ p.value "2.2.2"
11
+ p.value "2.1.2"
12
+ end
13
+
14
+ f.parameter "doc_rdoc", :label => "Install rdoc", :render_as => "checkbox"
15
+ f.parameter "doc_ri", :label => "Install ri", :render_as => "checkbox"
16
+
17
+ f.parameter "create-dummyapp",
18
+ :label => "Create dummy app?",
19
+ :render_as => "checkbox",
20
+ :attributes => {:default => "y"}
21
+ f.parameter "dummyapp-path",
22
+ :label => "Path for dummy app",
23
+ :render_as => "text",
24
+ :attributes => {
25
+ "required" => "y",
26
+ "default" => "/var/rails",
27
+ "validation" => "^((?:\/[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*(?:\-[a-zA-Z0-9]+)*)+)$",
28
+ "validation_error" => "Use a valid path without an ending slash (ie, <code>/var/www</code>)"
29
+ }
30
+
31
+ f.parameter "dummyapp-database",
32
+ :label => "Database for dummy app",
33
+ :render_as => "combobox" do |p|
34
+
35
+ p.value "sqlite3", :label => "SQLite 3"
36
+ p.value "mysql", :label => "MySQL"
37
+ p.value "sqlite2", :label => "SQLite 2"
38
+ p.value "postgresql", :label => "PostgreSQL"
39
+
40
+ end
41
+ end
42
+ end
43
+
44
+ def rails_readystack_blueprint
45
+ blueprint(:type => "readystack", :name => "rs.rails") do |f|
46
+ # this blueprint delivers "rs.rails"
47
+ f.provides "rs.rails"
48
+
49
+ # dependencies and order of execution -- top => bottom
50
+ # apache2/ngnix, mysql/postgre, passenger/mongrel
51
+ f.requires :group => "webservers",
52
+ :with => ["apache2", "nginx"]
53
+ f.requires :group => "database",
54
+ :with => ["mysql-server", "postgresqlserver"]
55
+ f.requires "rails"
56
+ f.requires :group => "proxy",
57
+ :with => ["passenger", "mongrel_cluster"]
58
+
59
+ # attributes
60
+ f.attributes :required => 'y'
61
+ f.outputs :installed_gems => "^Installed following gems: (.*)",
62
+ :success_indicator => "^SUCCESS: (.*)"
63
+
64
+ # always installs rails
65
+ f.dependency "rails", :render_as => "hidden"
66
+
67
+ # gives two webserver/proxy combo options:
68
+ # apache2 + passenger or nginx + mongrel
69
+ f.parameter "webserver-proxy", :label => "WebServer and Proxy" do |p|
70
+ p.attributes :required => "y"
71
+
72
+ p.aggregate "Apache2 and Passenger", :render_as => "radio" do |agr|
73
+ agr.dependency "apache2"
74
+ agr.dependency "passenger"
75
+ end
76
+
77
+ p.aggregate "Nginx and Mongrel Cluster", :render_as => "radio" do |agr|
78
+ agr.dependency "nginx"
79
+ agr.dependency "mongrel_cluster"
80
+ end
81
+
82
+ # doesn't install webserver/proxy
83
+ p.aggregate "No webserver & proxy", :render_as => "radio" do |agr|
84
+ agr.parameter "-"
85
+ end
86
+ end
87
+
88
+ # database options: mysql or postgresql
89
+ f.parameter "database-server", :label => "Database Server" do |p|
90
+ p.attributes :required => "y"
91
+
92
+ p.dependency "mysql-server", :label => "MySQL", :render_as => "radio"
93
+ p.dependency "postgresqlserver", :label => "PostgreSQL", :render_as => "radio"
94
+ p.no_op "No database server", :render_as => "radio"
95
+ end
96
+
97
+ # list of gems
98
+ f.aggregate "Additional Gems" do |agr|
99
+ %w{will_paginate thoughtbot-paperclip
100
+ rspec authlogic hpricot capistrano}.each do |gem|
101
+
102
+ agr.parameter "gem_#{gem}", :label => gem, :render_as => "checkbox"
103
+
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ should "probably rename this file and start testing for real" do
110
+ puts rails_blueprint.to_yaml
111
+ puts rails_readystack_blueprint.to_yaml
112
+ end
113
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'blueprint'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: webbynode-blueprint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Felipe Coury
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-29 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: felipe@webbynode.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - LICENSE
25
+ files:
26
+ - README.rdoc
27
+ - VERSION.yml
28
+ - lib/blueprint
29
+ - lib/blueprint/blueprint.rb
30
+ - lib/blueprint/components.rb
31
+ - lib/blueprint/utils.rb
32
+ - lib/blueprint.rb
33
+ - test/blueprint_test.rb
34
+ - test/test_helper.rb
35
+ - LICENSE
36
+ has_rdoc: true
37
+ homepage: http://github.com/webbynode/blueprint
38
+ post_install_message:
39
+ rdoc_options:
40
+ - --inline-source
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.2.0
60
+ signing_key:
61
+ specification_version: 2
62
+ summary: Wanna build a stack? Give us the blueprint.
63
+ test_files: []
64
+