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.
Files changed (45) hide show
  1. data/CHANGELOG.md +23 -0
  2. data/MAINTAINERS +13 -0
  3. data/README.md +48 -6
  4. data/lib/puppet_forge.rb +4 -0
  5. data/lib/puppet_forge/connection.rb +81 -0
  6. data/lib/puppet_forge/connection/connection_failure.rb +26 -0
  7. data/lib/puppet_forge/error.rb +34 -0
  8. data/lib/{her → puppet_forge}/lazy_accessors.rb +20 -27
  9. data/lib/{her → puppet_forge}/lazy_relations.rb +28 -9
  10. data/lib/puppet_forge/middleware/symbolify_json.rb +72 -0
  11. data/lib/puppet_forge/tar.rb +10 -0
  12. data/lib/puppet_forge/tar/mini.rb +81 -0
  13. data/lib/puppet_forge/unpacker.rb +68 -0
  14. data/lib/puppet_forge/v3.rb +11 -0
  15. data/lib/puppet_forge/v3/base.rb +106 -73
  16. data/lib/puppet_forge/v3/base/paginated_collection.rb +23 -14
  17. data/lib/puppet_forge/v3/metadata.rb +197 -0
  18. data/lib/puppet_forge/v3/module.rb +2 -1
  19. data/lib/puppet_forge/v3/release.rb +33 -8
  20. data/lib/puppet_forge/v3/user.rb +2 -0
  21. data/lib/puppet_forge/version.rb +1 -1
  22. data/puppet_forge.gemspec +6 -3
  23. data/spec/fixtures/v3/modules/puppetlabs-apache.json +21 -1
  24. data/spec/fixtures/v3/releases/puppetlabs-apache-0.0.1.json +4 -1
  25. data/spec/integration/forge/v3/module_spec.rb +79 -0
  26. data/spec/integration/forge/v3/release_spec.rb +75 -0
  27. data/spec/integration/forge/v3/user_spec.rb +70 -0
  28. data/spec/spec_helper.rb +15 -8
  29. data/spec/unit/forge/connection/connection_failure_spec.rb +30 -0
  30. data/spec/unit/forge/connection_spec.rb +53 -0
  31. data/spec/unit/{her → forge}/lazy_accessors_spec.rb +20 -13
  32. data/spec/unit/{her → forge}/lazy_relations_spec.rb +60 -46
  33. data/spec/unit/forge/middleware/symbolify_json_spec.rb +63 -0
  34. data/spec/unit/forge/tar/mini_spec.rb +85 -0
  35. data/spec/unit/forge/tar_spec.rb +9 -0
  36. data/spec/unit/forge/unpacker_spec.rb +58 -0
  37. data/spec/unit/forge/v3/base/paginated_collection_spec.rb +68 -46
  38. data/spec/unit/forge/v3/base_spec.rb +1 -1
  39. data/spec/unit/forge/v3/metadata_spec.rb +300 -0
  40. data/spec/unit/forge/v3/module_spec.rb +14 -36
  41. data/spec/unit/forge/v3/release_spec.rb +9 -30
  42. data/spec/unit/forge/v3/user_spec.rb +7 -7
  43. metadata +127 -41
  44. checksums.yaml +0 -7
  45. data/lib/puppet_forge/middleware/json_for_her.rb +0 -37
@@ -0,0 +1,23 @@
1
+ # Change Log
2
+
3
+ Starting with v2.0.0, all notable changes to this project will be documented in this file.
4
+ This project adheres to [Semantic Versioning](http://semver.org/).i
5
+
6
+ ## v2.0.0 - 2015-08-13
7
+
8
+ ### Added
9
+
10
+ * PuppetForge::V3::Release can now verify the md5, unpack, and install a release tarball.
11
+ * PuppetForge::Middleware::SymbolifyJson to change Faraday response hash keys into symbols.
12
+ * PuppetForge::V3::Metadata to represent a release's metadata as an object.
13
+ * PuppetForge::Connection to provide Faraday connections.
14
+
15
+ ### Changed
16
+
17
+ * Failed API requests, such as those for a module that doesn't exist, throw a Faraday::ResourceNotFound error.
18
+ * API requests are sent through Faraday directly rather than through Her.
19
+ * PuppetForge::V3::Base#where and PuppetForge::V3::Base#all now send an API request immediately and return a paginated collection.
20
+
21
+ ### Removed
22
+
23
+ * Depency on Her (also removes dependency on ActiveSupport).
@@ -0,0 +1,13 @@
1
+ If you'd like to make a change to this library, you should contact at least one
2
+ of the maintainers listed below. They will be able to discuss the change with
3
+ you and decide the next best step. The maintainers will also be notified of pull
4
+ requests as described in the README. This maintainers file should be kept up to
5
+ date with the current maintainers of the project.
6
+
7
+ If you need to contact the maintainers of this project you should email
8
+ info@puppetlabs.com.
9
+
10
+ Alex Dreyer, Puppet Labs
11
+ Anderson Mills, Puppet Labs
12
+ Jesse Scott, Puppet Labs
13
+
data/README.md CHANGED
@@ -23,8 +23,8 @@ Or install it yourself as:
23
23
 
24
24
  ##Dependencies
25
25
 
26
- * [Her](http://her-rb.org) ~> 0.6
27
- * [Typhoeus](https://github.com/typhoeus/typhoeus) ~> 0.6 (optional)
26
+ * [Faraday]() ~> 0.9.0
27
+ * [Typhoeus](https://github.com/typhoeus/typhoeus) ~> 0.7.0 (optional)
28
28
 
29
29
  Typhoeus will be used as the HTTP adapter if it is available, otherwise
30
30
  Net::HTTP will be used. We recommend using Typhoeus for production-level
@@ -78,12 +78,13 @@ resource model are documented on the [Resource Reference][resource_ref] page.
78
78
 
79
79
  ###Basic Interface
80
80
 
81
- Each of the models uses [Her](http://her-rb.org) (an ActiveRecord-like REST
82
- library) to map over the Forge API endpoints. Most simple ActiveRecord-style
83
- interactions function as intended.
81
+ Each of the models uses ActiveRecord-like REST functionality to map over the Forge API endpoints.
82
+ Most simple ActiveRecord-style interactions function as intended.
84
83
 
85
84
  Currently, only unauthenticated read-only actions are supported.
86
85
 
86
+ The methods find, where, and all immediately make one API request.
87
+
87
88
  ``` ruby
88
89
  # Find a Resource by Slug
89
90
  PuppetForge::User.find('puppetlabs') # => #<Forge::V3::User(/v3/users/puppetlabs)>
@@ -92,10 +93,50 @@ PuppetForge::User.find('puppetlabs') # => #<Forge::V3::User(/v3/users/puppetlabs
92
93
  PuppetForge::Module.all # See "Paginated Collections" below for important info about enumerating resource sets.
93
94
 
94
95
  # Find Resources with Conditions
95
- PuppetForge::Module.where(query: 'apache').all # See "Paginated Collections" below for important info about enumerating resource sets.
96
+ PuppetForge::Module.where(query: 'apache') # See "Paginated Collections" below for important info about enumerating resource sets.
96
97
  PuppetForge::Module.where(query: 'apache').first # => #<Forge::V3::Module(/v3/modules/puppetlabs-apache)>
97
98
  ```
98
99
 
100
+ For compatibility with older versions of the puppet_forge gem, the following two methods are functionally equivalent.
101
+
102
+ ``` ruby
103
+ PuppetForge::Module.where(query: 'apache')
104
+ PuppetForge::Module.where(query: 'apache').all # This method is deprecated and not recommended
105
+ ```
106
+
107
+ ####Errors
108
+
109
+ All API Requests (whether via find, where, or all) will raise a Faraday::ResourceNotFound error if the request fails.
110
+
111
+
112
+ ###Installing a Release
113
+
114
+ A release tarball can be downloaded and installed by following the steps below.
115
+
116
+ ``` ruby
117
+ release_slug = "puppetlabs-apache-1.6.0"
118
+ release_tarball = release_slug + ".tar.gz"
119
+ dest_dir = "/path/to/install/directory"
120
+ tmp_dir = "/path/to/tmpdir"
121
+
122
+ # Fetch Release information from API
123
+ # @raise Faraday::ResourceNotFound error if the given release does not exist
124
+ release = PuppetForge::Release.find release_slug
125
+
126
+ # Download the Release tarball
127
+ # @raise PuppetForge::ReleaseNotFound error if the given release does not exist
128
+ release.download(Pathname(release_tarball))
129
+
130
+ # Verify the MD5
131
+ # @raise PuppetForge::V3::Release::ChecksumMismatch error if the file's md5 does not match the API information
132
+ release.verify(Pathname(release_tarball))
133
+
134
+ # Unpack the files to a given directory
135
+ # @raise RuntimeError if it fails to extract the contents of the release tarball
136
+ PuppetForge::Unpacker.unpack(release_tarball, dest_dir, tmp_dir)
137
+ ```
138
+
139
+
99
140
  ###Paginated Collections
100
141
 
101
142
  The Forge API only returns paginated collections as of v3.
@@ -170,3 +211,4 @@ to create a free account to add new tickets.
170
211
 
171
212
  * Pieter van de Bruggen, Puppet Labs
172
213
  * Jesse Scott, Puppet Labs
214
+ * Austin Blatt, Puppet Labs
@@ -8,8 +8,12 @@ module PuppetForge
8
8
 
9
9
  self.host = 'https://forgeapi.puppetlabs.com'
10
10
 
11
+ require 'puppet_forge/tar'
12
+ require 'puppet_forge/unpacker'
11
13
  require 'puppet_forge/v3'
12
14
 
15
+ const_set :Metadata, PuppetForge::V3::Metadata
16
+
13
17
  const_set :User, PuppetForge::V3::User
14
18
  const_set :Module, PuppetForge::V3::Module
15
19
  const_set :Release, PuppetForge::V3::Release
@@ -0,0 +1,81 @@
1
+ require 'puppet_forge/connection/connection_failure'
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ require 'puppet_forge/middleware/symbolify_json'
6
+
7
+ module PuppetForge
8
+ # Provide a common mixin for adding a HTTP connection to classes.
9
+ #
10
+ # This module provides a common method for creating HTTP connections as well
11
+ # as reusing a single connection object between multiple classes. Including
12
+ # classes can invoke #conn to get a reasonably configured HTTP connection.
13
+ # Connection objects can be passed with the #conn= method.
14
+ #
15
+ # @example
16
+ # class HTTPThing
17
+ # include PuppetForge::Connection
18
+ # end
19
+ # thing = HTTPThing.new
20
+ # thing.conn = thing.make_connection('https://non-standard-forge.site')
21
+ #
22
+ # @api private
23
+ module Connection
24
+
25
+ attr_writer :conn
26
+
27
+ USER_AGENT = "#{PuppetForge.user_agent} PuppetForge.gem/#{PuppetForge::VERSION} Faraday/#{Faraday::VERSION} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})".strip
28
+
29
+ def self.authorization=(token)
30
+ @authorization = token
31
+ end
32
+
33
+ def self.authorization
34
+ @authorization
35
+ end
36
+
37
+ # @return [Faraday::Connection] An existing Faraday connection if one was
38
+ # already set, otherwise a new Faraday connection.
39
+ def conn
40
+ @conn ||= default_connection
41
+ end
42
+
43
+ def default_connection
44
+
45
+ begin
46
+ # Use Typhoeus if available.
47
+ Gem::Specification.find_by_name('typhoeus', '~> 0.6')
48
+ require 'typhoeus/adapters/faraday'
49
+ adapter = :typhoeus
50
+ rescue Gem::LoadError
51
+ adapter = Faraday.default_adapter
52
+ end
53
+
54
+ make_connection(PuppetForge.host, [adapter])
55
+ end
56
+ module_function :default_connection
57
+
58
+ # Generate a new Faraday connection for the given URL.
59
+ #
60
+ # @param url [String] the base URL for this connection
61
+ # @return [Faraday::Connection]
62
+ def make_connection(url, adapter_args = nil, opts = {})
63
+ adapter_args ||= [Faraday.default_adapter]
64
+ options = { :headers => { :user_agent => USER_AGENT } }.merge(opts)
65
+
66
+ if token = PuppetForge::Connection.authorization
67
+ options[:headers][:authorization] = token
68
+ end
69
+
70
+ Faraday.new(url, options) do |builder|
71
+ builder.use PuppetForge::Middleware::SymbolifyJson
72
+ builder.response(:json, :content_type => /\bjson$/)
73
+ builder.response(:raise_error)
74
+ builder.use(:connection_failure)
75
+
76
+ builder.adapter(*adapter_args)
77
+ end
78
+ end
79
+ module_function :make_connection
80
+ end
81
+ end
@@ -0,0 +1,26 @@
1
+ require 'faraday'
2
+
3
+ module PuppetForge
4
+ module Connection
5
+ # Wrap Faraday connection failures to include the host and optional proxy
6
+ # in use for the failed connection.
7
+ class ConnectionFailure < Faraday::Middleware
8
+ def call(env)
9
+ @app.call(env)
10
+ rescue Faraday::ConnectionFailed => e
11
+ baseurl = env[:url].dup
12
+ baseurl.path = ''
13
+ errmsg = "Unable to connect to #{baseurl.to_s}"
14
+ if proxy = env[:request][:proxy]
15
+ errmsg << " (using proxy #{proxy.uri.to_s})"
16
+ end
17
+ errmsg << ": #{e.message}"
18
+ m = Faraday::ConnectionFailed.new(errmsg)
19
+ m.set_backtrace(e.backtrace)
20
+ raise m
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ Faraday::Middleware.register_middleware(:connection_failure => lambda { PuppetForge::Connection::ConnectionFailure })
@@ -0,0 +1,34 @@
1
+ module PuppetForge
2
+ class Error < RuntimeError
3
+ attr_accessor :original
4
+ def initialize(message, original=nil)
5
+ super(message)
6
+ @original = original
7
+ end
8
+ end
9
+
10
+ class ExecutionFailure < PuppetForge::Error
11
+ end
12
+
13
+ class InvalidPathInPackageError < PuppetForge::Error
14
+ def initialize(options)
15
+ @entry_path = options[:entry_path]
16
+ @directory = options[:directory]
17
+ super "Attempt to install file into #{@entry_path.inspect} under #{@directory.inspect}"
18
+ end
19
+
20
+ def multiline
21
+ <<-MSG.strip
22
+ Could not install package
23
+ Package attempted to install file into
24
+ #{@entry_path.inspect} under #{@directory.inspect}.
25
+ MSG
26
+ end
27
+ end
28
+
29
+ class ModuleNotFound < PuppetForge::Error
30
+ end
31
+
32
+ class ReleaseNotFound < PuppetForge::Error
33
+ end
34
+ end
@@ -1,18 +1,12 @@
1
- # Her is an ORM for RESTful APIs, instead of databases.
2
- # @see http://her-rb.org/
3
- module Her
4
-
5
- # ActiveRecord-like interface for RESTful models.
6
- # @see http://her-rb.org/#usage/activerecord-like-methods
7
- module Model; end
1
+ module PuppetForge
8
2
 
9
3
  # When dealing with a remote service, it's reasonably common to receive only
10
4
  # a partial representation of the underlying object, with additional data
11
- # available upon request. {Her}, by default, provides a convenient interface
5
+ # available upon request. PuppetForge, by default, provides a convenient interface
12
6
  # for accessing whatever local data is available, but lacks good support for
13
7
  # fleshing out partial representations. In order to build a seamless
14
8
  # interface for both local and remote attriibutes, this module replaces the
15
- # default behavior of {Her::Model}s with an "updatable" interface.
9
+ # default behavior with an "updatable" interface.
16
10
  module LazyAccessors
17
11
 
18
12
  # Callback for module inclusion.
@@ -28,19 +22,25 @@ module Her
28
22
  end
29
23
  end
30
24
 
31
- # Override the default {Her::Model}#inspect behavior.
25
+ # Provide class name for object
26
+ #
27
+ def class_name
28
+ self.class.name.split("::").last.downcase
29
+ end
30
+
31
+ # Override the default #inspect behavior.
32
32
  #
33
33
  # The original behavior actually invokes each attribute accessor, which can
34
34
  # be somewhat problematic when the accessors have been overridden. This
35
35
  # implementation simply reports the contents of the attributes hash.
36
36
  def inspect
37
37
  attrs = attributes.map do |x, y|
38
- [ x, attribute_for_inspect(y) ].join('=')
38
+ [ x, y ].join('=')
39
39
  end
40
- "#<#{self.class}(#{request_path}) #{attrs.join(' ')}>"
40
+ "#<#{self.class}(#{uri}) #{attrs.join(' ')}>"
41
41
  end
42
42
 
43
- # Override the default {Her::Model}#method_misssing behavior.
43
+ # Override the default #method_misssing behavior.
44
44
  #
45
45
  # When we receive a {#method_missing} call, one of three things is true:
46
46
  # - the caller is looking up a piece of local data without an accessor
@@ -81,22 +81,15 @@ module Her
81
81
  return self if @_fetch
82
82
 
83
83
  klass = self.class
84
- params = { :_method => klass.method_for(:find), :_path => self.request_path }
85
84
 
86
- klass.request(params) do |data, response|
87
- if @_fetch = response.success?
88
- parsed = klass.parse(data[:data])
89
- parsed.merge!(:_metadata => data[:metadata], :_errors => data[:errors])
90
-
91
- self.send(:initialize, parsed)
92
- self.run_callbacks(:find)
93
- end
85
+ response = klass.request("#{self.class_name}s/#{self.slug}")
86
+ if @_fetch = response.success?
87
+ self.send(:initialize, response.body)
94
88
  end
95
89
 
96
90
  return self
97
91
  end
98
92
 
99
-
100
93
  # A Module subclass for attribute accessors.
101
94
  class AccessorContainer < Module
102
95
 
@@ -117,19 +110,19 @@ module Her
117
110
  # @return [void]
118
111
  def add_attributes(keys)
119
112
  keys.each do |key|
120
- next if methods.include?(name = :"#{key}")
113
+ next if methods.include?(name = key)
121
114
 
122
- define_method(name) do
115
+ define_method("#{name}") do
123
116
  fetch unless has_attribute?(name)
124
117
  attribute(name)
125
118
  end
126
119
 
127
- define_method(:"#{name}?") do
120
+ define_method("#{name}?") do
128
121
  fetch unless has_attribute?(name)
129
122
  has_attribute?(name)
130
123
  end
131
124
 
132
- define_method(:"#{name}=") do |value|
125
+ define_method("#{name}=") do |value|
133
126
  fetch unless has_attribute?(name)
134
127
  attributes[name] = value
135
128
  end
@@ -1,4 +1,4 @@
1
- module Her
1
+ module PuppetForge
2
2
 
3
3
  # This module provides convenience accessors for related resources. Related
4
4
  # classes will include {LazyAccessors}, allowing them to transparently fetch
@@ -11,9 +11,24 @@ module Her
11
11
  # @private
12
12
  def self.included(base)
13
13
  base.extend(self)
14
- base.after_initialize { @_lazy = {} }
15
14
  end
16
15
 
16
+ def parent
17
+ if self.is_a? Class
18
+ class_name = self.name
19
+ else
20
+ class_name = self.class.name
21
+ end
22
+
23
+ # Get the name of the version module
24
+ version = class_name.split("::")[-2]
25
+
26
+ if version.nil?
27
+ raise RuntimeError, "Unable to determine the parent PuppetForge version module"
28
+ end
29
+
30
+ PuppetForge.const_get(version)
31
+ end
17
32
  # @!macro [attach] lazy
18
33
  # @!method $1
19
34
  # Returns a lazily-loaded $1 proxy. To eagerly load this $1, call
@@ -32,16 +47,19 @@ module Her
32
47
  # @param name [Symbol] the name of the lazy attribute
33
48
  # @param class_name [#to_s] the lazy relation's class name
34
49
  def lazy(name, class_name = name)
35
- parent = self.parent
36
50
  klass = (class_name.is_a?(Class) ? class_name : nil)
37
- class_name = "#{class_name}".singularize.classify
51
+ class_name = "#{class_name}"
38
52
 
39
53
  define_method(name) do
54
+ @_lazy ||= {}
55
+
40
56
  @_lazy[name] ||= begin
57
+
41
58
  klass ||= parent.const_get(class_name)
42
- klass.send(:include, Her::LazyAccessors)
59
+
60
+ klass.send(:include, PuppetForge::LazyAccessors)
43
61
  fetch unless has_attribute?(name)
44
- value = attribute(name)
62
+ value = attributes[name]
45
63
  klass.new(value) if value
46
64
  end
47
65
  end
@@ -69,14 +87,15 @@ module Her
69
87
  # @param name [Symbol] the name of the lazy collection attribute
70
88
  # @param class_name [#to_s] the lazy relation's class name
71
89
  def lazy_collection(name, class_name = name)
72
- parent = self.parent
73
90
  klass = (class_name.is_a?(Class) ? class_name : nil)
74
- class_name = "#{class_name}".singularize.classify
91
+ class_name = "#{class_name}"
75
92
 
76
93
  define_method(name) do
94
+ @_lazy ||= {}
95
+
77
96
  @_lazy[name] ||= begin
78
97
  klass ||= parent.const_get(class_name)
79
- klass.send(:include, Her::LazyAccessors)
98
+ klass.send(:include, PuppetForge::LazyAccessors)
80
99
  fetch unless has_attribute?(name)
81
100
  (attribute(name) || []).map { |x| klass.new(x) }
82
101
  end