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.
- data/lib/devstructure/api.rb +98 -0
- data/lib/devstructure/blueprint.rb +266 -0
- data/lib/devstructure/puppet.rb +143 -0
- data/lib/devstructure.rb +2 -0
- data/man/man7/ruby-devstructure.7.gz +0 -0
- metadata +71 -0
@@ -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
|
data/lib/devstructure.rb
ADDED
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
|
+
|