webbynode-blueprint 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.
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
+