beeta 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.
@@ -0,0 +1,147 @@
1
+ require 'rake/gempackagetask'
2
+ require 'rake/rdoctask'
3
+ require './lib/beeta/config'
4
+
5
+ begin
6
+ require 'bundler'
7
+ Bundler::GemHelper.install_tasks
8
+ rescue LoadError => e
9
+ $stderr.puts(' Bundler is not installed. Its rake tasks won\'t work. '.
10
+ center((ENV['COLUMNS'] || 80).to_i, '*'))
11
+ end
12
+
13
+ $: << "#{File.dirname(__FILE__)}/lib"
14
+
15
+ Rake::RDocTask.new(:doc) { |t|
16
+ t.main = 'doc/README'
17
+ t.rdoc_files.include 'lib/**/*.rb', 'doc/*', 'bin/*', 'ext/**/*.c',
18
+ 'ext/**/*.rb'
19
+ t.options << '-S' << '-N'
20
+ t.rdoc_dir = 'doc/rdoc'
21
+ }
22
+
23
+ desc "Runs IRB, automatically require()ing beeta."
24
+ task(:irb) {
25
+ exec "irb -Ilib -rbeeta"
26
+ }
27
+
28
+ desc "Runs tests."
29
+ task(:test) {
30
+ system "ruby -Ilib -Itest test/*_test.rb"
31
+ }
32
+
33
+ task :default => :test
34
+
35
+ task(:gitolite_setup) {
36
+ require 'fileutils'
37
+ Beeta::Config.load
38
+ dir = Beeta::Config.gitolite_repo_path
39
+ location = "git@#{Beeta::Config.gitolite_host}:gitolite-admin"
40
+ FileUtils.mkdir_p File.dirname(dir)
41
+ if File.exists? dir
42
+ system "git pull #{dir}"
43
+ else
44
+ system "git clone #{location} #{dir}"
45
+ end
46
+ }
47
+
48
+ desc "Update the database with the latest schema."
49
+ task(:migrate) {
50
+ require 'sequel'
51
+ require 'sequel/extensions/migration'
52
+ Beeta::Config.load
53
+ Sequel.connect(Beeta::Config.db) { |db|
54
+ Sequel::Migrator.apply(db, './migrations')
55
+ }
56
+ }
57
+
58
+ desc "Run beeta with unicorn"
59
+ task(:unicorn) {
60
+ sh "unicorn -c conf/unicorn.rb"
61
+ }
62
+
63
+ desc "Run beeta with unicorn in daemon mode"
64
+ task(:daemon) {
65
+ sh "unicorn -D -c conf/unicorn.rb"
66
+ }
67
+
68
+ task(:install_gitolite) {
69
+ if File.exists? File.expand_path "~git/bin/gl-setup"
70
+ puts "Gitolite appears to be installed"
71
+ else
72
+ puts "Installing Gitolite"
73
+ tmpidrsa = "/tmp/#{ENV['USER']}-#{rand(10000)}.pub"
74
+ sh "cp ~/.ssh/id_rsa.pub #{tmpidrsa} && chmod 644 #{tmpidrsa}"
75
+ install_script = File.expand_path(File.join(File.dirname(__FILE__), 'bin/install_gitolite.sh'))
76
+ sh "su git -c '#{install_script} #{tmpidrsa}'"
77
+ sh "rm #{tmpidrsa}"
78
+ end
79
+ }
80
+
81
+ task(:install_deps) {
82
+ sh 'gem install bundler'
83
+ sh 'bundle'
84
+ }
85
+
86
+ desc "Perform all initial setup functions."
87
+ task(:newb) {
88
+ # automatic gitolite installation isn't working
89
+ # #Rake::Task["install_gitolite"].invoke
90
+ %w(gitolite_setup install_gitolite_gem install_deps migrate test
91
+ ).each do |task|
92
+ Rake::Task[task].invoke
93
+ end
94
+
95
+ puts <<MSG
96
+ ==============================
97
+ So, what have you done?
98
+ Configured gitolite, installed the gitolite gem, bundle installed gems,
99
+ run db migrations and executed tests.
100
+
101
+ What now?
102
+ 1. README
103
+ 2. Code stuff
104
+ 3. Submit back
105
+ 4. ???
106
+ 5. Profit!
107
+ MSG
108
+ }
109
+
110
+ desc "Perform project release check-in and tag with a version number. Each new release version will be automatically."
111
+ task(:release) {
112
+ puts 'Running release task, may take a few minutes'
113
+ new_version = increment_ver
114
+ run_commands(
115
+ "git add lib/version.rb" &&
116
+ "git commit -am 'Bump to Beeta version #{new_version}'" &&
117
+ "git tag v#{new_version} HEAD^" &&
118
+ "gem build beeta.gemspec")
119
+
120
+ puts '********************************************************************************'
121
+ puts
122
+ puts "Don't forget to `gem push` and `git push --tags`!"
123
+ puts
124
+ puts '********************************************************************************'
125
+ }
126
+
127
+ def increment_ver
128
+ puts "Reading the current Beeta version"
129
+ specs = Gem::SpecFetcher.fetcher.fetch(Gem::Dependency.new("beeta"))
130
+ versions = specs.map {|spec,| spec.version}.sort
131
+ new_version = versions.last.to_s
132
+ puts "New Version is going to be #{new_version}"
133
+ version_file =<<-EOT
134
+ module Beeta
135
+ VERSION = "#{new_version}"
136
+ end
137
+ EOT
138
+
139
+ puts "Using Beeta version #{new_version}"
140
+ File.open('lib/version.rb', 'w') do |f|
141
+ f.write version_file
142
+ end
143
+ new_version
144
+ end
145
+
146
+
147
+
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env sh
2
+
3
+ # This script will install gitolite under the current user's account. Normally it is expected that you would install gitolite
4
+ # to a 'git' user. Therefore, you should probably copy your public key into /tmp and su/sudo into the git account and run the
5
+ # script from there.
6
+ #
7
+ # For more information see: http://sitaramc.github.com/gitolite/doc/1-INSTALL.html
8
+
9
+ GITOLITE_DIR=/tmp/gitolite-source
10
+ TMPKEY=$1
11
+ VER=v1.5.9.1
12
+ KEY_ERROR_MSG='Please provide a public key to import into gitolite.'
13
+
14
+ if [ -z $TMPKEY ]; then
15
+ echo $KEY_ERROR_MSG
16
+ exit
17
+ fi
18
+
19
+ if [ ! -f $TMPKEY ]; then
20
+ echo $TMPKEY 'does not exist. ' $KEY_ERROR_MSG
21
+ exit
22
+ fi
23
+
24
+ if [ ! -d $GITOLITE_DIR ]; then
25
+ git clone git://github.com/sitaramc/gitolite $GITOLITE_DIR
26
+ fi
27
+
28
+ cd $GITOLITE_DIR && git checkout $VER
29
+ mkdir -p $HOME/bin $HOME/share/gitolite/conf $HOME/share/gitolite/hooks
30
+ $GITOLITE_DIR/src/gl-system-install $HOME/bin $HOME/share/gitolite/conf $HOME/share/gitolite/hooks
31
+ $HOME/bin/gl-setup $TMPKEY
32
+
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 AT&T
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ DEALINGS IN THE SOFTWARE.
File without changes
@@ -0,0 +1,15 @@
1
+ " For as long as I can get away with it, here are the code formatting standards
2
+ " for the codebase. I don't know how to do this in Emacs or TextMate, but feel
3
+ " free to add equivalent files.
4
+
5
+ " Tabs and shifts rendered as 4 spaces; change it if you want!
6
+ set ts=4
7
+ set sw=4
8
+
9
+ " Hard tabs for indentation, soft tabs for alignment if that is needed for some
10
+ " reason:
11
+ set noexpandtab
12
+ set sts=0
13
+
14
+ " Wrap at 80 characters:
15
+ set tw=80
@@ -0,0 +1,41 @@
1
+ %w(
2
+ watts
3
+ json
4
+ sequel
5
+ gitolite
6
+ ).each &method(:require)
7
+
8
+ module Beeta
9
+ def self.init
10
+ # First off, we need to load the configuration file:
11
+ Beeta::Config.load
12
+
13
+ # Before the models are loaded, we need to connect to the DB:
14
+ Sequel::Model.db = Sequel.connect Beeta::Config.db
15
+
16
+ const_set(:GitoliteRepo,
17
+ Gitolite::GitoliteAdmin.new(Beeta::Config.gitolite_repo_path))
18
+ end
19
+ end
20
+
21
+ require 'beeta/config'
22
+ Beeta.init
23
+
24
+ %w(
25
+ beeta/resource
26
+ beeta/model
27
+ ).each &method(:require)
28
+
29
+ class Beeta::App < Watts::App
30
+ include Beeta::Resource
31
+
32
+ resource('/', Discovery) {
33
+ resource("app", AppList) {
34
+ resource(/^[-0-9a-z]+$/i, App)
35
+ }
36
+
37
+ resource("user", UserList) {
38
+ resource(/^[0-9a-z]+$/i, User)
39
+ }
40
+ }
41
+ end
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+
3
+ module Beeta
4
+ module Config
5
+ ConfigFile = %W(
6
+ #{ENV['BEETA_CONF']}
7
+ ./beeta.json
8
+ #{ENV['HOME']}/.beeta.json
9
+ /etc/beeta.json
10
+ ).select { |fn| File.exist? fn }.first
11
+
12
+ class << self; attr_accessor :config, :loaded; end
13
+
14
+ def self.load(fn = nil, force = false)
15
+ fn ||= ConfigFile
16
+ return false if(fn.nil? || (!force && loaded))
17
+
18
+ self.config = JSON.parse File.read(fn)
19
+ # TODO: When the config solidifies reasonably, get rid of the
20
+ # overly metaprogrammed bits here, and replace with regular
21
+ # methods. It's just a little easier for now to be able to add
22
+ # values to the config file and have the methods appear out of
23
+ # nowhere.
24
+ config.each { |k,v|
25
+ class << self; self; end.module_eval {
26
+ define_method(k) { v }
27
+ }
28
+ }
29
+
30
+ self.loaded = true
31
+ end
32
+ end
33
+ end
34
+
35
+ Beeta::Config.load
@@ -0,0 +1,98 @@
1
+ require 'sequel/model'
2
+ require 'digest/sha1'
3
+
4
+ module Beeta::Model
5
+ module Mixin
6
+ def to_json
7
+ h = {}
8
+ self.class::PublicProperties.each { |p| h[p] = send(p) }
9
+ h.to_json
10
+ end
11
+ end
12
+
13
+ class User < Sequel::Model
14
+ include Mixin
15
+ set_dataset :user
16
+
17
+ PublicProperties = [
18
+ :email,
19
+ :created,
20
+ :app_uris,
21
+ ]
22
+
23
+ def self.random_salt
24
+ rand.to_s[2..-1]
25
+ end
26
+
27
+ def self.hash_password salt, pw
28
+ Digest::SHA1.hexdigest "#{salt}#{pw}"
29
+ end
30
+
31
+ def self.authenticate email, pw
32
+ u = self[:email => email]
33
+ return unless u
34
+ return u if hash_password(u.salt, pw) == u.hashed_password
35
+ end
36
+
37
+ def before_create
38
+ self.created = Time.now
39
+ super
40
+ end
41
+
42
+ def app_uris
43
+ []
44
+ end
45
+
46
+ def password=(pw)
47
+ self.salt = self.class.random_salt
48
+ self.hashed_password = self.class.hash_password salt, pw
49
+ pw
50
+ end
51
+ end
52
+
53
+ class App < Sequel::Model
54
+ include Mixin
55
+ set_dataset :app
56
+
57
+ many_to_one :owner, :class => User
58
+ one_to_many :instances
59
+
60
+ PublicProperties = [
61
+ :owner,
62
+ :name,
63
+ :max_instances,
64
+ :min_instances,
65
+ :git_repo,
66
+ ]
67
+
68
+ # The address of the git repository.
69
+ def git_repo
70
+ # TODO: Don't hard-code the git user? For the sake of simplicity,
71
+ # I think it's okay to say that the gitolite user has to be named
72
+ # 'git' for now.
73
+ "git@#{Beeta::Config.gitolite_host}:#{name}"
74
+ end
75
+
76
+ def validate
77
+ super
78
+ errors.add(:name, "can't be empty.") if(name.nil? || name.empty?)
79
+ if(((App[:name => name].id != id) rescue nil))
80
+ errors.add(:name, "must be unique.")
81
+ end
82
+
83
+ errors.add(:owner, "can't be blank.") unless owner
84
+
85
+ errors.empty?
86
+ end
87
+ end
88
+
89
+ # Currently unused placeholder. The idea is that we have N instances
90
+ # (running Unicorn/Rainbows/etc.) that can be routed to. We're going to
91
+ # punt on the routing for now, but this will need to be hooked in so that
92
+ # we can keep track of pid/host/port to pass back through the API.
93
+ class Instance < Sequel::Model
94
+ set_dataset :instance
95
+
96
+ many_to_one :app
97
+ end
98
+ end
@@ -0,0 +1,116 @@
1
+ require 'beeta/model'
2
+
3
+ # TODO: These are all pretty short, but will get bigger and should be split.
4
+ # While most resources are under 10 lines, though, this is a little easier.
5
+ module Beeta::Resource
6
+ include Beeta
7
+
8
+ # The Generic resource, with some utility methods for the other Beeta
9
+ # resources.
10
+ class Generic < Watts::Resource
11
+ include Beeta
12
+
13
+ def error_404
14
+ @error_404 ||= json_resp({'error'=>'Not Found'}, 404)
15
+ end
16
+
17
+ # Returns the body given with the request, as with a POST or PUT.
18
+ def req_body
19
+ @req_body ||= env['rack.input'].read
20
+ end
21
+
22
+ # Returns the req_body, run through the JSON parser. Returns nil if we
23
+ # can't parse it.
24
+ def json_body
25
+ @json_body ||=
26
+ begin
27
+ JSON.parse(req_body)
28
+ rescue JSON::ParserError
29
+ nil
30
+ end
31
+ end
32
+
33
+ def json_resp body, status = 200
34
+ js = JSON.unparse body
35
+ [status,
36
+ { 'Content-Type' => 'application/json',
37
+ 'Content-Length' => js.size.to_s,
38
+ },
39
+ [js]]
40
+ end
41
+
42
+ def json_error str, status = 400
43
+ json_resp({'error' => str}, status)
44
+ end
45
+ end
46
+
47
+ class Discovery < Generic
48
+ get { |*_|
49
+ json_resp 'apps' => '/app', 'users' => '/users'
50
+ }
51
+ end
52
+
53
+ # TODO: These are all pretty similar; maybe a CRUD and a CRUDList
54
+ # superclass, but that depends on if we call gitolite from the models (I'm
55
+ # leaning towards yes) or we manage that through an intermediary, and also
56
+ # on auth. We'll be using SSO ideally, although we may skip that for now.
57
+
58
+ class App < Generic
59
+ get { |name| json_resp Models::App[:name => name] }
60
+ put { |name|
61
+ # TODO: Authorization.
62
+ a = Models::App[:name => name]
63
+ return error_404 unless a
64
+ a.set json_body
65
+ return json_resp(a) if a.save
66
+ # TODO: Restart the app.
67
+ }
68
+ end
69
+
70
+ class AppList < Generic
71
+ get { json_resp Model::App.all }
72
+
73
+ # Not particularly married to POSTing to the app list, as a name must
74
+ # be supplied anyway, so just a PUT to /app/name might be more
75
+ # appropriate.
76
+ post {
77
+ # TODO: Authentication, tagging the App as owned by the user that
78
+ # is posting the data.
79
+ unless json_body
80
+ return json_error('No body provided, or could not parse body.')
81
+ end
82
+
83
+ a = Model::App.new json_body
84
+ if !a.valid?
85
+ # TODO: 409 when the name is taken.
86
+ return json_resp(a.errors, 400)
87
+ end
88
+
89
+ # TODO: Make this the model's responsibility(?)
90
+ repo = Gitolite::Config::Repo.new a.name
91
+ # TODO: After auth stuff, the below:
92
+ repo.add_permission('RW+C', '', `id -nu`.strip)
93
+ Beeta::GitoliteRepo.config.add_repo repo
94
+ Beeta::GitoliteRepo.save_and_apply
95
+
96
+ return json_resp(a) if a.save
97
+ # TODO: Provide git URI.
98
+ }
99
+ end
100
+
101
+ class UserList < Generic
102
+ get { |name| json_resp Models::User.all }
103
+ # TODO: Use SSO.
104
+ end
105
+
106
+ class User < Generic
107
+ get { |name| json_resp Models::User[:name => name] }
108
+ put { |name|
109
+ a = Models::App[:name => name]
110
+ return error_404 unless a
111
+ a.set json_body
112
+ return json_resp(a) if a.save
113
+ # TODO: Restart the app.
114
+ }
115
+ end
116
+ end
@@ -0,0 +1,3 @@
1
+ module Beeta
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: beeta
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Pete Elmore
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-11 00:00:00 -08:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: watts
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: json
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: sequel
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :runtime
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: sqlite3
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :runtime
71
+ version_requirements: *id004
72
+ - !ruby/object:Gem::Dependency
73
+ name: gitolite
74
+ prerelease: false
75
+ requirement: &id005 !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - "="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ - 0
83
+ - 2
84
+ - alpha
85
+ version: 0.0.2.alpha
86
+ type: :runtime
87
+ version_requirements: *id005
88
+ description:
89
+ email: pete@debu.gs
90
+ executables:
91
+ - install_gitolite.sh
92
+ extensions: []
93
+
94
+ extra_rdoc_files:
95
+ - doc/TODO
96
+ - doc/project.vim
97
+ - doc/LICENSE
98
+ files:
99
+ - lib/beeta/resource.rb
100
+ - lib/beeta/config.rb
101
+ - lib/beeta/model.rb
102
+ - lib/version.rb
103
+ - lib/beeta.rb
104
+ - doc/TODO
105
+ - doc/project.vim
106
+ - doc/LICENSE
107
+ - bin/install_gitolite.sh
108
+ - Rakefile
109
+ has_rdoc: true
110
+ homepage: http://github.com/pete/beeta
111
+ licenses: []
112
+
113
+ post_install_message:
114
+ rdoc_options: []
115
+
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ~>
122
+ - !ruby/object:Gem::Version
123
+ segments:
124
+ - 1
125
+ - 6
126
+ - 8
127
+ version: 1.6.8
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ requirements: []
137
+
138
+ rubyforge_project:
139
+ rubygems_version: 1.3.7
140
+ signing_key:
141
+ specification_version: 3
142
+ summary: Platform in the form of a service.
143
+ test_files: []
144
+