devstructure 0.0.2

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.
@@ -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
+