puppet_forge 1.0.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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