mason 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/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Mason
2
+
3
+ Build things
4
+
5
+ ## Instructions
6
+
7
+ ### Install buildpacks locally
8
+
9
+ $ mason buildpacks
10
+ * buildpacks (~/.mason/buildpacks)
11
+ = foo: https://github.com/ddollar/buildpack-foo.git
12
+ = bar: https://github.com/ddollar/buildpack-bar.git
13
+
14
+ $ mason buildpacks:install https://github.com/ddollar/buildpack-baz.git
15
+ * adding buildpack https://github.com/ddollar/buildpack-baz.git
16
+
17
+ ### Use buildpacks to build things
18
+
19
+ $ mason build /tmp/app
20
+ * detecting buildpack... done
21
+ = name: Baz
22
+ = url: https://github.com/ddollar/buildpack-baz.git
23
+ * compiling:
24
+ ... COMPILE OUTPUT
25
+ * packaging... done
26
+ = type: squashfs
27
+ = file: /tmp/app.img
28
+
29
+ $ mason build /tmp/app -t dir -o /tmp/compiledapp
30
+ * detecting buildpack... done
31
+ = name: Baz
32
+ = url: https://github.com/ddollar/buildpack-baz.git
33
+ * compiling...
34
+ ... COMPILE OUTPUT
35
+ * packaging... done
36
+ = type: dir
37
+ = dir: /tmp/compiledapp
38
+
39
+ $ mason build /tmp/app -b https://github.com/ddollar/buildpack-other.git -t tgz
40
+ * detecting buildpack... done
41
+ = name: Other
42
+ = url: https://github.com/ddollar/buildpack-other.git
43
+ * compiling...
44
+ ... COMPILE OUTPUT
45
+ * packaging... done
46
+ = type: tgz
47
+ = file: /tmp/app.tgz
48
+
49
+ ### Use vagrant to build things for other platforms
50
+
51
+ $ mason build /tmp/app -s cedar
52
+ * booting vm for cedar
53
+ ...
data/bin/mason ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require "mason/cli"
6
+
7
+ begin
8
+ Mason::CLI.run
9
+ rescue Errno::EPIPE
10
+ rescue Mason::CommandFailed => ex
11
+ $stderr.puts " ! #{ex}"
12
+ exit 1
13
+ end
@@ -0,0 +1,5 @@
1
+ Vagrant::Config.run do |config|
2
+ config.vagrant.dotfile_name = File.expand_path("~/.mason/vagrant")
3
+
4
+ BOXES
5
+ end
data/lib/mason.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Mason
2
+
3
+ class CommandFailed < StandardError; end
4
+
5
+ end
@@ -0,0 +1,64 @@
1
+ require "mason"
2
+ require "tmpdir"
3
+
4
+ class Mason::Buildpack
5
+
6
+ attr_reader :dir, :name, :url
7
+
8
+ def initialize(dir)
9
+ @dir = dir
10
+ Dir.chdir(@dir) do
11
+ @name = File.basename(@dir)
12
+ @url = %x{ git config remote.origin.url }.chomp
13
+ end
14
+ end
15
+
16
+ def <=>(other)
17
+ self.name <=> other.name
18
+ end
19
+
20
+ def detect(app)
21
+ mkchtmpdir do
22
+ output = %x{ #{script("detect")} "#{app}" }
23
+ $?.exitstatus.zero? ? output.chomp : nil
24
+ end
25
+ end
26
+
27
+ def compile(app)
28
+ mkchtmpdir do |cache_dir|
29
+ compile_dir = Dir.mktmpdir
30
+ FileUtils.rm_rf compile_dir
31
+ FileUtils.cp_r app, compile_dir
32
+ Dir.chdir(compile_dir) do
33
+ IO.popen(%{ #{script("compile")} "#{compile_dir}" "#{cache_dir}" }) do |io|
34
+ until io.eof?
35
+ data = io.gets
36
+ data.gsub!(/^-----> /, " + ")
37
+ data.gsub!(/^ /, " ")
38
+ data.gsub!(/^\s+$/, "")
39
+ print data
40
+ end
41
+ end
42
+ end
43
+ compile_dir
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def mkchtmpdir
50
+ ret = nil
51
+ Dir.mktmpdir do |dir|
52
+ Dir.chdir(dir) do
53
+ ret = yield(dir)
54
+ end
55
+ end
56
+ ret
57
+ end
58
+
59
+ def script(name)
60
+ File.join(dir, "bin", name)
61
+ end
62
+
63
+ end
64
+
@@ -0,0 +1,51 @@
1
+ require "fileutils"
2
+ require "mason"
3
+ require "mason/buildpack"
4
+ require "uri"
5
+
6
+ class Mason::Buildpacks
7
+
8
+ def self.install(url)
9
+ FileUtils.mkdir_p root
10
+
11
+ Dir.chdir(root) do
12
+ if URI.parse(url).path =~ /buildpack-(\w+)/
13
+ name = $1
14
+ raise "#{name} buildpack already installed" if File.exists?(name)
15
+ system "git clone #{url} #{name} >/dev/null 2>&1"
16
+ raise "failed to clone buildpack" unless $?.exitstatus.zero?
17
+ else
18
+ raise "BUILDPACK should be a url containing buildpack-NAME.git"
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.uninstall(name)
24
+ Dir.chdir(root) do
25
+ raise "#{name} buildpack is not installed" unless File.exists?(name)
26
+ FileUtils.rm_rf name
27
+ end
28
+ end
29
+
30
+ def self.root(expand=true)
31
+ dir = "~/.mason/buildpacks"
32
+ expand ? File.expand_path(dir) : dir
33
+ end
34
+
35
+ def self.buildpacks
36
+ @buildpacks ||= begin
37
+ Dir[File.join(root, "*")].map do |buildpack_dir|
38
+ Mason::Buildpack.new(buildpack_dir)
39
+ end
40
+ end
41
+ end
42
+
43
+ def self.detect(app)
44
+ buildpacks.each do |buildpack|
45
+ ret = buildpack.detect(app)
46
+ return [buildpack, ret] if ret
47
+ end
48
+ nil
49
+ end
50
+
51
+ end
data/lib/mason/cli.rb ADDED
@@ -0,0 +1,256 @@
1
+ require "mason"
2
+ require "mason/buildpacks"
3
+ require "mason/stacks"
4
+ require "mason/version"
5
+ require "thor"
6
+ require "thor/shell/basic"
7
+
8
+ class Mason::CLI < Thor
9
+
10
+ class_option :help, :type => :boolean, :aliases => "-h", :desc => "help for this command"
11
+
12
+ map %w( -v -V --version ) => :version
13
+
14
+ desc "version", "display version"
15
+
16
+ def version
17
+ puts "mason v#{Mason::VERSION}"
18
+ end
19
+
20
+ desc "build APP", "build an app"
21
+
22
+ method_option :buildpack, :type => :string, :aliases => "-b", :desc => "use a custom buildpack"
23
+ method_option :output, :type => :string, :aliases => "-o", :desc => "output location"
24
+ method_option :quiet, :type => :boolean, :aliases => "-q", :desc => "quiet packaging output"
25
+ method_option :stack, :type => :string, :aliases => "-s", :desc => "use a stack for building"
26
+ method_option :type, :type => :string, :aliases => "-t", :desc => "output type (dir, img, tgz)"
27
+
28
+ def build(app)
29
+ raise "no such directory: #{app}" unless File.exists?(app)
30
+
31
+ type = options[:type]
32
+ output = options[:output]
33
+
34
+ type = File.extname(output)[1..-1] if !type && output
35
+ output = "#{app}.#{type}" if !output && type
36
+ type ||= "dir"
37
+
38
+ raise "no such output format: #{type}" unless %w( dir img tgz ).include?(type)
39
+
40
+ if stack = options[:stack]
41
+ print "* booting stack #{stack} (this may take a while)... "
42
+ Mason::Stacks.up(stack)
43
+ puts "done"
44
+
45
+ buildpacks_dir = File.expand_path("~/.mason/share/#{stack}/buildpacks")
46
+ compile_dir = File.expand_path("~/.mason/share/#{stack}/app")
47
+ mason_dir = File.expand_path("~/.mason/share/#{stack}/mason")
48
+
49
+ FileUtils.rm_rf buildpacks_dir
50
+ FileUtils.rm_rf compile_dir
51
+ FileUtils.rm_rf mason_dir
52
+
53
+ FileUtils.cp_r File.expand_path("~/.mason/buildpacks"), buildpacks_dir
54
+ FileUtils.cp_r app, compile_dir
55
+ FileUtils.cp_r File.expand_path("../../../", __FILE__), mason_dir
56
+
57
+ mason_args = %{ /share/app -q -o /share/output -t #{type} }
58
+ mason_args += %{ -b "#{options[:buildpack]}" } if options[:buildpack]
59
+
60
+ Mason::Stacks.run(stack, <<-COMMAND)
61
+ gem spec thor 2>&1 >/dev/null || sudo gem install thor
62
+ /usr/bin/env ruby -rubygems /share/mason/bin/mason build #{mason_args}
63
+ COMMAND
64
+
65
+ FileUtils.cp_r File.expand_path("~/.mason/share/#{stack}/output"), output
66
+
67
+ puts "* packaging"
68
+ puts " = type: #{type}"
69
+ puts " = location: #{output}"
70
+ else
71
+ print "* detecting buildpack... "
72
+
73
+ buildpack, ret = Mason::Buildpacks.detect(app)
74
+ raise "no valid buildpack detected" unless buildpack
75
+
76
+ puts "done"
77
+ puts " = name: #{buildpack.name}"
78
+ puts " = url: #{buildpack.url}"
79
+ puts " = display: #{ret}"
80
+
81
+ puts "* compiling..."
82
+ compile_dir = buildpack.compile(app)
83
+
84
+ print "* packaging... " unless options[:quiet]
85
+ case type.to_sym
86
+ when :tgz then
87
+ Dir.chdir(compile_dir) do
88
+ system %{ tar czf "#{output}" . }
89
+ end
90
+ when :img then
91
+ puts "not yet"
92
+ when :dir then
93
+ FileUtils.rm_rf output
94
+ FileUtils.cp_r compile_dir, output
95
+ else
96
+ raise "no such output type: #{type}"
97
+ end
98
+
99
+ unless options[:quiet]
100
+ puts "done"
101
+ puts " = type: #{type}"
102
+ puts " = location: #{output}"
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ desc "vagrant COMMAND", "run a vagrant command in the mason environment"
109
+
110
+ def vagrant(*args)
111
+ Mason::Stacks.vagrant(args)
112
+ end
113
+
114
+ desc "buildpacks", "list installed buildpacks"
115
+
116
+ def buildpacks
117
+ buildpacks = Mason::Buildpacks.buildpacks
118
+
119
+ puts "* buildpacks (#{Mason::Buildpacks.root(false)})"
120
+ buildpacks.sort.each do |buildpack|
121
+ puts " = #{buildpack.name}: #{buildpack.url}"
122
+ end
123
+
124
+ puts " - no buildpacks installed, use buildpacks:add" if buildpacks.length.zero?
125
+ end
126
+
127
+ class Buildpacks < Thor
128
+
129
+ desc "buildpacks:install URL", "install a buildpack"
130
+
131
+ def install(url)
132
+ puts "* adding buildpack #{url}"
133
+ Mason::Buildpacks.install url
134
+ end
135
+
136
+ desc "buildpacks:uninstall NAME", "uninstall a buildpack"
137
+
138
+ def uninstall(name)
139
+ puts "* removing buildpack #{name}"
140
+ Mason::Buildpacks.uninstall name
141
+ end
142
+
143
+ end
144
+
145
+ desc "stacks", "list available stacks"
146
+
147
+ def stacks
148
+ puts "* available stacks"
149
+ Mason::Stacks.vms.each do |name, vm|
150
+ next if name == :default
151
+ puts " - #{name} [#{Mason::Stacks.state(name)}]"
152
+ end
153
+ end
154
+
155
+ class Stacks < Thor
156
+
157
+ # Hackery. Take the run method away from Thor so that we can redefine it.
158
+ class << self
159
+ def is_thor_reserved_word?(word, type)
160
+ return false if word == 'run'
161
+ super
162
+ end
163
+ end
164
+
165
+ desc "stacks:create name", "create a new stack"
166
+
167
+ method_option :box, :type => :string, :aliases => "-b", :desc => "vagrant box name"
168
+
169
+ def create(name)
170
+ box = options[:box] || name
171
+ print "* creating stack #{name}... "
172
+ Mason::Stacks.create(name, box)
173
+ puts "done"
174
+ end
175
+
176
+ desc "stacks:destroy STACK", "destroy a stack"
177
+
178
+ def destroy(name)
179
+ print "* destroying stack #{name}... "
180
+ Mason::Stacks.destroy(name)
181
+ puts "done"
182
+ end
183
+
184
+ desc "stacks:up STACK", "boot a stack"
185
+
186
+ def up(name)
187
+ print "* booting stack #{name} (this will take a while)..."
188
+ Mason::Stacks.up(name)
189
+ puts "done"
190
+ end
191
+
192
+ desc "stacks:down STACK", "boot a stack"
193
+
194
+ def down(name)
195
+ print "* stopping stack #{name}..."
196
+ Mason::Stacks.down(name)
197
+ puts "done"
198
+ end
199
+
200
+ desc "stacks:run STACK COMMAND", "run a command on a stack"
201
+
202
+ def run(name, *args)
203
+ Mason::Stacks.run(name, args.join(" "))
204
+ end
205
+
206
+ end
207
+
208
+ # hack thor
209
+ def self.run
210
+ args = ARGV.dup
211
+ parts = args.first.to_s.split(":")
212
+ method = parts.pop
213
+ ns = parts.pop
214
+
215
+ args[0] = method
216
+
217
+ klass = case ns
218
+ when "buildpacks" then Buildpacks
219
+ when "stacks" then Stacks
220
+ else self
221
+ end
222
+
223
+ unless (args & %w( -h --help )).empty?
224
+ klass.task_help(Thor::Shell::Basic.new, args.first)
225
+ return
226
+ end
227
+
228
+ klass.start(args)
229
+ # rescue StandardError => ex
230
+ # raise Mason::CommandFailed, ex.message
231
+ end
232
+
233
+ private
234
+
235
+ def vagrantfile
236
+ FileUtils.mkdir_p File.expand_path("~/.mason")
237
+ file = File.expand_path("~/.mason/Vagrantfile")
238
+ build_vagrantfile unless File.exists?(file)
239
+ file
240
+ end
241
+
242
+ def build_vagrantfile(boxes={})
243
+ data = File.read(File.expand_path("../../../data/Vagrantfile.template", __FILE__))
244
+ data.gsub! "BOXES", (boxes.map do |name, box|
245
+ <<-BOX
246
+ config.vm.define :#{name} do |config|
247
+ config.vm.box = "#{box}"
248
+ end
249
+ BOX
250
+ end.join("\n"))
251
+ File.open(File.expand_path("~/.mason/Vagrantfile"), "w") do |file|
252
+ file.puts data
253
+ end
254
+ end
255
+
256
+ end
@@ -0,0 +1,110 @@
1
+ require "fileutils"
2
+ require "mason"
3
+
4
+ class Mason::Stacks
5
+
6
+ def self.load_vagrant!
7
+ require "vagrant"
8
+ build_vagrantfile unless File.exists?(vagrantfile)
9
+ end
10
+
11
+ def self.vagrant_env(display=false)
12
+ ui = display ? Vagrant::UI::Basic : nil
13
+ Vagrant::Environment.new(:vagrantfile_name => vagrantfile, :ui_class => ui)
14
+ end
15
+
16
+ def self.vms
17
+ load_vagrant!
18
+ vagrant_env.vms
19
+ end
20
+
21
+ def self.vagrant(args)
22
+ vagrant_env(true).cli(args)
23
+ end
24
+
25
+ def self.stacks
26
+ @stacks ||= begin
27
+ vms.inject({}) do |hash, (name, vm)|
28
+ next(hash) if name == :default
29
+ hash.update(name => vm.box ? vm.box.name : "")
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.create(name, box)
35
+ raise "stack already exists: #{name}" if stacks.keys.include?(name.to_sym)
36
+ raise "vagrant box does not exist: #{box}" unless vagrant_env.boxes.map(&:name).include?(box)
37
+ build_vagrantfile(stacks.update(name => box))
38
+ end
39
+
40
+ def self.destroy(name)
41
+ raise "no such stack: #{name}" unless stacks.keys.include?(name.to_sym)
42
+ vm = vms[name.to_sym]
43
+ vm.halt rescue Vagrant::Errors::VBoxManagerError
44
+ vm.destroy rescue Vagrant::Errors::VBoxManageError
45
+ s = stacks
46
+ s.delete(name.to_sym)
47
+ build_vagrantfile(s)
48
+ end
49
+
50
+ def self.state(name)
51
+ raise "no such stack: #{name}" unless stacks.keys.include?(name.to_sym)
52
+ case vms[name.to_sym].state.to_sym
53
+ when :running then :up
54
+ else :down
55
+ end
56
+ end
57
+
58
+ def self.up(name)
59
+ raise "no such stack: #{name}" unless stacks.keys.include?(name.to_sym)
60
+ return if state(name) == :up
61
+ vms[name.to_sym].up
62
+ end
63
+
64
+ def self.down(name)
65
+ raise "no such stack: #{name}" unless stacks.keys.include?(name.to_sym)
66
+ return if state(name) == :down
67
+ vms[name.to_sym].suspend
68
+ end
69
+
70
+ def self.run(name, command)
71
+ raise "no suck stack: #{name}" unless stacks.keys.include?(name.to_sym)
72
+ vms[name.to_sym].channel.execute(command, :error_check => false) do |type, data|
73
+ print data
74
+ end
75
+ end
76
+
77
+ def self.vagrantfile
78
+ File.expand_path("~/.mason/Vagrantfile")
79
+ end
80
+
81
+ def self.vagrantfile_template
82
+ File.expand_path("../../../data/Vagrantfile.template", __FILE__)
83
+ end
84
+
85
+ def self.share_dir(name)
86
+ dir = File.expand_path("~/.mason/share/#{name}")
87
+ FileUtils.mkdir_p dir unless File.exists?(dir)
88
+ dir
89
+ end
90
+
91
+ def self.build_vagrantfile(stacks={})
92
+ data = File.read(vagrantfile_template)
93
+ ip_base = 3
94
+ data.gsub! "BOXES", (stacks.map do |name, box|
95
+ ip_base += 1
96
+ <<-BOX
97
+ config.vm.define :#{name} do |config|
98
+ config.vm.box = "#{box}"
99
+ config.vm.base_mac = "080027706AA#{ip_base}"
100
+ config.vm.network :hostonly, "33.33.33.#{ip_base}"
101
+ config.vm.share_folder "share", "/share", "#{share_dir(name)}"
102
+ end
103
+ BOX
104
+ end.join("\n").chomp)
105
+ File.open(vagrantfile, "w") do |file|
106
+ file.puts data
107
+ end
108
+ end
109
+
110
+ end
@@ -0,0 +1,3 @@
1
+ module Mason
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mason
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Dollar
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: &70148573564580 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70148573564580
25
+ description: Build things
26
+ email: ddollar@gmail.com
27
+ executables:
28
+ - mason
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - bin/mason
33
+ - data/Vagrantfile.template
34
+ - lib/mason/buildpack.rb
35
+ - lib/mason/buildpacks.rb
36
+ - lib/mason/cli.rb
37
+ - lib/mason/stacks.rb
38
+ - lib/mason/version.rb
39
+ - lib/mason.rb
40
+ - README.md
41
+ homepage: http://github.com/ddollar/mason
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.11
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Build things
65
+ test_files: []