community-zero 1.0.0

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,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