puppet_forge 1.0.6 → 2.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.
- data/CHANGELOG.md +23 -0
- data/MAINTAINERS +13 -0
- data/README.md +48 -6
- data/lib/puppet_forge.rb +4 -0
- data/lib/puppet_forge/connection.rb +81 -0
- data/lib/puppet_forge/connection/connection_failure.rb +26 -0
- data/lib/puppet_forge/error.rb +34 -0
- data/lib/{her → puppet_forge}/lazy_accessors.rb +20 -27
- data/lib/{her → puppet_forge}/lazy_relations.rb +28 -9
- data/lib/puppet_forge/middleware/symbolify_json.rb +72 -0
- data/lib/puppet_forge/tar.rb +10 -0
- data/lib/puppet_forge/tar/mini.rb +81 -0
- data/lib/puppet_forge/unpacker.rb +68 -0
- data/lib/puppet_forge/v3.rb +11 -0
- data/lib/puppet_forge/v3/base.rb +106 -73
- data/lib/puppet_forge/v3/base/paginated_collection.rb +23 -14
- data/lib/puppet_forge/v3/metadata.rb +197 -0
- data/lib/puppet_forge/v3/module.rb +2 -1
- data/lib/puppet_forge/v3/release.rb +33 -8
- data/lib/puppet_forge/v3/user.rb +2 -0
- data/lib/puppet_forge/version.rb +1 -1
- data/puppet_forge.gemspec +6 -3
- data/spec/fixtures/v3/modules/puppetlabs-apache.json +21 -1
- data/spec/fixtures/v3/releases/puppetlabs-apache-0.0.1.json +4 -1
- data/spec/integration/forge/v3/module_spec.rb +79 -0
- data/spec/integration/forge/v3/release_spec.rb +75 -0
- data/spec/integration/forge/v3/user_spec.rb +70 -0
- data/spec/spec_helper.rb +15 -8
- data/spec/unit/forge/connection/connection_failure_spec.rb +30 -0
- data/spec/unit/forge/connection_spec.rb +53 -0
- data/spec/unit/{her → forge}/lazy_accessors_spec.rb +20 -13
- data/spec/unit/{her → forge}/lazy_relations_spec.rb +60 -46
- data/spec/unit/forge/middleware/symbolify_json_spec.rb +63 -0
- data/spec/unit/forge/tar/mini_spec.rb +85 -0
- data/spec/unit/forge/tar_spec.rb +9 -0
- data/spec/unit/forge/unpacker_spec.rb +58 -0
- data/spec/unit/forge/v3/base/paginated_collection_spec.rb +68 -46
- data/spec/unit/forge/v3/base_spec.rb +1 -1
- data/spec/unit/forge/v3/metadata_spec.rb +300 -0
- data/spec/unit/forge/v3/module_spec.rb +14 -36
- data/spec/unit/forge/v3/release_spec.rb +9 -30
- data/spec/unit/forge/v3/user_spec.rb +7 -7
- metadata +127 -41
- checksums.yaml +0 -7
- data/lib/puppet_forge/middleware/json_for_her.rb +0 -37
@@ -0,0 +1,72 @@
|
|
1
|
+
module PuppetForge
|
2
|
+
module Middleware
|
3
|
+
|
4
|
+
# SymbolifyJson is a Faraday Middleware that will process any response formatted as a hash
|
5
|
+
# and change all the keys into symbols (as long as they respond to the method #to_sym.
|
6
|
+
#
|
7
|
+
# This middleware makes no changes to the values of the hash.
|
8
|
+
# If the response is not a hash, no changes will be made.
|
9
|
+
class SymbolifyJson < Faraday::Middleware
|
10
|
+
|
11
|
+
# Processes an array
|
12
|
+
#
|
13
|
+
# @return an array with any hash's keys turned into symbols if possible
|
14
|
+
def process_array(array)
|
15
|
+
array.map do |arg|
|
16
|
+
# Search any arrays and hashes for hash keys
|
17
|
+
if arg.is_a? Hash
|
18
|
+
process_hash(arg)
|
19
|
+
elsif arg.is_a? Array
|
20
|
+
process_array(arg)
|
21
|
+
else
|
22
|
+
arg
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Processes a hash
|
28
|
+
#
|
29
|
+
# @return a hash with all keys turned into symbols if possible
|
30
|
+
def process_hash(hash)
|
31
|
+
|
32
|
+
# hash.map returns an array in the format
|
33
|
+
# [ [key, value], [key2, value2], ... ]
|
34
|
+
# Hash[] converts that into a hash in the format
|
35
|
+
# { key => value, key2 => value2, ... }
|
36
|
+
Hash[hash.map do |key, val|
|
37
|
+
# Convert to a symbol if possible
|
38
|
+
if key.respond_to? :to_sym
|
39
|
+
new_key = key.to_sym
|
40
|
+
else
|
41
|
+
new_key = key
|
42
|
+
end
|
43
|
+
|
44
|
+
# If value is a hash or array look for more hash keys inside.
|
45
|
+
if val.is_a?(Hash)
|
46
|
+
[new_key, process_hash(val)]
|
47
|
+
elsif val.is_a?(Array)
|
48
|
+
[new_key, process_array(val)]
|
49
|
+
else
|
50
|
+
[new_key, val]
|
51
|
+
end
|
52
|
+
end]
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_response(env)
|
56
|
+
if !env["body"].nil? && env["body"].is_a?(Hash)
|
57
|
+
process_hash(env.body)
|
58
|
+
else
|
59
|
+
env.body
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def call(environment)
|
64
|
+
@app.call(environment).on_complete do |env|
|
65
|
+
env.body = process_response(env)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'archive/tar/minitar'
|
3
|
+
|
4
|
+
module PuppetForge
|
5
|
+
class Tar
|
6
|
+
class Mini
|
7
|
+
|
8
|
+
SYMLINK_FLAGS = [2]
|
9
|
+
VALID_TAR_FLAGS = (0..7)
|
10
|
+
|
11
|
+
# @return [Hash{:symbol => Array<String>}] a hash with file-category keys pointing to lists of filenames.
|
12
|
+
def unpack(sourcefile, destdir)
|
13
|
+
# directories need to be changed outside of the Minitar::unpack because directories don't have a :file_done action
|
14
|
+
dirlist = []
|
15
|
+
file_lists = {}
|
16
|
+
Zlib::GzipReader.open(sourcefile) do |reader|
|
17
|
+
file_lists = validate_files(reader)
|
18
|
+
Archive::Tar::Minitar.unpack(reader, destdir, file_lists[:valid]) do |action, name, stats|
|
19
|
+
case action
|
20
|
+
when :file_done
|
21
|
+
FileUtils.chmod('u+rw,g+r,a-st', "#{destdir}/#{name}")
|
22
|
+
when :file_start
|
23
|
+
validate_entry(destdir, name)
|
24
|
+
when :dir
|
25
|
+
validate_entry(destdir, name)
|
26
|
+
dirlist << "#{destdir}/#{name}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
dirlist.each {|d| File.chmod(0755, d)}
|
31
|
+
file_lists
|
32
|
+
end
|
33
|
+
|
34
|
+
def pack(sourcedir, destfile)
|
35
|
+
Zlib::GzipWriter.open(destfile) do |writer|
|
36
|
+
Archive::Tar::Minitar.pack(sourcedir, writer)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Categorize all the files in tarfile as :valid, :invalid, or :symlink.
|
43
|
+
#
|
44
|
+
# :invalid files include 'x' and 'g' flags from the PAX standard but and any other non-standard tar flags.
|
45
|
+
# tar format info: http://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Ftaf.htm
|
46
|
+
# pax format info: http://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Fpxarchfm.htm
|
47
|
+
# :symlinks are not supported in Puppet modules
|
48
|
+
# :valid files are any of those that can be used in modules
|
49
|
+
# @param tarfile name of the tarfile
|
50
|
+
# @return [Hash{:symbol => Array<String>}] a hash with file-category keys pointing to lists of filenames.
|
51
|
+
def validate_files(tarfile)
|
52
|
+
file_lists = {:valid => [], :invalid => [], :symlinks => []}
|
53
|
+
Archive::Tar::Minitar.open(tarfile).each do |entry|
|
54
|
+
flag = entry.typeflag
|
55
|
+
if flag.nil? || flag =~ /[[:digit:]]/ && SYMLINK_FLAGS.include?(flag.to_i)
|
56
|
+
file_lists[:symlinks] << entry.name
|
57
|
+
elsif flag.nil? || flag =~ /[[:digit:]]/ && VALID_TAR_FLAGS.include?(flag.to_i)
|
58
|
+
file_lists[:valid] << entry.name
|
59
|
+
else
|
60
|
+
file_lists[:invalid] << entry.name
|
61
|
+
end
|
62
|
+
end
|
63
|
+
file_lists
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_entry(destdir, path)
|
67
|
+
if Pathname.new(path).absolute?
|
68
|
+
raise PuppetForge::InvalidPathInPackageError, :entry_path => path, :directory => destdir
|
69
|
+
end
|
70
|
+
|
71
|
+
path = File.expand_path File.join(destdir, path)
|
72
|
+
|
73
|
+
if path !~ /\A#{Regexp.escape destdir}/
|
74
|
+
raise PuppetForge::InvalidPathInPackageError, :entry_path => path, :directory => destdir
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'puppet_forge/error'
|
3
|
+
require 'puppet_forge/tar'
|
4
|
+
|
5
|
+
module PuppetForge
|
6
|
+
class Unpacker
|
7
|
+
# Unpack a tar file into a specified directory
|
8
|
+
#
|
9
|
+
# @param filename [String] the file to unpack
|
10
|
+
# @param target [String] the target directory to unpack into
|
11
|
+
# @return [Hash{:symbol => Array<String>}] a hash with file-category keys pointing to lists of filenames.
|
12
|
+
# The categories are :valid, :invalid and :symlink
|
13
|
+
def self.unpack(filename, target, tmpdir)
|
14
|
+
inst = self.new(filename, target, tmpdir)
|
15
|
+
file_lists = inst.unpack
|
16
|
+
inst.move_into(Pathname.new(target))
|
17
|
+
file_lists
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set the owner/group of the target directory to those of the source
|
21
|
+
# Note: don't call this function on Microsoft Windows
|
22
|
+
#
|
23
|
+
# @param source [Pathname] source of the permissions
|
24
|
+
# @param target [Pathname] target of the permissions change
|
25
|
+
def self.harmonize_ownership(source, target)
|
26
|
+
FileUtils.chown_R(source.stat.uid, source.stat.gid, target)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param filename [String] the file to unpack
|
30
|
+
# @param target [String] the target directory to unpack into
|
31
|
+
def initialize(filename, target, tmpdir)
|
32
|
+
@filename = filename
|
33
|
+
@target = target
|
34
|
+
@tmpdir = tmpdir
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def unpack
|
39
|
+
begin
|
40
|
+
PuppetForge::Tar.instance.unpack(@filename, @tmpdir)
|
41
|
+
rescue PuppetForge::ExecutionFailure => e
|
42
|
+
raise RuntimeError, "Could not extract contents of module archive: #{e.message}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
def move_into(dir)
|
48
|
+
dir.rmtree if dir.exist?
|
49
|
+
FileUtils.mv(root_dir, dir)
|
50
|
+
ensure
|
51
|
+
FileUtils.rmtree(@tmpdir)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
def root_dir
|
56
|
+
return @root_dir if @root_dir
|
57
|
+
|
58
|
+
# Grab the first directory containing a metadata.json file
|
59
|
+
metadata_file = Dir["#{@tmpdir}/**/metadata.json"].sort_by(&:length)[0]
|
60
|
+
|
61
|
+
if metadata_file
|
62
|
+
@root_dir = Pathname.new(metadata_file).dirname
|
63
|
+
else
|
64
|
+
raise "No valid metadata.json found!"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/puppet_forge/v3.rb
CHANGED
@@ -2,9 +2,20 @@ module PuppetForge
|
|
2
2
|
|
3
3
|
# Models specific to the Puppet Forge's v3 API.
|
4
4
|
module V3
|
5
|
+
# Normalize a module name to use a hyphen as the separator between the
|
6
|
+
# author and module.
|
7
|
+
|
8
|
+
# @example
|
9
|
+
# PuppetForge::V3.normalize_name('my/module') #=> 'my-module'
|
10
|
+
# PuppetForge::V3.normalize_name('my-module') #=> 'my-module'
|
11
|
+
def self.normalize_name(name)
|
12
|
+
name.tr('/', '-')
|
13
|
+
end
|
5
14
|
end
|
6
15
|
end
|
7
16
|
|
17
|
+
require 'puppet_forge/v3/metadata'
|
18
|
+
|
8
19
|
require 'puppet_forge/v3/user'
|
9
20
|
require 'puppet_forge/v3/module'
|
10
21
|
require 'puppet_forge/v3/release'
|
data/lib/puppet_forge/v3/base.rb
CHANGED
@@ -1,97 +1,130 @@
|
|
1
|
-
require '
|
2
|
-
require 'her/lazy_accessors'
|
3
|
-
require 'her/lazy_relations'
|
4
|
-
|
5
|
-
require 'puppet_forge/middleware/json_for_her'
|
1
|
+
require 'puppet_forge/connection'
|
6
2
|
require 'puppet_forge/v3/base/paginated_collection'
|
3
|
+
require 'puppet_forge/error'
|
4
|
+
|
5
|
+
require 'puppet_forge/lazy_accessors'
|
6
|
+
require 'puppet_forge/lazy_relations'
|
7
7
|
|
8
8
|
module PuppetForge
|
9
9
|
module V3
|
10
10
|
|
11
|
-
# Acts as the base class for all PuppetForge::V3::* models.
|
12
|
-
# some overrides of behaviors from Her, in addition to convenience methods
|
13
|
-
# and abstractions of common behavior.
|
11
|
+
# Acts as the base class for all PuppetForge::V3::* models.
|
14
12
|
#
|
15
13
|
# @api private
|
16
14
|
class Base
|
17
|
-
include
|
18
|
-
include
|
19
|
-
include Her::LazyRelations
|
20
|
-
|
21
|
-
use_api begin
|
22
|
-
begin
|
23
|
-
# Use Typhoeus if available.
|
24
|
-
Gem::Specification.find_by_name('typhoeus', '~> 0.6')
|
25
|
-
require 'typhoeus/adapters/faraday'
|
26
|
-
adapter = Faraday::Adapter::Typhoeus
|
27
|
-
rescue Gem::LoadError
|
28
|
-
adapter = Faraday::Adapter::NetHttp
|
29
|
-
end
|
15
|
+
include PuppetForge::LazyAccessors
|
16
|
+
include PuppetForge::LazyRelations
|
30
17
|
|
31
|
-
|
32
|
-
|
33
|
-
|
18
|
+
def initialize(json_response)
|
19
|
+
@attributes = json_response
|
20
|
+
orm_resp_item json_response
|
21
|
+
end
|
22
|
+
|
23
|
+
def orm_resp_item(json_response)
|
24
|
+
json_response.each do |key, value|
|
25
|
+
unless respond_to? key
|
26
|
+
define_singleton_method("#{key}") { @attributes[key] }
|
27
|
+
define_singleton_method("#{key}=") { |val| @attributes[key] = val }
|
28
|
+
end
|
34
29
|
end
|
35
30
|
end
|
36
31
|
|
32
|
+
# @return true if attribute exists, false otherwise
|
33
|
+
#
|
34
|
+
def has_attribute?(attr)
|
35
|
+
@attributes.has_key?(:"#{attr}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def attribute(name)
|
39
|
+
@attributes[:"#{name}"]
|
40
|
+
end
|
41
|
+
|
42
|
+
def attributes
|
43
|
+
@attributes
|
44
|
+
end
|
45
|
+
|
37
46
|
class << self
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
|
48
|
+
include PuppetForge::Connection
|
49
|
+
|
50
|
+
API_VERSION = "v3"
|
51
|
+
|
52
|
+
def api_version
|
53
|
+
API_VERSION
|
54
|
+
end
|
55
|
+
|
56
|
+
# @private
|
57
|
+
def request(resource, item = nil, params = {})
|
58
|
+
unless conn.url_prefix =~ /^#{PuppetForge.host}/
|
59
|
+
conn.url_prefix = "#{PuppetForge.host}"
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
if item.nil?
|
64
|
+
uri_path = "/v3/#{resource}"
|
65
|
+
else
|
66
|
+
uri_path = "/v3/#{resource}/#{item}"
|
49
67
|
end
|
50
68
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
69
|
+
PuppetForge::V3::Base.conn.get uri_path, params
|
70
|
+
end
|
71
|
+
|
72
|
+
def find(slug)
|
73
|
+
return nil if slug.nil?
|
74
|
+
|
75
|
+
resp = request("#{self.name.split("::").last.downcase}s", slug)
|
58
76
|
|
59
|
-
|
77
|
+
self.new(resp.body)
|
60
78
|
end
|
61
79
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
# @api private
|
68
|
-
# @api her
|
69
|
-
# @param parsed_data [Hash<(:data, :errors)>] the parsed response data
|
70
|
-
# @return [PaginatedCollection] the collection
|
71
|
-
def new_collection(parsed_data)
|
72
|
-
col = super :data => parsed_data[:data][:results] || [],
|
73
|
-
:metadata => parsed_data[:data][:pagination] || { limit: 10, total: 0, offset: 0 },
|
74
|
-
:errors => parsed_data[:errors]
|
75
|
-
|
76
|
-
PaginatedCollection.new(self, col.to_a, col.metadata, col.errors)
|
80
|
+
def where(params)
|
81
|
+
resp = request("#{self.name.split("::").last.downcase}s", nil, params)
|
82
|
+
|
83
|
+
new_collection(resp)
|
77
84
|
end
|
78
|
-
end
|
79
85
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
attributes[:slug] ||= uri[/[^\/]+$/]
|
85
|
-
end
|
86
|
+
# Return a paginated collection of all modules
|
87
|
+
def all(params = {})
|
88
|
+
where(params)
|
89
|
+
end
|
86
90
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
91
|
+
def get_collection(uri_path)
|
92
|
+
resource, params = split_uri_path uri_path
|
93
|
+
resp = request(resource, nil, params)
|
94
|
+
|
95
|
+
new_collection(resp)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Faraday's Util#escape method will replace a '+' with '%2B' to prevent it being
|
99
|
+
# interpreted as a space. For compatibility with the Forge API, we would like a '+'
|
100
|
+
# to be interpreted as a space so they are changed to spaces here.
|
101
|
+
def convert_plus_to_space(str)
|
102
|
+
str.gsub(/[+]/, ' ')
|
103
|
+
end
|
104
|
+
|
105
|
+
# @private
|
106
|
+
def split_uri_path(uri_path)
|
107
|
+
all, resource, params = /(?:\/v3\/)([^\/]+)(?:\?)(.*)/.match(uri_path).to_a
|
108
|
+
|
109
|
+
params = convert_plus_to_space(params).split('&')
|
110
|
+
|
111
|
+
param_hash = Hash.new
|
112
|
+
params.each do |param|
|
113
|
+
key, val = param.split('=')
|
114
|
+
param_hash[key] = val
|
115
|
+
end
|
116
|
+
|
117
|
+
[resource, param_hash]
|
118
|
+
end
|
119
|
+
|
120
|
+
# @private
|
121
|
+
def new_collection(faraday_resp)
|
122
|
+
if faraday_resp[:errors].nil?
|
123
|
+
PaginatedCollection.new(self, faraday_resp.body[:results], faraday_resp.body[:pagination], nil)
|
124
|
+
else
|
125
|
+
PaginatedCollection.new(self)
|
126
|
+
end
|
127
|
+
end
|
95
128
|
end
|
96
129
|
end
|
97
130
|
end
|