mason 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []