puppet_forge 4.0.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby-rspec.yml +2 -1
- data/CHANGELOG.md +11 -0
- data/CODEOWNERS +1 -1
- data/README.md +16 -0
- data/lib/puppet_forge/error.rb +21 -5
- data/lib/puppet_forge/lru_cache.rb +78 -0
- data/lib/puppet_forge/v3/base.rb +21 -1
- data/lib/puppet_forge/v3/module.rb +5 -0
- data/lib/puppet_forge/v3/release.rb +33 -0
- data/lib/puppet_forge/version.rb +1 -1
- data/spec/spec_helper.rb +5 -2
- data/spec/unit/forge/lru_cache_spec.rb +127 -0
- data/spec/unit/forge/v3/base_spec.rb +30 -0
- data/spec/unit/forge/v3/module_spec.rb +2 -2
- data/spec/unit/forge/v3/release_spec.rb +38 -0
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2eed3dead78d19c535ee9a2b372a89e17efcf7b24f01c752bb38096a2ed52e37
|
4
|
+
data.tar.gz: 2036388fc19159a57d425383aa6c89326d222a900f9fd61fbbb529e62d004f28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca288c45d5329d1656221858c03a1125e0e29eb1e6701b95da90baa2cd6ba254907e15fafbf4f1474b69a43014a3c9f10fbcaed620d3913d4bf221194b3a2cc5
|
7
|
+
data.tar.gz: 8f5a441289712e2cb9ef9c7d34a17cef347b2b09ff0f60fc1430cceae3780229201aecee3b0baa601a3fd343060ed1976fad247e8e54abbbfb7d5a9a37f30fc0
|
@@ -15,13 +15,14 @@ jobs:
|
|
15
15
|
strategy:
|
16
16
|
matrix:
|
17
17
|
ruby:
|
18
|
+
- '3.2'
|
18
19
|
- '3.1'
|
19
20
|
- '3.0'
|
20
21
|
- '2.7'
|
21
22
|
- '2.6'
|
22
23
|
|
23
24
|
steps:
|
24
|
-
- uses: actions/checkout@
|
25
|
+
- uses: actions/checkout@v3
|
25
26
|
- name: Set up Ruby ${{ matrix.ruby }}
|
26
27
|
uses: ruby/setup-ruby@v1
|
27
28
|
with:
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,17 @@
|
|
3
3
|
Starting with v2.0.0, all notable changes to this project will be documented in this file.
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
5
5
|
|
6
|
+
## v5.0.0 - 2023-05-07
|
7
|
+
|
8
|
+
* Ruby 3.2 support.
|
9
|
+
* LRU caching for HTTP response caching.
|
10
|
+
* Raise a ModuleNotFound error instead of just nil when a module is not found.
|
11
|
+
|
12
|
+
## v4.1.0 - 2023-02-21
|
13
|
+
|
14
|
+
* Add upload method functionality.
|
15
|
+
* Allows the user to search by an array of endorsements.
|
16
|
+
|
6
17
|
## v4.0.0 - 2022-11-30
|
7
18
|
|
8
19
|
* Breaking: The `puppet_forge` gem now requires at least Ruby 2.6.0
|
data/CODEOWNERS
CHANGED
@@ -1 +1 @@
|
|
1
|
-
* @puppetlabs/forge-team
|
1
|
+
* @puppetlabs/forge-team @bastelfreak
|
data/README.md
CHANGED
@@ -136,7 +136,23 @@ release.verify(Pathname(release_tarball))
|
|
136
136
|
# @raise RuntimeError if it fails to extract the contents of the release tarball
|
137
137
|
PuppetForge::Unpacker.unpack(release_tarball, dest_dir, tmp_dir)
|
138
138
|
```
|
139
|
+
### Uploading a module release
|
139
140
|
|
141
|
+
You can upload new module versions to the forge by following the steps below.
|
142
|
+
|
143
|
+
> Note: This API requires authorization. See [Authorization](#authorization) for more information.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
release_tarball = 'pkg/puppetlabs-apache-1.6.0.tar.gz'
|
147
|
+
|
148
|
+
# Upload a module tarball to the Puppet Forge
|
149
|
+
# Returns an instance of V3::Release class and the response from the forge upload
|
150
|
+
# @raise PuppetForge::ReleaseForbidden if a 403 response is recieved from the server
|
151
|
+
# @raise PuppetForge::ReleaseBadContent if the module to upload is not valid
|
152
|
+
# @raise Faraday::ClientError if any errors encountered in the upload
|
153
|
+
# @raise PuppetForge::FileNotFound if the given tarball cannot be found
|
154
|
+
release, response = PuppetForge::V3::Release.upload(release_tarball)
|
155
|
+
```
|
140
156
|
|
141
157
|
### Paginated Collections
|
142
158
|
|
data/lib/puppet_forge/error.rb
CHANGED
@@ -28,16 +28,32 @@ Could not install package
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
|
32
|
+
class ErrorWithDetail < PuppetForge::Error
|
33
|
+
def self.from_response(response)
|
34
|
+
body = JSON.parse(response[:body])
|
35
|
+
|
36
|
+
message = body['message']
|
37
|
+
if body.key?('errors') && !body['errors']&.empty?
|
38
|
+
message << "\nThe following errors were returned from the server:\n - #{body['errors'].join("\n - ")}"
|
39
|
+
end
|
40
|
+
|
41
|
+
new(message)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class FileNotFound < PuppetForge::Error
|
46
|
+
end
|
47
|
+
|
31
48
|
class ModuleNotFound < PuppetForge::Error
|
32
49
|
end
|
33
50
|
|
34
51
|
class ReleaseNotFound < PuppetForge::Error
|
35
52
|
end
|
36
53
|
|
37
|
-
class ReleaseForbidden < PuppetForge::
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
54
|
+
class ReleaseForbidden < PuppetForge::ErrorWithDetail
|
55
|
+
end
|
56
|
+
|
57
|
+
class ReleaseBadContent < PuppetForge::ErrorWithDetail
|
42
58
|
end
|
43
59
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module PuppetForge
|
4
|
+
# Implements a simple LRU cache. This is used internally by the
|
5
|
+
# {PuppetForge::V3::Base} class to cache API responses.
|
6
|
+
class LruCache
|
7
|
+
# Takes a list of strings (or objects that respond to #to_s) and
|
8
|
+
# returns a SHA256 hash of the strings joined with colons. This is
|
9
|
+
# a convenience method for generating cache keys. Cache keys do not
|
10
|
+
# have to be SHA256 hashes, but they must be unique.
|
11
|
+
def self.new_key(*string_args)
|
12
|
+
Digest::SHA256.hexdigest(string_args.map(&:to_s).join(':'))
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Integer] the maximum number of items to cache.
|
16
|
+
attr_reader :max_size
|
17
|
+
|
18
|
+
# @param max_size [Integer] the maximum number of items to cache. This can
|
19
|
+
# be overridden by setting the PUPPET_FORGE_MAX_CACHE_SIZE environment
|
20
|
+
# variable.
|
21
|
+
def initialize(max_size = 30)
|
22
|
+
raise ArgumentError, "max_size must be a positive integer" unless max_size.is_a?(Integer) && max_size > 0
|
23
|
+
|
24
|
+
@max_size = ENV['PUPPET_FORGE_MAX_CACHE_SIZE'] ? ENV['PUPPET_FORGE_MAX_CACHE_SIZE'].to_i : max_size
|
25
|
+
@cache = {}
|
26
|
+
@lru = []
|
27
|
+
@semaphore = Mutex.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Retrieves a value from the cache.
|
31
|
+
# @param key [Object] the key to look up in the cache
|
32
|
+
# @return [Object] the cached value for the given key, or nil if
|
33
|
+
# the key is not present in the cache.
|
34
|
+
def get(key)
|
35
|
+
if cache.key?(key)
|
36
|
+
semaphore.synchronize do
|
37
|
+
# If the key is present, move it to the front of the LRU
|
38
|
+
# list.
|
39
|
+
lru.delete(key)
|
40
|
+
lru.unshift(key)
|
41
|
+
end
|
42
|
+
cache[key]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Adds a value to the cache.
|
47
|
+
# @param key [Object] the key to add to the cache
|
48
|
+
# @param value [Object] the value to add to the cache
|
49
|
+
def put(key, value)
|
50
|
+
semaphore.synchronize do
|
51
|
+
if cache.key?(key)
|
52
|
+
# If the key is already present, delete it from the LRU list.
|
53
|
+
lru.delete(key)
|
54
|
+
elsif cache.size >= max_size
|
55
|
+
# If the cache is full, remove the least recently used item.
|
56
|
+
cache.delete(lru.pop)
|
57
|
+
end
|
58
|
+
# Add the key to the front of the LRU list and add the value
|
59
|
+
# to the cache.
|
60
|
+
lru.unshift(key)
|
61
|
+
cache[key] = value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Clears the cache.
|
66
|
+
def clear
|
67
|
+
semaphore.synchronize do
|
68
|
+
cache.clear
|
69
|
+
lru.clear
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Makes testing easier as these can be accessed directly with #send.
|
76
|
+
attr_reader :cache, :lru, :semaphore
|
77
|
+
end
|
78
|
+
end
|
data/lib/puppet_forge/v3/base.rb
CHANGED
@@ -4,6 +4,7 @@ require 'puppet_forge/error'
|
|
4
4
|
|
5
5
|
require 'puppet_forge/lazy_accessors'
|
6
6
|
require 'puppet_forge/lazy_relations'
|
7
|
+
require 'puppet_forge/lru_cache'
|
7
8
|
|
8
9
|
module PuppetForge
|
9
10
|
module V3
|
@@ -53,8 +54,22 @@ module PuppetForge
|
|
53
54
|
API_VERSION
|
54
55
|
end
|
55
56
|
|
57
|
+
# @private
|
58
|
+
def lru_cache
|
59
|
+
@lru_cache ||= PuppetForge::LruCache.new
|
60
|
+
end
|
61
|
+
|
62
|
+
# @private
|
63
|
+
def lru_cache_key(*args)
|
64
|
+
PuppetForge::LruCache.new_key(*args)
|
65
|
+
end
|
66
|
+
|
56
67
|
# @private
|
57
68
|
def request(resource, item = nil, params = {}, reset_connection = false, conn_opts = {})
|
69
|
+
cache_key = lru_cache_key(resource, item, params)
|
70
|
+
cached = lru_cache.get(cache_key)
|
71
|
+
return cached unless cached.nil?
|
72
|
+
|
58
73
|
conn(reset_connection, conn_opts) if reset_connection
|
59
74
|
unless conn.url_prefix.to_s =~ /^#{PuppetForge.host}/
|
60
75
|
conn.url_prefix = "#{PuppetForge.host}"
|
@@ -66,7 +81,12 @@ module PuppetForge
|
|
66
81
|
uri_path = "v3/#{resource}/#{item}"
|
67
82
|
end
|
68
83
|
|
69
|
-
|
84
|
+
# The API expects a space separated string. This allows the user to invoke it with a more natural feeling array.
|
85
|
+
params['endorsements'] = params['endorsements'].join(' ') if params['endorsements'].is_a? Array
|
86
|
+
|
87
|
+
result = PuppetForge::V3::Base.conn.get uri_path, params
|
88
|
+
lru_cache.put(cache_key, result)
|
89
|
+
result
|
70
90
|
end
|
71
91
|
|
72
92
|
# @private
|
@@ -11,6 +11,11 @@ module PuppetForge
|
|
11
11
|
lazy :current_release, 'Release'
|
12
12
|
lazy_collection :releases, 'Release'
|
13
13
|
|
14
|
+
def self.find(slug)
|
15
|
+
super
|
16
|
+
rescue Faraday::ResourceNotFound
|
17
|
+
raise PuppetForge::ModuleNotFound, "Module #{slug} not found"
|
18
|
+
end
|
14
19
|
end
|
15
20
|
end
|
16
21
|
end
|
@@ -2,6 +2,7 @@ require 'puppet_forge/v3/base'
|
|
2
2
|
require 'puppet_forge/v3/module'
|
3
3
|
|
4
4
|
require 'digest'
|
5
|
+
require 'base64'
|
5
6
|
|
6
7
|
module PuppetForge
|
7
8
|
module V3
|
@@ -38,6 +39,38 @@ module PuppetForge
|
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
42
|
+
# Uploads the tarbarll to the forge
|
43
|
+
#
|
44
|
+
# @param path [Pathname] tarball file path
|
45
|
+
# @return resp
|
46
|
+
def self.upload(path)
|
47
|
+
# We want to make sure that the file exists before trying to upload it
|
48
|
+
raise PuppetForge::FileNotFound, "The file '#{path}' does not exist." unless File.file?(path)
|
49
|
+
|
50
|
+
file = File.open(path, 'rb')
|
51
|
+
encoded_string = Base64.encode64(file.read)
|
52
|
+
data = { file: encoded_string }
|
53
|
+
|
54
|
+
resp = conn.post do |req|
|
55
|
+
req.url '/v3/releases'
|
56
|
+
req.headers['Content-Type'] = 'application/json'
|
57
|
+
req.body = data.to_json
|
58
|
+
end
|
59
|
+
|
60
|
+
[self, resp]
|
61
|
+
rescue Faraday::ClientError => e
|
62
|
+
if e.response
|
63
|
+
case e.response[:status]
|
64
|
+
when 403
|
65
|
+
raise PuppetForge::ReleaseForbidden.from_response(e.response)
|
66
|
+
when 400
|
67
|
+
raise PuppetForge::ReleaseBadContent.from_response(e.response)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
raise e
|
72
|
+
end
|
73
|
+
|
41
74
|
# Verify that a downloaded module matches the best available checksum in the metadata for this release,
|
42
75
|
# validates SHA-256 checksum if available, otherwise validates MD5 checksum
|
43
76
|
#
|
data/lib/puppet_forge/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -12,7 +12,10 @@ require 'puppet_forge'
|
|
12
12
|
|
13
13
|
module StubbingFaraday
|
14
14
|
|
15
|
-
def stub_api_for(klass, base_url = "http://api.example.com")
|
15
|
+
def stub_api_for(klass, base_url = "http://api.example.com", lru_cache: false)
|
16
|
+
unless lru_cache # Disable LRU cache by default
|
17
|
+
allow(klass).to receive(:lru_cache).and_return(instance_double('PuppetForge::LruCache', get: nil, put: nil, clear: nil))
|
18
|
+
end
|
16
19
|
allow(klass).to receive(:conn) do
|
17
20
|
Faraday.new :url => base_url do |builder|
|
18
21
|
builder.response(:json, :content_type => /\bjson$/, :parser_options => { :symbolize_names => true })
|
@@ -38,7 +41,7 @@ module StubbingFaraday
|
|
38
41
|
[ 404 ].tap do |response|
|
39
42
|
local = File.join(PROJECT_ROOT, 'spec', 'fixtures', xplatform_path)
|
40
43
|
|
41
|
-
if File.
|
44
|
+
if File.exist?("#{local}.headers") && File.exist?("#{local}.json")
|
42
45
|
File.open("#{local}.headers") do |file|
|
43
46
|
response[0] = file.readline[/\d{3}/].to_i
|
44
47
|
response[1] = headers = {}
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PuppetForge::LruCache do
|
4
|
+
it 'creates a cache key from a list of strings' do
|
5
|
+
expect { subject.class.new_key('foo', 'bar', 'baz') }.not_to raise_error
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'creates a new instance' do
|
9
|
+
expect { PuppetForge::LruCache.new(1) }.not_to raise_error
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'raises an error if max_size is not a positive integer' do
|
13
|
+
expect { PuppetForge::LruCache.new(-1) }.to raise_error(ArgumentError)
|
14
|
+
expect { PuppetForge::LruCache.new(0) }.to raise_error(ArgumentError)
|
15
|
+
expect { PuppetForge::LruCache.new(1.5) }.to raise_error(ArgumentError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'defaults to a max_size of 30' do
|
19
|
+
expect(PuppetForge::LruCache.new.max_size).to eq(30)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'allows max_size to be set via the max_size parameter' do
|
23
|
+
expect(PuppetForge::LruCache.new(42).max_size).to eq(42)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'provides a #get method' do
|
27
|
+
expect(PuppetForge::LruCache.new).to respond_to(:get)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'provides a #put method' do
|
31
|
+
expect(PuppetForge::LruCache.new).to respond_to(:put)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'provides a #clear method' do
|
35
|
+
expect(PuppetForge::LruCache.new).to respond_to(:clear)
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with environment variables' do
|
39
|
+
around(:each) do |example|
|
40
|
+
@old_max_size = ENV['PUPPET_FORGE_MAX_CACHE_SIZE']
|
41
|
+
ENV['PUPPET_FORGE_MAX_CACHE_SIZE'] = '42'
|
42
|
+
example.run
|
43
|
+
ENV['PUPPET_FORGE_MAX_CACHE_SIZE'] = @old_max_size
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'uses the value of the PUPPET_FORGE_MAX_CACHE_SIZE environment variable if present' do
|
47
|
+
expect(PuppetForge::LruCache.new.max_size).to eq(42)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context '#get' do
|
52
|
+
it 'returns nil if the key is not present in the cache' do
|
53
|
+
expect(PuppetForge::LruCache.new.get('foo')).to be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns the cached value for the given key' do
|
57
|
+
cache = PuppetForge::LruCache.new
|
58
|
+
cache.put('foo', 'bar')
|
59
|
+
expect(cache.get('foo')).to eq('bar')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'moves the key to the front of the LRU list' do
|
63
|
+
cache = PuppetForge::LruCache.new
|
64
|
+
cache.put('foo', 'bar')
|
65
|
+
cache.put('baz', 'qux')
|
66
|
+
cache.get('foo')
|
67
|
+
expect(cache.send(:lru)).to eq(['foo', 'baz'])
|
68
|
+
end
|
69
|
+
|
70
|
+
# The below test is non-deterministic but I'm not sure how to unit
|
71
|
+
# test thread-safety.
|
72
|
+
# it 'is thread-safe' do
|
73
|
+
# cache = PuppetForge::LruCache.new
|
74
|
+
# cache.put('foo', 'bar')
|
75
|
+
# cache.put('baz', 'qux')
|
76
|
+
# threads = []
|
77
|
+
# threads << Thread.new { 100.times { cache.get('foo') } }
|
78
|
+
# threads << Thread.new { 100.times { cache.get('baz') } }
|
79
|
+
# threads.each(&:join)
|
80
|
+
# expect(cache.send(:lru)).to eq(['baz', 'foo'])
|
81
|
+
# end
|
82
|
+
end
|
83
|
+
|
84
|
+
context '#put' do
|
85
|
+
it 'adds the key to the front of the LRU list' do
|
86
|
+
cache = PuppetForge::LruCache.new
|
87
|
+
cache.put('foo', 'bar')
|
88
|
+
expect(cache.send(:lru)).to eq(['foo'])
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'adds the value to the cache' do
|
92
|
+
cache = PuppetForge::LruCache.new
|
93
|
+
cache.put('foo', 'bar')
|
94
|
+
expect(cache.send(:cache)).to eq({ 'foo' => 'bar' })
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'removes the least recently used item if the cache is full' do
|
98
|
+
cache = PuppetForge::LruCache.new(2)
|
99
|
+
cache.put('foo', 'bar')
|
100
|
+
cache.put('baz', 'qux')
|
101
|
+
cache.put('quux', 'corge')
|
102
|
+
expect(cache.send(:lru)).to eq(['quux', 'baz'])
|
103
|
+
end
|
104
|
+
|
105
|
+
# The below test is non-deterministic but I'm not sure how to unit
|
106
|
+
# test thread-safety.
|
107
|
+
# it 'is thread-safe' do
|
108
|
+
# cache = PuppetForge::LruCache.new
|
109
|
+
# threads = []
|
110
|
+
# threads << Thread.new { 100.times { cache.put('foo', 'bar') } }
|
111
|
+
# threads << Thread.new { 100.times { cache.put('baz', 'qux') } }
|
112
|
+
# threads.each(&:join)
|
113
|
+
# expect(cache.send(:lru)).to eq(['baz', 'foo'])
|
114
|
+
# end
|
115
|
+
end
|
116
|
+
|
117
|
+
context '#clear' do
|
118
|
+
it 'clears the cache' do
|
119
|
+
cache = PuppetForge::LruCache.new
|
120
|
+
cache.put('foo', 'bar')
|
121
|
+
cache.put('baz', 'qux')
|
122
|
+
cache.clear
|
123
|
+
expect(cache.send(:lru).empty?).to be_truthy
|
124
|
+
expect(cache.send(:cache).empty?).to be_truthy
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -61,6 +61,7 @@ describe PuppetForge::V3::Base do
|
|
61
61
|
describe 'the host url setting' do
|
62
62
|
context 'without a path prefix' do
|
63
63
|
before(:each) do
|
64
|
+
PuppetForge::V3::Base.lru_cache.clear # We test the cache later, so clear it now
|
64
65
|
@orig_host = PuppetForge.host
|
65
66
|
PuppetForge.host = 'https://api.example.com'
|
66
67
|
|
@@ -83,10 +84,25 @@ describe PuppetForge::V3::Base do
|
|
83
84
|
base = PuppetForge::V3::Base.find 'puppet'
|
84
85
|
expect(base.username).to eq('foo')
|
85
86
|
end
|
87
|
+
|
88
|
+
it 'caches responses' do
|
89
|
+
stub_api_for(PuppetForge::V3::Base, lru_cache: true) do |stubs|
|
90
|
+
stub_fixture(stubs, :get, '/v3/bases/puppet')
|
91
|
+
end
|
92
|
+
allow(PuppetForge::V3::Base.lru_cache).to receive(:put).and_call_original
|
93
|
+
allow(PuppetForge::V3::Base.lru_cache).to receive(:get).and_call_original
|
94
|
+
|
95
|
+
PuppetForge::V3::Base.find 'puppet'
|
96
|
+
PuppetForge::V3::Base.find 'puppet'
|
97
|
+
PuppetForge::V3::Base.find 'puppet'
|
98
|
+
expect(PuppetForge::V3::Base.lru_cache).to have_received(:put).once
|
99
|
+
expect(PuppetForge::V3::Base.lru_cache).to have_received(:get).exactly(3).times
|
100
|
+
end
|
86
101
|
end
|
87
102
|
|
88
103
|
context 'with a path prefix' do
|
89
104
|
before(:each) do
|
105
|
+
PuppetForge::V3::Base.lru_cache.clear # We test the cache later, so clear it now
|
90
106
|
@orig_host = PuppetForge.host
|
91
107
|
PuppetForge.host = 'https://api.example.com/uri/prefix'
|
92
108
|
|
@@ -109,6 +125,20 @@ describe PuppetForge::V3::Base do
|
|
109
125
|
base = PuppetForge::V3::Base.find 'puppet'
|
110
126
|
expect(base.username).to eq('bar')
|
111
127
|
end
|
128
|
+
|
129
|
+
it 'caches responses' do
|
130
|
+
stub_api_for(PuppetForge::V3::Base, PuppetForge.host, lru_cache: true) do |stubs|
|
131
|
+
stub_fixture(stubs, :get, '/uri/prefix/v3/bases/puppet')
|
132
|
+
end
|
133
|
+
allow(PuppetForge::V3::Base.lru_cache).to receive(:put).and_call_original
|
134
|
+
allow(PuppetForge::V3::Base.lru_cache).to receive(:get).and_call_original
|
135
|
+
|
136
|
+
PuppetForge::V3::Base.find 'puppet'
|
137
|
+
PuppetForge::V3::Base.find 'puppet'
|
138
|
+
PuppetForge::V3::Base.find 'puppet'
|
139
|
+
expect(PuppetForge::V3::Base.lru_cache).to have_received(:put).once
|
140
|
+
expect(PuppetForge::V3::Base.lru_cache).to have_received(:get).exactly(3).times
|
141
|
+
end
|
112
142
|
end
|
113
143
|
end
|
114
144
|
end
|
@@ -28,8 +28,8 @@ describe PuppetForge::V3::Module do
|
|
28
28
|
expect(mod_stateless.name).to eq('apache')
|
29
29
|
end
|
30
30
|
|
31
|
-
it '
|
32
|
-
expect { missing_mod }.to raise_error(
|
31
|
+
it 'raises exception for non-existent modules' do
|
32
|
+
expect { missing_mod }.to raise_error(PuppetForge::ModuleNotFound, 'Module absent-apache not found')
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -218,5 +218,43 @@ describe PuppetForge::V3::Release do
|
|
218
218
|
expect(release.created_at).to_not be nil
|
219
219
|
end
|
220
220
|
end
|
221
|
+
|
222
|
+
describe '#upload' do
|
223
|
+
let(:tarball) { "#{PROJECT_ROOT}/spec/tmp/module.tgz" }
|
224
|
+
let(:file_object) { double('file', read: 'file contents') }
|
225
|
+
|
226
|
+
let(:release) { PuppetForge::V3::Release.upload(tarball) }
|
227
|
+
let(:mock_conn) { instance_double('PuppetForge::V3::Connection', url_prefix: PuppetForge.host) }
|
228
|
+
|
229
|
+
context 'when there is no auth token provided' do
|
230
|
+
it 'raises PuppetForge::ReleaseForbidden' do
|
231
|
+
allow(File).to receive(:file?).and_return(true)
|
232
|
+
allow(File).to receive(:open).and_return(file_object)
|
233
|
+
allow(described_class).to receive(:conn).and_return(mock_conn)
|
234
|
+
|
235
|
+
response = { status: 403, body: { 'message' => 'Forbidden' }.to_json }
|
236
|
+
expect(mock_conn).to receive(:post).and_raise(Faraday::ClientError.new('Forbidden', response))
|
237
|
+
expect { release }.to raise_error(PuppetForge::ReleaseForbidden)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
context 'when the module is not valid' do
|
242
|
+
it 'raises PuppetForge::ReleaseBadRequest' do
|
243
|
+
allow(File).to receive(:file?).and_return(true)
|
244
|
+
allow(File).to receive(:open).and_return(file_object)
|
245
|
+
allow(described_class).to receive(:conn).and_return(mock_conn)
|
246
|
+
|
247
|
+
response = { status: 400, body: { message: 'Bad Content' }.to_json }
|
248
|
+
expect(mock_conn).to receive(:post).and_raise(Faraday::ClientError.new('400', response))
|
249
|
+
expect { release }.to raise_error(PuppetForge::ReleaseBadContent)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context 'when the tarball does not exist' do
|
254
|
+
it 'raises PuppetForge::FileNotFound' do
|
255
|
+
expect { PuppetForge::V3::Release.upload(tarball) }.to raise_error(PuppetForge::FileNotFound)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
221
259
|
end
|
222
260
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puppet_forge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet Labs
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -187,6 +187,7 @@ files:
|
|
187
187
|
- lib/puppet_forge/error.rb
|
188
188
|
- lib/puppet_forge/lazy_accessors.rb
|
189
189
|
- lib/puppet_forge/lazy_relations.rb
|
190
|
+
- lib/puppet_forge/lru_cache.rb
|
190
191
|
- lib/puppet_forge/tar.rb
|
191
192
|
- lib/puppet_forge/tar/mini.rb
|
192
193
|
- lib/puppet_forge/unpacker.rb
|
@@ -236,6 +237,7 @@ files:
|
|
236
237
|
- spec/unit/forge/connection_spec.rb
|
237
238
|
- spec/unit/forge/lazy_accessors_spec.rb
|
238
239
|
- spec/unit/forge/lazy_relations_spec.rb
|
240
|
+
- spec/unit/forge/lru_cache_spec.rb
|
239
241
|
- spec/unit/forge/tar/mini_spec.rb
|
240
242
|
- spec/unit/forge/tar_spec.rb
|
241
243
|
- spec/unit/forge/unpacker_spec.rb
|
@@ -251,7 +253,7 @@ homepage: https://github.com/puppetlabs/forge-ruby
|
|
251
253
|
licenses:
|
252
254
|
- Apache-2.0
|
253
255
|
metadata: {}
|
254
|
-
post_install_message:
|
256
|
+
post_install_message:
|
255
257
|
rdoc_options: []
|
256
258
|
require_paths:
|
257
259
|
- lib
|
@@ -267,7 +269,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
267
269
|
version: '0'
|
268
270
|
requirements: []
|
269
271
|
rubygems_version: 3.1.6
|
270
|
-
signing_key:
|
272
|
+
signing_key:
|
271
273
|
specification_version: 4
|
272
274
|
summary: Access the Puppet Forge API from Ruby for resource information and to download
|
273
275
|
releases.
|
@@ -308,6 +310,7 @@ test_files:
|
|
308
310
|
- spec/unit/forge/connection_spec.rb
|
309
311
|
- spec/unit/forge/lazy_accessors_spec.rb
|
310
312
|
- spec/unit/forge/lazy_relations_spec.rb
|
313
|
+
- spec/unit/forge/lru_cache_spec.rb
|
311
314
|
- spec/unit/forge/tar/mini_spec.rb
|
312
315
|
- spec/unit/forge/tar_spec.rb
|
313
316
|
- spec/unit/forge/unpacker_spec.rb
|