community-zero 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ #
2
+ # Copyright 2013, Seth Vargo <sethvargo@gmail.com>
3
+ # Copyright 2013, Opscode, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module CommunityZero
19
+ # A general error for failed communicate with a REST API.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class RestError < Error
23
+ attr_reader :response_code, :error
24
+
25
+ def initialize(response_code, error)
26
+ @response_code = response_code
27
+ @error = error
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ #
2
+ # Copyright 2013, Seth Vargo <sethvargo@gmail.com>
3
+ # Copyright 2013, Opscode, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module CommunityZero
19
+ # The base class for objects.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class Object
23
+ require 'community_zero/objects/cookbook'
24
+ end
25
+ end
@@ -0,0 +1,99 @@
1
+ #
2
+ # Copyright 2013, Seth Vargo <sethvargo@gmail.com>
3
+ # Copyright 2013, Opscode, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module CommunityZero
19
+ # An object representation of a cookbook.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class Cookbook
23
+ class << self
24
+ # Create a cookbook object from a hash.
25
+ #
26
+ # @param [Hash] hash
27
+ # the hash from which to create the cookbook
28
+ def from_hash(hash)
29
+ new(hash)
30
+ end
31
+
32
+ # Create a cookbook object from a hash and commit that object
33
+ # to memory.
34
+ #
35
+ # @param [Hash] hash
36
+ # the hash from which to create the cookbook
37
+ def create(hash)
38
+ new(hash).save
39
+ end
40
+ end
41
+
42
+ # Create a new cookbook from the given hash.
43
+ #
44
+ # @param [Hash] hash
45
+ # the hash from which to create the cookbook
46
+ def initialize(hash = {})
47
+ @average_rating = 3
48
+ hash.each { |k,v| instance_variable_set(:"@#{k}",v) }
49
+ end
50
+
51
+ # Save this cookbook in the store.
52
+ def save
53
+ Store.update(self)
54
+ end
55
+
56
+ # Delete this cookbook from the store.
57
+ def destroy
58
+ Store.remove(self)
59
+ end
60
+
61
+ # A list of all other versions of this cookbook.
62
+ #
63
+ # @return [Array<String>]
64
+ # the other verions
65
+ def versions
66
+ @versions ||= Store.versions(self)
67
+ end
68
+
69
+ # The latest (newest) version.
70
+ #
71
+ # @return [String]
72
+ # the newest version
73
+ def latest_version
74
+ @latest_version ||= versions.last
75
+ end
76
+
77
+ # Dump this cookbook to a hash.
78
+ #
79
+ # @return [Hash]
80
+ # the hash representation of this cookbook
81
+ def to_hash
82
+ methods = instance_variables.map { |i| i.to_s.gsub('@', '') }
83
+ Hash[*methods.map { |m| [m, send(m.to_sym)] }.flatten]
84
+ end
85
+
86
+ def method_missing(m, *args, &block)
87
+ if m.to_s =~ /\=$/
88
+ value = args.size == 1 ? args[0] : args
89
+ instance_variable_set(:"@#{m.to_s.gsub('=', '')}", value)
90
+ else
91
+ instance_variable_get(:"@#{m}")
92
+ end
93
+ end
94
+
95
+ def respond_to?(m, include_private = false)
96
+ instance_variables.map(&:to_s).include?("@#{m}")
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,60 @@
1
+ #
2
+ # Copyright 2013, Seth Vargo <sethvargo@gmail.com>
3
+ # Copyright 2013, Opscode, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module CommunityZero
19
+ # A singleton request.
20
+ #
21
+ # @author Seth Vargon <sethvargo@gmail.com>
22
+ class Request
23
+ attr_reader :env
24
+
25
+ def initialize(env)
26
+ @env = env
27
+ end
28
+
29
+ def base_uri
30
+ @base_uri ||= "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{env['SCRIPT_NAME']}"
31
+ end
32
+
33
+ def method
34
+ @env['REQUEST_METHOD']
35
+ end
36
+
37
+ def path
38
+ @path ||= env['PATH_INFO'].split('/').select { |part| part != "" }
39
+ end
40
+
41
+ def body=(body)
42
+ @body = body
43
+ end
44
+
45
+ def body
46
+ @body ||= env['rack.input'].read
47
+ end
48
+
49
+ def query_params
50
+ @query_params ||= begin
51
+ params = Rack::Request.new(env).GET
52
+ params.keys.each do |key|
53
+ params[key] = URI.unescape(params[key])
54
+ end
55
+ params
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,55 @@
1
+ #
2
+ # Copyright 2013, Seth Vargo <sethvargo@gmail.com>
3
+ # Copyright 2013, Opscode, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module CommunityZero
19
+ # The router for the Community Server.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class Router
23
+ require 'community_zero/endpoint'
24
+
25
+ attr_reader :server, :routes
26
+
27
+ def initialize(server, *routes)
28
+ @server = server
29
+ @routes = routes.map do |route, endpoint|
30
+ pattern = Regexp.new("^#{route.gsub(/:[A-Za-z_]+/, '[^/]*')}$")
31
+ [pattern, endpoint]
32
+ end
33
+ end
34
+
35
+ def call(request)
36
+ begin
37
+ path = '/' + request.path.join('/')
38
+ find_endpoint(path).new(server).call(request)
39
+ rescue
40
+ [
41
+ 500,
42
+ { 'Content-Type' => 'text/plain' },
43
+ "Exception raised! #{$!.inspect}\n#{$!.backtrace.join("\n")}"
44
+ ]
45
+ end
46
+ end
47
+
48
+ private
49
+ def find_endpoint(path)
50
+ _, endpoint = routes.find { |route, endpoint| route.match(path) }
51
+ endpoint || NotFoundEndpoint
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,124 @@
1
+ #
2
+ # Copyright 2013, Seth Vargo <sethvargo@gmail.com>
3
+ # Copyright 2013, Opscode, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module CommunityZero
19
+ # A single instance of the Community Server.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class Server
23
+ require 'community_zero'
24
+
25
+ DEFAULT_OPTIONS = {
26
+ :host => '0.0.0.0',
27
+ :port => 3000
28
+ }.freeze
29
+
30
+ # The list of options passed to the server.
31
+ #
32
+ # @return [Hash]
33
+ attr_reader :options
34
+
35
+ # Create a new Community site server.
36
+ #
37
+ # @param [Hash] options
38
+ # a list of options to pass in
39
+ #
40
+ # @option options [String] :host
41
+ # the host to listen on (default is 0.0.0.0)
42
+ # @option options [String, Fixnum] :port
43
+ # the port to listen on (default is 3000)
44
+ def initialize(options = {})
45
+ @options = DEFAULT_OPTIONS.merge(options)
46
+ end
47
+
48
+ # Start the community server.
49
+ #
50
+ # @return [Thread]
51
+ # the thread the server is running in
52
+ def start
53
+ if options[:publish]
54
+ puts ">> Starting Community Zero (v#{CommunityZero::VERSION})..."
55
+ puts ">> Puma (v#{Puma::Const::PUMA_VERSION}) is listening at #{url}"
56
+ puts ">> Press CTRL+C to stop"
57
+ end
58
+
59
+ begin
60
+ thread = server.run.join
61
+ rescue Object, Interrupt
62
+ puts "\n>> Stopping Puma..." if options[:publish]
63
+ server.stop(true) if running?
64
+ end
65
+ end
66
+
67
+ # Determine if the server is currently running.
68
+ #
69
+ # @return [Boolean]
70
+ # true if the server is currently running, false otherwise
71
+ def running?
72
+ server && server.running?
73
+ end
74
+
75
+ # Returns the URL the server is listening on.
76
+ #
77
+ # @example
78
+ # server = CommunityZero.new
79
+ # server.url #=> http://0.0.0.0:3000
80
+ #
81
+ # @example
82
+ # server = CommunityZero.new(host: 'example.com', port: 80)
83
+ # server.url #=> http://example.com:80
84
+ def url
85
+ @url ||= "http://#{options[:host]}:#{options[:port]}"
86
+ end
87
+
88
+ private
89
+ # The Community Zero server.
90
+ #
91
+ # @return [Puma::Server]
92
+ # the actual server object
93
+ def server
94
+ return @server if @server
95
+
96
+ @server = Puma::Server.new(app, Puma::Events.new(STDERR, STDOUT))
97
+ @server.add_tcp_listener(options[:host], options[:port])
98
+ @server
99
+ end
100
+
101
+ # The actual application the server will respond to.
102
+ #
103
+ # @return []
104
+ def app
105
+ lambda do |env|
106
+ request = Request.new(env)
107
+ response = router.call(request)
108
+
109
+ response[-1] = Array(response[-1])
110
+ response
111
+ end
112
+ end
113
+
114
+ def router
115
+ @router ||= Router.new(self,
116
+ ['/search', SearchEndpoint],
117
+ ['/cookbooks', CookbooksEndpoint],
118
+ ['/cookbooks/:name', CookbookEndpoint],
119
+ ['/cookbooks/:name/versions/:version', CookbookVersionsVersionEndpoint],
120
+ )
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,149 @@
1
+ #
2
+ # Copyright 2013, Seth Vargo <sethvargo@gmail.com>
3
+ # Copyright 2013, Opscode, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module CommunityZero
19
+ # @author Seth Vargo <sethvargo@gmail.com>
20
+ module Store
21
+ extend self
22
+
23
+ # The number of cookbooks in the store.
24
+ #
25
+ # @return [Fixnum]
26
+ # the number of cookbooks in the store
27
+ def size
28
+ _cookbooks.keys.size
29
+ end
30
+
31
+ # The full array of cookbooks.
32
+ #
33
+ # @example
34
+ # [
35
+ # #<CommunityZero::Cookbook apache2>,
36
+ # #<CommunityZero::Cookbook apt>
37
+ # ]
38
+ #
39
+ # @return [Array<CommunityZero::Cookbook>]
40
+ # the list of cookbooks
41
+ def cookbooks
42
+ _cookbooks.map { |_,v| v[v.keys.first] }
43
+ end
44
+
45
+ # Query the installed cookbooks, returning those who's name matches the
46
+ # given query.
47
+ #
48
+ # @param [String] query
49
+ # the query parameter
50
+ #
51
+ # @return [Array<CommunityZero::Cookbook>]
52
+ # the list of cookbooks that match the given query
53
+ def search(query)
54
+ regex = Regexp.new(query, 'i')
55
+ _cookbooks.collect do |_, v|
56
+ v[v.keys.first] if regex.match(v[v.keys.first].name)
57
+ end.compact
58
+ end
59
+
60
+ # Delete all cookbooks in the store.
61
+ def destroy_all
62
+ @_cookbooks = nil
63
+ end
64
+
65
+ # Add the given cookbook to the cookbook store. This method's
66
+ # implementation prohibits duplicate cookbooks from entering the store.
67
+ #
68
+ # @param [CommunityZero::Cookbook] cookbook
69
+ # the cookbook to add
70
+ def add(cookbook)
71
+ cookbook = cookbook.dup
72
+ cookbook.created_at = Time.now
73
+ cookbook.updated_at = Time.now
74
+
75
+ entry = _cookbooks[cookbook.name] ||= {}
76
+ entry[cookbook.version] = cookbook
77
+ end
78
+ alias_method :update, :add
79
+
80
+ # Remove the cookbook from the store.
81
+ #
82
+ # @param [CommunityZero::Cookbook] cookbook
83
+ # the cookbook to remove
84
+ def remove(cookbook)
85
+ return unless has_cookbook?(cookbook.name, cookbook.version)
86
+ _cookbooks[cookbook.name].delete(cookbook.version)
87
+ end
88
+
89
+ # Determine if the cookbook store contains a cookbook.
90
+ #
91
+ # @see {find} for the method signature and parameters
92
+ def has_cookbook?(name, version = nil)
93
+ !find(name, version).nil?
94
+ end
95
+
96
+ # Determine if the cookbook store contains a cookbook. If the version
97
+ # attribute is nil, this method will return the latest cookbook version by
98
+ # that name that exists. If the version is specified, this method will only
99
+ # return that specific version, or nil if that cookbook at that version
100
+ # exists.
101
+ #
102
+ # @param [String] name
103
+ # the name of the cookbook to find
104
+ # @param [String, nil] version
105
+ # the version of the cookbook to search
106
+ #
107
+ # @return [CommunityZero::Cookbook, nil]
108
+ # the cookbook in the store, or nil if one does not exist
109
+ def find(name, version = nil)
110
+ possibles = _cookbooks[name]
111
+ return nil if possibles.nil?
112
+
113
+ version ||= possibles.keys.sort.last
114
+ possibles[version]
115
+ end
116
+
117
+ # Return a list of all versions for the given cookbook.
118
+ #
119
+ # @param [String, CommunityZero::Cookbook] name
120
+ # the cookbook or name of the cookbook to get versions for
121
+ def versions(name)
122
+ name = name.respond_to?(:name) ? name.name : name
123
+ (_cookbooks[name] && _cookbooks[name].keys.sort) || []
124
+ end
125
+
126
+ private
127
+ # All the cookbooks in the store.
128
+ #
129
+ # @example
130
+ # {
131
+ # 'apache2' => {
132
+ # '1.0.0' => {
133
+ # 'license' => 'Apache 2.0',
134
+ # 'version' => '1.0.0',
135
+ # 'tarball_file_size' => 20949,
136
+ # 'file' => 'http://s3.amazonaws.com...',
137
+ # 'cookbook' => 'http://localhost:4000/apache2',
138
+ # 'average_rating' => nil
139
+ # }
140
+ # }
141
+ # }
142
+ #
143
+ # @return [Hash<String, Hash<String, CommunityZero::Cookbook>>]
144
+ def _cookbooks
145
+ @_cookbooks ||= {}
146
+ end
147
+
148
+ end
149
+ end