devstructure 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,98 @@
1
+ require 'devstructure'
2
+ require 'devstructure/blueprint'
3
+
4
+ require 'json'
5
+ require 'net/http'
6
+ require 'net/https'
7
+ require 'uri'
8
+
9
+ class DevStructure::API < Net::HTTP
10
+
11
+ # Undo Net::HTTP's allocator hijacking.
12
+ class << self
13
+ alias new newobj
14
+ end
15
+
16
+ def initialize(token=nil)
17
+ uri = URI.parse("https://devstructure.com")
18
+ super uri.host, uri.port
19
+ use_ssl = "https" == uri.scheme
20
+ @token = if token
21
+ token
22
+ else
23
+ "#{
24
+ File.read("/etc/token").chomp
25
+ }#{
26
+ File.read(File.expand_path("~/.token")).chomp
27
+ }"
28
+ end
29
+ end
30
+
31
+ def path(*args)
32
+ args.reject! { |arg| arg.nil? }
33
+ "/#{args.join("/")}.json"
34
+ end
35
+
36
+ def headers
37
+ {
38
+ "Authorization" => "Token token=\"#{@token}\"",
39
+ "Content-Type" => "application/json",
40
+ "Host" => "devstructure.com",
41
+ }
42
+ end
43
+
44
+ class Blueprints
45
+ include DevStructure
46
+
47
+ def initialize(api, username=nil)
48
+ @api = api
49
+ @username = username
50
+ end
51
+
52
+ def method_missing(symbol, *args)
53
+ if @username
54
+ get symbol.to_s
55
+ else
56
+ @username = symbol.to_s
57
+ self
58
+ end
59
+ end
60
+
61
+ def list
62
+ @api.start unless @api.started?
63
+ response, body = @api.get(@api.path("blueprints", @username),
64
+ @api.headers)
65
+ return response unless Net::HTTPOK == response.class
66
+ JSON.parse(body).collect { |hash| Blueprint.new(hash["name"], hash) }
67
+ end
68
+
69
+ def get(name, token=nil)
70
+ @api.start unless @api.started?
71
+ response, body = @api.get(@api.path("blueprints", @username,
72
+ name, token), @api.headers)
73
+ return response unless Net::HTTPOK == response.class
74
+ hash = JSON.parse(body)
75
+ Blueprint.new(hash["name"], hash)
76
+ end
77
+
78
+ def post(name, options={})
79
+ @api.start unless @api.started?
80
+ response, body = @api.post(@api.path("blueprints", @username, name),
81
+ JSON.generate(options), @api.headers)
82
+ response
83
+ end
84
+
85
+ def delete(name, options={})
86
+ @api.start unless @api.started?
87
+ response, body = @api.delete(@api.path("blueprints", @username, name),
88
+ @api.headers)
89
+ response
90
+ end
91
+
92
+ end
93
+
94
+ def blueprints(username=nil)
95
+ Blueprints.new(self, username)
96
+ end
97
+
98
+ end
@@ -0,0 +1,266 @@
1
+ require 'devstructure'
2
+ require 'devstructure/api'
3
+ require 'devstructure/puppet'
4
+
5
+ require 'base64'
6
+
7
+ class DevStructure::Blueprint < Hash
8
+ include DevStructure
9
+ include DevStructure::Puppet
10
+
11
+ def initialize(name, hash)
12
+ @name = name
13
+ self["name"] = name
14
+ super nil
15
+ clear
16
+ hash.each { |k, v| self[k.to_s] = v }
17
+ end
18
+
19
+ attr_accessor :name
20
+
21
+ def save(ds=nil)
22
+ ds ||= API.new
23
+ ds.blueprints(owner).post(name, self)
24
+ end
25
+
26
+ private :[]=
27
+
28
+ def method_missing(symbol, *args)
29
+ self[symbol.to_s]
30
+ end
31
+
32
+ def files
33
+ self["blueprint"]["_files"]
34
+ end
35
+
36
+ def packages
37
+ self["blueprint"]["_packages"]
38
+ end
39
+
40
+ def -(other)
41
+ b = self.dup
42
+ other.walk(
43
+ :package => lambda { |manager, command, package, version|
44
+ unless b.packages[package]
45
+ b.packages[manager]["_packages"].delete package
46
+ end
47
+ }
48
+ )
49
+ b
50
+ end
51
+
52
+ def self.disclaimer
53
+ puts <<EOF
54
+ # This file was automatically generated by ruby-devstructure(7).
55
+ EOF
56
+ end
57
+
58
+ def sh
59
+ self.class.disclaimer
60
+
61
+ # Packages.
62
+ walk(
63
+ :package => lambda { |manager, command, package, version|
64
+ return if "rubygems-update" == manager && manager == package
65
+ printf "#{command}\n", package, version
66
+ if "rubygems-update" == package && command =~ /gem(\d+\.\d+) install/
67
+ puts "PATH=\"$PATH:/var/lib/gems/#{$1}/bin\" update_rubygems"
68
+ end
69
+ }
70
+ )
71
+
72
+ # System files.
73
+ files.sort.each do |pathname, content|
74
+ if Hash == content.class
75
+ if content["_target"]
76
+ puts "ln -s #{content["_target"]} #{pathname}"
77
+ next
78
+ elsif content["_base64"]
79
+ content = content["_base64"]
80
+ command = "base64 --decode"
81
+ end
82
+ else
83
+ content.gsub!(/\\/, "\\\\\\\\")
84
+ content.gsub!(/\$/, "\\$")
85
+ command = "cat"
86
+ end
87
+ eof = "EOF"
88
+ eof << "EOF" while content =~ /^#{eof}$/
89
+ puts "#{command} >#{pathname} <<#{eof}"
90
+ puts content
91
+ puts eof
92
+ end
93
+
94
+ end
95
+
96
+ def puppet
97
+ self.class.disclaimer
98
+ puts "class #{@name} {"
99
+ puts Exec.defaults(:path => ENV["PATH"].split(":"))
100
+
101
+ # Packages.
102
+ walk(
103
+ :before => lambda { |manager, command|
104
+ p = packages[manager]["_packages"]
105
+ return if 0 == p.length || 1 == p.length && manager == p.keys.first
106
+ puts "\n\t# Packages that depend on #{manager}."
107
+ case manager
108
+ when /rubygems|rip/
109
+ puts "\tpackage {"
110
+ when /ruby|python/
111
+ puts "\texec {"
112
+ else
113
+ puts "\tpackage {"
114
+ end
115
+ },
116
+ :package => lambda { |manager, command, package, version|
117
+ command =~ /gem(\d+\.\d+) install/
118
+ case manager
119
+
120
+ # Ruby gems. We grab gems installed by Rip and put them on the
121
+ # real system.
122
+ # TODO If the user's using one of the custom opt-ruby-* packages,
123
+ # we should notice and wrap around into the system's equivalent
124
+ # package instead of blindly installing it off to the side.
125
+ when "rubygems-update"
126
+ unless manager == package
127
+ puts Package.partial(package,
128
+ :require => [Package["ruby#{$1}-dev"], Exec["update_rubygems"]],
129
+ :provider => :gem,
130
+ :ensure => version
131
+ )
132
+ end
133
+ when /rubygems|rip/
134
+ options = {
135
+ :require => Package["rubygems#{$1}", "ruby#{$1}-dev"],
136
+ :provider => :gem,
137
+ :ensure => version
138
+ }
139
+ options[:require] += [Package[manager]] unless manager == package
140
+ puts Package.partial(package, options)
141
+ when /ruby/
142
+ puts Exec.partial(sprintf("#{command}\n", package, version),
143
+ :require => Package[manager]
144
+ )
145
+ when /python/
146
+ puts Exec.partial(sprintf("#{command}\n", package),
147
+ :require => Package[manager, "python-setuptools"]
148
+ )
149
+
150
+ # TODO Expand Java, Erlang, etc.
151
+
152
+ # Debian packages get no special treatment.
153
+ else
154
+ if manager == package
155
+ puts Package.partial(package, :ensure => version)
156
+ else
157
+ puts Package.partial(package,
158
+ :require => Package[manager],
159
+ :ensure => version
160
+ )
161
+ end
162
+
163
+ end
164
+ },
165
+ :after => lambda { |manager, command|
166
+ p = packages[manager]["_packages"]
167
+ return if 0 == p.length || 1 == p.length && manager == p.keys.first
168
+ puts "\t}"
169
+
170
+ # Update RubyGems if we're through with gems in Debian's location.
171
+ return unless manager =~ /^rubygems\d+\.\d+$/
172
+ return unless p["rubygems-update"]
173
+ puts Exec.complete("update_rubygems",
174
+ :subscribe => Package["rubygems-update"],
175
+ :path => %w(/usr/bin /var/lib/gems/1.8/bin),
176
+ :refreshonly => true
177
+ )
178
+
179
+ }
180
+ )
181
+
182
+ # System files.
183
+ if 0 < files.length
184
+ puts "\n\t# System files depend on all packages."
185
+ puts Package.defaults(
186
+ :before => files.sort.map { |pathname, content|
187
+ Puppet::File[pathname]
188
+ }
189
+ )
190
+ puts "\tfile {"
191
+ files.sort.each do |pathname, content|
192
+ if Hash == content.class
193
+ if content["_target"]
194
+ puts Puppet::File.partial(pathname, :ensure => content["_target"])
195
+ next
196
+ elsif content["_base64"]
197
+ content = Base64.decode64(content["_base64"])
198
+ end
199
+ end
200
+ puts Puppet::File.partial(pathname,
201
+ :content => content,
202
+ :ensure => :present
203
+ )
204
+ end
205
+ puts "\t}"
206
+ end
207
+
208
+ puts "\n}"
209
+ end
210
+
211
+ def chef
212
+ puts "# No soup for you."
213
+ raise NotImplementedError, "Contractor::Blueprint#chef", caller
214
+ end
215
+
216
+ # Walk a package tree and execute callbacks along the way. Callbacks
217
+ # are listed in the options hash. These are supported:
218
+ # :before => lambda { |manager, command| }
219
+ # Executed before a manager's dependencies are enumerated.
220
+ # :package => lambda { |manager, command, package, version| }
221
+ # Executed when a package is enumerated.
222
+ # :after => lambda { |manager, command| }
223
+ # Executed after a manager's dependencies are enumerated.
224
+ def walk(options={})
225
+
226
+ # Start with packages installed with apt.
227
+ options[:manager] ||= "apt"
228
+
229
+ # Handle managers.
230
+ if options[:before].respond_to?(:call)
231
+ options[:before].call options[:manager],
232
+ packages[options[:manager]]["_command"]
233
+ end
234
+
235
+ # Callback for each package that depends on this manager. Note which
236
+ # ones themselves are managers.
237
+ managers = []
238
+ packages[options[:manager]]["_packages"].sort.each do |package, version|
239
+
240
+ # Handle packages.
241
+ if options[:package].respond_to?(:call)
242
+ options[:package].call options[:manager],
243
+ packages[options[:manager]]["_command"], package, version
244
+ end
245
+
246
+ if options[:manager] != package && packages[package]
247
+ managers << package
248
+ end
249
+ end
250
+
251
+ # Handle managers.
252
+ if options[:after].respond_to?(:call)
253
+ options[:after].call options[:manager],
254
+ packages[options[:manager]]["_command"]
255
+ end
256
+
257
+ # Recurse into each manager that was just installed. This is done
258
+ # after the completed round because there could have been other
259
+ # dependencies at that level aside from the manager.
260
+ managers.each do |manager|
261
+ walk options.merge(:manager => manager)
262
+ end
263
+
264
+ end
265
+
266
+ end
@@ -0,0 +1,143 @@
1
+ require 'devstructure'
2
+
3
+ # Make symbols Comparable.
4
+ class Symbol
5
+ def <=>(other)
6
+ to_s <=> other.to_s
7
+ end
8
+ end
9
+
10
+ module DevStructure::Puppet
11
+
12
+ # A generic Puppet resource to be subclassed. The name of the class
13
+ # dictates how the resource will be printed so do not instantiate this
14
+ # class directly.
15
+ class Resource
16
+
17
+ # A resource that will (by default) provide its complete representation.
18
+ def self.complete(name, options={})
19
+ self.new name, :complete, options
20
+ end
21
+
22
+ # A resource that will (by default) provide its partial representation.
23
+ def self.partial(name, options={})
24
+ self.new name, :partial, options
25
+ end
26
+
27
+ # A resource that will provide defaults for its resource type.
28
+ def self.defaults(options={})
29
+ self.new "", :defaults, options
30
+ end
31
+
32
+ # A resource with only a name, typically being listed as a dependency.
33
+ # This method enables syntax that looks a lot like the Puppet code that
34
+ # will be generated.
35
+ def self.[](*args)
36
+ if 1 == args.length
37
+ self.new args[0], :complete
38
+ else
39
+ args.collect { |arg| self[arg] }
40
+ end
41
+ end
42
+
43
+ # Return Puppet code for this resource. Whether a complete, partial,
44
+ # or defaults representation is returned depends on which class method
45
+ # instantiated this resource but can be overridden by passing a Symbol.
46
+ def to_s(style=nil)
47
+ style ||= @style
48
+ out = []
49
+
50
+ # Settle on the indentation level for the rest of the resource.
51
+ # Raise an exception if the style is invalid.
52
+ tab = case style
53
+ when :complete
54
+ out << "\t#{@type} { \"#{@name}\":"
55
+ "\t"
56
+ when :partial
57
+ out << "\t\t\"#{@name}\":"
58
+ "\t\t"
59
+ when :defaults
60
+ out << "\t#{@type.capitalize} {"
61
+ "\t"
62
+ else
63
+ raise ArgumentError
64
+ end
65
+
66
+ # Handle the options Hash.
67
+ if 0 < @options.length
68
+
69
+ # Note the longest option name so we can line up => operators as
70
+ # per the Puppet coding standards.
71
+ l = @options.collect { |k, v| k.to_s.length }.max
72
+
73
+ # Map options to Puppet code strings. Symbols become unquoted
74
+ # strings, nils become undefs, and Arrays are flattened. Unless
75
+ # the value is a Symbol, #inspect is called on the value.
76
+ out += @options.sort.collect do |k, v|
77
+ k = "#{k.to_s}#{" " * (l - k.to_s.length)}"
78
+ v = if v.respond_to?(:flatten)
79
+ if 1 == v.length
80
+ v[0]
81
+ else
82
+ v.flatten
83
+ end
84
+ else
85
+ v
86
+ end
87
+ v = :undef if v.nil?
88
+ v = v.inspect.gsub(/\$/, "\\$") unless Symbol == v.class
89
+ "\t#{tab}#{k} => #{v},"
90
+ end
91
+
92
+ # Close this resource.
93
+ case style
94
+ when :complete
95
+ out << "\t}"
96
+ when :partial
97
+ out << "#{out.pop.chop};"
98
+ when :defaults
99
+ out << "\t}"
100
+ end
101
+
102
+ # Don't bother with an empty options Hash. Go ahead and close this
103
+ # resource.
104
+ else
105
+ case style
106
+ when :complete
107
+ out << "#{out.pop} }"
108
+ when :partial
109
+ out << "#{out.pop};"
110
+ when :defaults
111
+ out << "#{out.pop}}"
112
+ end
113
+
114
+ end
115
+ out.join "\n"
116
+ end
117
+
118
+ # Resources themselves appear in options as dependencies so they are
119
+ # represented in Puppet's reference notation.
120
+ def inspect
121
+ "#{@type.capitalize}[\"#{@name}\"]"
122
+ end
123
+
124
+ # A resource with a name and default representation.
125
+ def initialize(name, style, options={})
126
+ @type = self.class.to_s.downcase[/[^:]+$/]
127
+ @name = name.to_s.downcase
128
+ @style = style.to_sym
129
+ @options = options
130
+ end
131
+ private :initialize
132
+
133
+ end
134
+
135
+ # Some actual resource types.
136
+ class Package < Resource
137
+ end
138
+ class Exec < Resource
139
+ end
140
+ class File < Resource
141
+ end
142
+
143
+ end
@@ -0,0 +1,2 @@
1
+ module DevStructure
2
+ end
Binary file
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devstructure
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Richard Crowley
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-07 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Ruby bindings to the DevStructure API
23
+ email: richard@devstructure.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/devstructure.rb
32
+ - lib/devstructure/api.rb
33
+ - lib/devstructure/blueprint.rb
34
+ - lib/devstructure/puppet.rb
35
+ - man/man7/ruby-devstructure.7.gz
36
+ has_rdoc: true
37
+ homepage: http://github.com/devstructure/ruby-devstructure
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ hash: 3
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.7
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Ruby bindings to the DevStructure API
70
+ test_files: []
71
+