puppet-forge-server 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.travis.yml +20 -0
- data/Gemfile +20 -0
- data/README.md +127 -0
- data/bin/puppet-forge-server +21 -0
- data/lib/puppet_forge_server.rb +63 -0
- data/lib/puppet_forge_server/api/v1/modules.rb +71 -0
- data/lib/puppet_forge_server/api/v1/releases.rb +31 -0
- data/lib/puppet_forge_server/api/v3/modules.rb +57 -0
- data/lib/puppet_forge_server/api/v3/releases.rb +38 -0
- data/lib/puppet_forge_server/app/version1.rb +67 -0
- data/lib/puppet_forge_server/app/version3.rb +68 -0
- data/lib/puppet_forge_server/backends/directory.rb +67 -0
- data/lib/puppet_forge_server/backends/proxy.rb +63 -0
- data/lib/puppet_forge_server/backends/proxy_v1.rb +69 -0
- data/lib/puppet_forge_server/backends/proxy_v3.rb +63 -0
- data/lib/puppet_forge_server/errors.rb +20 -0
- data/lib/puppet_forge_server/http/http_client.rb +35 -0
- data/lib/puppet_forge_server/models/builder.rb +37 -0
- data/lib/puppet_forge_server/models/metadata.rb +46 -0
- data/lib/puppet_forge_server/patches.rb +61 -0
- data/lib/puppet_forge_server/server.rb +88 -0
- data/lib/puppet_forge_server/utils/archiver.rb +29 -0
- data/lib/puppet_forge_server/utils/buffer.rb +33 -0
- data/lib/puppet_forge_server/utils/http.rb +30 -0
- data/lib/puppet_forge_server/utils/option_parser.rb +76 -0
- data/lib/puppet_forge_server/utils/url.rb +36 -0
- data/lib/puppet_forge_server/version.rb +19 -0
- data/puppet-forge-server.gemspec +47 -0
- metadata +172 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 North Development AB
|
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
|
+
module PuppetForgeServer::Api::V3
|
18
|
+
module Releases
|
19
|
+
def get_releases(metadata)
|
20
|
+
metadata.map do |element|
|
21
|
+
name = element[:metadata].name.sub(/^[^-]+-/, '')
|
22
|
+
|
23
|
+
{
|
24
|
+
:uri => "/v3/releases/#{element[:metadata].name}-#{element[:metadata].version}",
|
25
|
+
:module => {
|
26
|
+
:uri => "/v3/modules/#{element[:metadata].name}",
|
27
|
+
:name => name
|
28
|
+
},
|
29
|
+
:metadata => element[:metadata].to_hash,
|
30
|
+
:version => element[:metadata].version,
|
31
|
+
:tags => element[:tags] ? element[:tags] : [element[:metadata].author, name],
|
32
|
+
:file_uri => "/v3/files#{element[:path]}",
|
33
|
+
:file_md5 => element[:checksum]
|
34
|
+
}
|
35
|
+
end.version_sort_by { |r| r[:version] }.reverse
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 North Development AB
|
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
|
+
require 'sinatra/base'
|
18
|
+
require 'json'
|
19
|
+
|
20
|
+
module PuppetForgeServer::App
|
21
|
+
class Version1 < Sinatra::Base
|
22
|
+
include PuppetForgeServer::Api::V1::Releases
|
23
|
+
include PuppetForgeServer::Api::V1::Modules
|
24
|
+
include PuppetForgeServer::Utils::Buffer
|
25
|
+
|
26
|
+
def initialize(backends)
|
27
|
+
super(nil)
|
28
|
+
@backends = backends
|
29
|
+
end
|
30
|
+
|
31
|
+
get '/api/v1/releases.json' do
|
32
|
+
unless params[:module]
|
33
|
+
halt 400, {'error' => 'The number of version constraints in the query does not match the number of module names'}.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
author, name = params[:module].split '/'
|
37
|
+
version = params[:version] if params[:version]
|
38
|
+
|
39
|
+
metadata = @backends.map do |backend|
|
40
|
+
backend.get_metadata(author, name, {:version => version, :with_checksum => false})
|
41
|
+
end.flatten.uniq
|
42
|
+
|
43
|
+
halt 400, {'errors' => ["'#{params[:module]}' is not a valid module slug"]}.to_json if metadata.empty?
|
44
|
+
|
45
|
+
{"#{author}/#{name}" => get_releases(metadata)}.to_json
|
46
|
+
end
|
47
|
+
|
48
|
+
get '/api/v1/files/*' do
|
49
|
+
captures = params[:captures].first
|
50
|
+
buffer = get_buffer(@backends, captures)
|
51
|
+
|
52
|
+
halt 404, {'errors' => ['404 Not found']}.to_json unless buffer
|
53
|
+
|
54
|
+
content_type 'application/octet-stream'
|
55
|
+
attachment captures.split('/').last
|
56
|
+
download buffer
|
57
|
+
end
|
58
|
+
|
59
|
+
get '/modules.json' do
|
60
|
+
query = params[:q]
|
61
|
+
metadata = @backends.map do |backend|
|
62
|
+
backend.query_metadata(query, {:with_checksum => false})
|
63
|
+
end.flatten.uniq
|
64
|
+
get_modules(metadata).to_json
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 North Development AB
|
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
|
+
require 'sinatra/base'
|
18
|
+
|
19
|
+
module PuppetForgeServer::App
|
20
|
+
class Version3 < Sinatra::Base
|
21
|
+
include PuppetForgeServer::Api::V3::Releases
|
22
|
+
include PuppetForgeServer::Api::V3::Modules
|
23
|
+
include PuppetForgeServer::Utils::Buffer
|
24
|
+
|
25
|
+
def initialize(backends)
|
26
|
+
super(nil)
|
27
|
+
@backends = backends
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/v3/releases' do
|
31
|
+
halt 400, {'error' => 'The number of version constraints in the query does not match the number of module names'}.to_json unless params[:module]
|
32
|
+
|
33
|
+
author, name, version = params[:module].split '-'
|
34
|
+
metadata = @backends.map do |backend|
|
35
|
+
backend.get_metadata(author, name, {:version => version})
|
36
|
+
end.flatten.uniq
|
37
|
+
|
38
|
+
halt 404, {'pagination' => {'next' => false}, 'results' => []}.to_json if metadata.empty?
|
39
|
+
{'pagination' => {'next' => false}, 'results' => get_releases(metadata)}.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
get '/v3/files/*' do
|
43
|
+
captures = params[:captures].first
|
44
|
+
buffer = get_buffer(@backends, captures)
|
45
|
+
|
46
|
+
halt 404, {'errors' => ['404 Not found']}.to_json unless buffer
|
47
|
+
|
48
|
+
content_type 'application/octet-stream'
|
49
|
+
attachment captures.split('/').last
|
50
|
+
download buffer
|
51
|
+
end
|
52
|
+
|
53
|
+
get '/v3/modules/:author-:name' do
|
54
|
+
author = params[:author]
|
55
|
+
name = params[:name]
|
56
|
+
|
57
|
+
halt 400, {'errors' => "'#{params[:module]}' is not a valid module slug"}.to_json unless author && name
|
58
|
+
|
59
|
+
metadata = @backends.map do |backend|
|
60
|
+
backend.get_metadata(author, name)
|
61
|
+
end.flatten.uniq
|
62
|
+
|
63
|
+
halt 404, {'errors' => ['404 Not found']}.to_json if metadata.empty?
|
64
|
+
|
65
|
+
get_modules(metadata).to_json
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 North Development AB
|
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
|
+
require 'digest/md5'
|
18
|
+
require 'pathname'
|
19
|
+
|
20
|
+
module PuppetForgeServer::Backends
|
21
|
+
class Directory
|
22
|
+
include PuppetForgeServer::Utils::Archiver
|
23
|
+
|
24
|
+
# Give highest priority to locally hosted modules
|
25
|
+
@@PRIORITY = 0
|
26
|
+
attr_reader :PRIORITY
|
27
|
+
|
28
|
+
def initialize(url)
|
29
|
+
@module_dir = url
|
30
|
+
end
|
31
|
+
|
32
|
+
def query_metadata(query, options = {})
|
33
|
+
get_file_metadata("*#{query}*.tar.gz", options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_metadata(author, name, options = {})
|
37
|
+
version = options[:version] ? options[:version] : '*'
|
38
|
+
get_file_metadata("#{author}-#{name}-#{version}.tar.gz", options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_file_buffer(relative_path)
|
42
|
+
path = File.join(File.expand_path(@module_dir), relative_path)
|
43
|
+
File.open(path, 'r') if File.exist?(path)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def read_metadata(archive_path)
|
48
|
+
metadata_file = read_entry(archive_path, %r[[^/]+/metadata\.json$])
|
49
|
+
JSON.parse(metadata_file)
|
50
|
+
rescue => error
|
51
|
+
warn "Error reading from module archive #{archive_path}: #{error}"
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_file_metadata(file_name, options)
|
56
|
+
options = ({:with_checksum => true}).merge(options)
|
57
|
+
Dir["#{@module_dir}/**/#{file_name}"].map do |path|
|
58
|
+
checksum = Digest::MD5.hexdigest(File.read(path)) if options[:with_checksum] == true
|
59
|
+
{
|
60
|
+
:metadata => PuppetForgeServer::Models::Metadata.new(read_metadata(path)),
|
61
|
+
:checksum => checksum,
|
62
|
+
:path => "/#{Pathname.new(path).relative_path_from(Pathname.new(@module_dir))}"
|
63
|
+
}
|
64
|
+
end.compact
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 North Development AB
|
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
|
+
require 'json'
|
18
|
+
require 'digest/sha1'
|
19
|
+
|
20
|
+
module PuppetForgeServer::Backends
|
21
|
+
class Proxy
|
22
|
+
|
23
|
+
def initialize(url, cache_dir, http_client)
|
24
|
+
@url = url
|
25
|
+
@cache_dir = File.join(cache_dir, Digest::SHA1.hexdigest(@url))
|
26
|
+
@http_client = http_client
|
27
|
+
|
28
|
+
# Create directory structure for all alphabetic letters
|
29
|
+
(10...36).each do |i|
|
30
|
+
FileUtils.mkdir_p(File.join(@cache_dir, i.to_s(36)))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_file_buffer(relative_path)
|
35
|
+
file_name = relative_path.split('/').last
|
36
|
+
File.join(@cache_dir, file_name[0], file_name)
|
37
|
+
path = Dir["#{@cache_dir}/**/#{file_name}"].first
|
38
|
+
unless File.exist?("#{path}")
|
39
|
+
buffer = download("/#{relative_path}")
|
40
|
+
File.open(File.join(@cache_dir, file_name[0], file_name), 'wb') do |file|
|
41
|
+
file.write(buffer.read)
|
42
|
+
end
|
43
|
+
path = File.join(@cache_dir, file_name[0], file_name)
|
44
|
+
end
|
45
|
+
File.open(path, 'rb')
|
46
|
+
rescue OpenURI::HTTPError
|
47
|
+
# ignore
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
def get(relative_url)
|
52
|
+
@http_client.get(url(relative_url))
|
53
|
+
end
|
54
|
+
|
55
|
+
def download(relative_url)
|
56
|
+
@http_client.download(url(relative_url))
|
57
|
+
end
|
58
|
+
|
59
|
+
def url(relative_url)
|
60
|
+
@url + relative_url
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 North Development AB
|
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
|
+
require 'json'
|
18
|
+
require 'digest/md5'
|
19
|
+
|
20
|
+
module PuppetForgeServer::Backends
|
21
|
+
class ProxyV1 < PuppetForgeServer::Backends::Proxy
|
22
|
+
|
23
|
+
# Priority should be lower than v3 API proxies as v3 requires less API calls
|
24
|
+
@@PRIORITY = 15
|
25
|
+
attr_reader :PRIORITY
|
26
|
+
|
27
|
+
def initialize(url, cache_dir, http_client = PuppetForgeServer::Http::HttpClient.new)
|
28
|
+
super(url, cache_dir, http_client)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_metadata(author, name, options = {})
|
32
|
+
options = ({:with_checksum => true}).merge(options)
|
33
|
+
query ="#{author}/#{name}"
|
34
|
+
get_module_metadata(JSON.parse(get("/modules.json?q=#{query}")).select { |e| e['full_name'].match("#{query}") }, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def query_metadata(query, options = {})
|
38
|
+
options = ({:with_checksum => true}).merge(options)
|
39
|
+
get_module_metadata(JSON.parse(get("/modules.json?q=#{query}")).select { |e| e['full_name'].match("*#{query}*") }, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def read_metadata(element, release)
|
44
|
+
element['project_page'] = element['project_url']
|
45
|
+
element['name'] = element['full_name'] ? element['full_name'].gsub('/', '-') : element['name']
|
46
|
+
element['description'] = element['desc']
|
47
|
+
element['version'] = release['version'] ? release['version'] : element['version']
|
48
|
+
element['dependencies'] = release['dependencies'] ? release['dependencies'] : []
|
49
|
+
%w(project_url full_name releases tag_list desc).each { |key| element.delete(key) }
|
50
|
+
element
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_module_metadata(modules, options)
|
54
|
+
modules.map do |element|
|
55
|
+
version = options['version'] ? "&version=#{options['version']}" : ''
|
56
|
+
JSON.parse(get("/api/v1/releases.json?module=#{element['author']}/#{element['name']}#{version}")).values.first.map do |release|
|
57
|
+
tags = element['tag_list'] ? element['tag_list'] : nil
|
58
|
+
raw_metadata = read_metadata(element, release)
|
59
|
+
{
|
60
|
+
:metadata => PuppetForgeServer::Models::Metadata.new(raw_metadata),
|
61
|
+
:checksum => options[:with_checksum] ? Digest::MD5.hexdigest(File.read(get_file_buffer(release['file']))) : nil,
|
62
|
+
:path => "#{release['file']}",
|
63
|
+
:tags => tags
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end.compact
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 North Development AB
|
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
|
+
require 'json'
|
18
|
+
|
19
|
+
module PuppetForgeServer::Backends
|
20
|
+
class ProxyV3 < PuppetForgeServer::Backends::Proxy
|
21
|
+
|
22
|
+
@@PRIORITY = 10
|
23
|
+
attr_reader :PRIORITY
|
24
|
+
|
25
|
+
def initialize(url, cache_dir, http_client = PuppetForgeServer::Http::HttpClient.new)
|
26
|
+
super(url, cache_dir, http_client)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_metadata(author, name, options = {})
|
30
|
+
options = ({:with_checksum => true}).merge(options)
|
31
|
+
query ="#{author}-#{name}"
|
32
|
+
releases = options[:version] ? [JSON.parse(get("/v3/releases/#{query}-#{options[:version]}"))] : get_all_release_pages("/v3/releases?module=#{query}")
|
33
|
+
get_release_metadata(releases)
|
34
|
+
end
|
35
|
+
|
36
|
+
def query_metadata(query, options = {})
|
37
|
+
author, name = query.split('-')
|
38
|
+
get_metadata(author, name, options) if author && name
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def get_all_release_pages(next_page)
|
43
|
+
releases = []
|
44
|
+
begin
|
45
|
+
result = JSON.parse(get(next_page))
|
46
|
+
releases += result['results']
|
47
|
+
next_page = result['pagination']['next']
|
48
|
+
end while next_page
|
49
|
+
releases
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_release_metadata(releases)
|
53
|
+
releases.map do |element|
|
54
|
+
{
|
55
|
+
:metadata => PuppetForgeServer::Models::Metadata.new(element['metadata']),
|
56
|
+
:checksum => element['file_md5'],
|
57
|
+
:path => element['file_uri'],
|
58
|
+
:tags => element['tags']
|
59
|
+
}
|
60
|
+
end.compact
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 North Development AB
|
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
|
+
module PuppetForgeServer::Errors
|
18
|
+
class Expected
|
19
|
+
end
|
20
|
+
end
|