community-zero 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +202 -0
- data/README.md +109 -0
- data/Rakefile +18 -0
- data/bin/community-zero +54 -0
- data/lib/community_zero.rb +31 -0
- data/lib/community_zero/chef.rb +20 -0
- data/lib/community_zero/chef/metadata.rb +49 -0
- data/lib/community_zero/endpoint.rb +112 -0
- data/lib/community_zero/endpoints/cookbook_endpoint.rb +68 -0
- data/lib/community_zero/endpoints/cookbook_versions_version_endpoint.rb +59 -0
- data/lib/community_zero/endpoints/cookbooks_endpoint.rb +102 -0
- data/lib/community_zero/endpoints/not_found_endpoint.rb +37 -0
- data/lib/community_zero/endpoints/search_endpoint.rb +44 -0
- data/lib/community_zero/error.rb +25 -0
- data/lib/community_zero/errors/rest_error.rb +30 -0
- data/lib/community_zero/object.rb +25 -0
- data/lib/community_zero/objects/cookbook.rb +99 -0
- data/lib/community_zero/request.rb +60 -0
- data/lib/community_zero/router.rb +55 -0
- data/lib/community_zero/server.rb +124 -0
- data/lib/community_zero/store.rb +149 -0
- data/lib/community_zero/version.rb +20 -0
- metadata +150 -0
@@ -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
|