puppet_forge 4.0.0 → 5.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.
- 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
|