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,112 @@
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 any endpoint.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class Endpoint
23
+ require 'community_zero/endpoints/cookbook_endpoint'
24
+ require 'community_zero/endpoints/cookbook_versions_version_endpoint'
25
+ require 'community_zero/endpoints/cookbooks_endpoint'
26
+ require 'community_zero/endpoints/not_found_endpoint'
27
+ require 'community_zero/endpoints/search_endpoint'
28
+
29
+ METHODS = [:get, :put, :post, :delete].freeze
30
+
31
+ attr_reader :server
32
+
33
+ # Create a new endpoint.
34
+ #
35
+ # @param [CommunityZero::Server] server
36
+ # the server to respond to this endpoint
37
+ def initialize(server)
38
+ @server = server
39
+ end
40
+
41
+ # Generate the URL for the given cookbook.
42
+ #
43
+ # @param [CommunityZero::Cookbook] cookbook
44
+ # the coookbook to generate the URL for
45
+ #
46
+ # @return [String]
47
+ # the URL
48
+ def url_for(cookbook)
49
+ "#{server.url}/cookbooks/#{cookbook.name}"
50
+ end
51
+
52
+ # Generate the version URL for the given cookbook and version.
53
+ #
54
+ # @param [CommunityZero::Cookbook] cookbook
55
+ # the coookbook to generate the URL for
56
+ # @param [String] version
57
+ # the version to generate a string for
58
+ #
59
+ # @return [String]
60
+ # the URL
61
+ def version_url_for(cookbook, version)
62
+ "#{server.url}/cookbooks/#{cookbook.name}/versions/#{version.gsub('.', '_')}"
63
+ end
64
+
65
+ # Call the request.
66
+ #
67
+ # @param [CommunityZero::Request] request
68
+ # the request object
69
+ def call(request)
70
+ m = request.method.downcase.to_sym
71
+
72
+ # Only respond to listed methods
73
+ unless respond_to?(m)
74
+ allowed = METHODS.select { |m| respond_to?(m) }.map(&:upcase).join(', ')
75
+ return [
76
+ 405,
77
+ { 'Content-Type' => 'text/plain', 'Allow' => allowed },
78
+ "Method not allowed: '#{request.env['REQUEST_METHOD']}'"
79
+ ]
80
+ end
81
+
82
+ # Must accept JSON
83
+ unless request.env['HTTP_ACCEPT'].to_s.split(';').include?('application/json')
84
+ return [
85
+ 406,
86
+ { 'Content-Type' => 'text/plain' },
87
+ 'Must accept application/json'
88
+ ]
89
+ end
90
+
91
+ begin
92
+ send(m, request)
93
+ rescue RestError => e
94
+ error(e.response_code, e.error)
95
+ end
96
+ end
97
+
98
+ private
99
+ def error(response_code, error)
100
+ respond(response_code, { 'error' => error })
101
+ end
102
+
103
+ def respond(response_code = 200, content)
104
+ [
105
+ response_code,
106
+ { 'Content-Type' => 'application/json' },
107
+ JSON.pretty_generate(content)
108
+ ]
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,68 @@
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 endpoint for interacting with a single cookbook.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class CookbookEndpoint < Endpoint
23
+ # GET /cookbooks/:name
24
+ def get(request)
25
+ name = request.path.last
26
+ cookbook = Store.find(name)
27
+
28
+ if cookbook = Store.find(name)
29
+ respond({
30
+ 'name' => cookbook.name,
31
+ 'maintainer' => cookbook.maintainer,
32
+ 'category' => cookbook.category,
33
+ 'external_url' => cookbook.external_url,
34
+ 'description' => cookbook.description,
35
+ 'average_rating' => cookbook.average_rating,
36
+ 'versions' => cookbook.versions.map { |i| version_url_for(cookbook, i) },
37
+ 'latest_version' => version_url_for(cookbook, cookbook.latest_version),
38
+ 'created_at' => cookbook.created_at,
39
+ 'updated_at' => cookbook.upadated_at,
40
+ })
41
+ else
42
+ respond(404,
43
+ {
44
+ 'error_code' => 'NOT_FOUND',
45
+ 'error_messages' => ['Resource not found'],
46
+ }
47
+ )
48
+ end
49
+ end
50
+
51
+ # DELETE /cookbooks/:name
52
+ def delete(request)
53
+ name = request.path.last
54
+
55
+ if cookbook = Store.find(name)
56
+ cookbook.destroy
57
+ respond({})
58
+ else
59
+ respond(404,
60
+ {
61
+ 'error_code' => 'NOT_FOUND',
62
+ 'error_messages' => ['Resource not found'],
63
+ }
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,59 @@
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 endpoint for interacting with a single cookbook version.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class CookbookVersionsVersionEndpoint < Endpoint
23
+ def get(request)
24
+ name, version = request.path[1], request.path[-1].gsub('_', '.')
25
+
26
+ unless cookbook = Store.find(name)
27
+ return respond(404,
28
+ {
29
+ 'error_code' => 'NOT_FOUND',
30
+ 'error_messages' => ['Resource not found'],
31
+ }
32
+ )
33
+ end
34
+
35
+ version = cookbook.latest_version if version == 'latest'
36
+ cookbook = Store.find(name, version)
37
+ respond(response_hash_for(cookbook))
38
+ end
39
+
40
+ private
41
+ # The response hash for this cookbook.
42
+ #
43
+ # @param [CommunityZero::Cookbook] cookbook
44
+ # the cookbook to generate a hash for
45
+ def response_hash_for(cookbook)
46
+ {
47
+ 'cookbook' => url_for(cookbook),
48
+ 'average_rating' => cookbook.average_rating,
49
+ 'version' => cookbook.version,
50
+ 'license' => cookbook.license,
51
+ 'file' => "http://s3.amazonaws.com/#{cookbook.name}.tgz",
52
+ 'tarball_file_size' => cookbook.name.split('').map(&:ord).inject(&:+) * 25, # don't even
53
+ 'created_at' => cookbook.created_at,
54
+ 'updated_at' => cookbook.upadated_at,
55
+ }
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,102 @@
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 endpoint for all cookbooks.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class CookbooksEndpoint < Endpoint
23
+ require 'rubygems/package'
24
+ require 'zlib'
25
+
26
+ # GET /cookbooks
27
+ def get(request)
28
+ start = Integer(request.query_params['start'] || 0)
29
+ items = Integer(request.query_params['items'] || 10)
30
+ cookbooks = Store.cookbooks[start...items] || []
31
+
32
+ respond({
33
+ 'items' => cookbooks.collect { |cookbook|
34
+ {
35
+ 'cookbook_name' => cookbook.name,
36
+ 'cookbook_description' => cookbook.description,
37
+ 'cookbook' => url_for(cookbook),
38
+ 'cookbook_maintainer' => cookbook.maintainer
39
+ }
40
+ },
41
+ 'total' => Store.size,
42
+ 'start' => start.to_i,
43
+ })
44
+ end
45
+
46
+ # POST /cookbooks
47
+ def post(request)
48
+ params = Rack::Utils::Multipart.parse_multipart(request.env)
49
+ cookbook = params['cookbook']
50
+ tarball = params['tarball']
51
+
52
+ metadata = Metadata.new(read_tarball(tarball))
53
+
54
+ if Store.find(metadata.name, metadata.version)
55
+ respond(401,
56
+ {
57
+ 'error_code' => 'ALREADY_EXISTS',
58
+ 'error_messages' => ['Resource already exists'],
59
+ }
60
+ )
61
+ else
62
+ respond(create_cookbook(metadata).to_hash)
63
+ end
64
+ end
65
+
66
+ private
67
+ # Create the cookbook from the metadata.
68
+ #
69
+ # @param [CommunityZero::Metadata] metadata
70
+ # the metadata to create the cookbook from
71
+ def create_cookbook(metadata)
72
+ Cookbook.create(
73
+ :name => metadata.name,
74
+ :category => nil,
75
+ :maintainer => metadata.maintainer,
76
+ :description => metadata.description,
77
+ :version => metadata.version
78
+ )
79
+ end
80
+
81
+ # Parse the metadata from the tarball.
82
+ #
83
+ # @param [Tempfile] tarball
84
+ # the temporarily uploaded file
85
+ def read_tarball(tarball)
86
+ gzip = Zlib::GzipReader.new(tarball[:tempfile])
87
+ tar = Gem::Package::TarReader.new(gzip)
88
+ metadata = nil
89
+
90
+ tar.each do |entry|
91
+ if entry.full_name =~ /metadata\.rb$/
92
+ metadata = entry.read
93
+ break
94
+ end
95
+ end
96
+ tar.close
97
+
98
+ return metadata
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,37 @@
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 general 404 endpoint.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class NotFoundEndpoint < Endpoint
23
+ def call(request)
24
+ error("Object not found: #{request.env['REQUEST_PATH']}")
25
+ end
26
+
27
+ private
28
+ def error(message)
29
+ [
30
+ 404,
31
+ { 'Content-Type' => 'application/json' },
32
+ JSON.pretty_generate({ 'error' => message })
33
+ ]
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,44 @@
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 endpoint for searching for cookbooks.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class SearchEndpoint < Endpoint
23
+ # GET /search?q=QUERY
24
+ def get(request)
25
+ q = request.query_params['q'].to_s
26
+ start = Integer(request.query_params['start'] || 0)
27
+ items = Integer(request.query_params['items'] || 10)
28
+ cookbooks = Store.search(q)[start...items] || []
29
+
30
+ respond({
31
+ 'items' => cookbooks.collect { |cookbook|
32
+ {
33
+ 'cookbook_name' => cookbook.name,
34
+ 'cookbook_description' => cookbook.description,
35
+ 'cookbook' => url_for(cookbook),
36
+ 'cookbook_maintainer' => cookbook.maintainer
37
+ }
38
+ },
39
+ 'total' => cookbooks.size,
40
+ 'start' => start,
41
+ })
42
+ end
43
+ end
44
+ 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 errors.
20
+ #
21
+ # @author Seth Vargo <sethvargo@gmail.com>
22
+ class Error < StandardError; end
23
+
24
+ require 'community_zero/errors/rest_error'
25
+ end