berkshelf-api 1.4.0 → 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 (31) hide show
  1. data/CHANGELOG.md +12 -0
  2. data/README.md +19 -4
  3. data/lib/berkshelf/api.rb +2 -1
  4. data/lib/berkshelf/api/cache_builder.rb +1 -1
  5. data/lib/berkshelf/api/cache_builder/worker/file_store.rb +1 -1
  6. data/lib/berkshelf/api/cache_builder/worker/supermarket.rb +47 -0
  7. data/lib/berkshelf/api/config.rb +1 -4
  8. data/lib/berkshelf/api/remote_cookbook.rb +17 -1
  9. data/lib/berkshelf/api/rspec.rb +1 -1
  10. data/lib/berkshelf/api/site_connector.rb +1 -1
  11. data/lib/berkshelf/api/site_connector/supermarket.rb +67 -0
  12. data/lib/berkshelf/api/version.rb +1 -1
  13. data/spec/unit/berkshelf/api/application_spec.rb +6 -4
  14. data/spec/unit/berkshelf/api/cache_builder/worker/chef_server_spec.rb +5 -4
  15. data/spec/unit/berkshelf/api/cache_builder/worker/file_store_spec.rb +5 -4
  16. data/spec/unit/berkshelf/api/cache_builder/worker/github_spec.rb +13 -17
  17. data/spec/unit/berkshelf/api/cache_builder/worker/supermarket_spec.rb +103 -0
  18. data/spec/unit/berkshelf/api/cache_builder/worker_spec.rb +1 -1
  19. data/spec/unit/berkshelf/api/cache_builder_spec.rb +12 -8
  20. data/spec/unit/berkshelf/api/cache_manager_spec.rb +16 -15
  21. data/spec/unit/berkshelf/api/config_spec.rb +3 -4
  22. data/spec/unit/berkshelf/api/dependency_cache_spec.rb +10 -6
  23. data/spec/unit/berkshelf/api/endpoint/v1_spec.rb +58 -12
  24. data/spec/unit/berkshelf/api/rack_app_spec.rb +5 -2
  25. data/spec/unit/berkshelf/api/rest_gateway_spec.rb +31 -10
  26. data/spec/unit/berkshelf/api/site_connector/supermarket_spec.rb +90 -0
  27. metadata +9 -25
  28. data/lib/berkshelf/api/cache_builder/worker/opscode.rb +0 -67
  29. data/lib/berkshelf/api/site_connector/opscode.rb +0 -181
  30. data/spec/unit/berkshelf/api/cache_builder/worker/opscode_spec.rb +0 -43
  31. data/spec/unit/berkshelf/api/site_connector/opscode_spec.rb +0 -85
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # 2.0.0
2
+
3
+ * Enhancements
4
+ * Opscode Cache builder is now the Supermarket cache builder
5
+
6
+ * Bug fixes
7
+ * API server will now be configured to run as API user instead of root
8
+ * Fix issue where Berkshelf API user's home directory was not created
9
+
10
+ * Backwards incompatible changes
11
+ * The Opscode cache builder was removed - the old community site is no longer a valid endpoint to cache
12
+
1
13
  # 1.4.0
2
14
 
3
15
  * Enhancements
data/README.md CHANGED
@@ -91,15 +91,30 @@ Ruby 1.9 mode is required on all interpreters.
91
91
 
92
92
  You may configure the endpoints to index by editing the JSON configuration file (default: `#{ENV['HOME']}/.berkshelf/api-server/config.json`).
93
93
 
94
- ### Opscode Community Site
94
+ ### Supermarket Community Site
95
95
 
96
- ````json
96
+ Please note: this is unnecessary. You may point your Berksfile at "https://supermarket.getchef.com" instead.
97
+
98
+ ```json
99
+ {
100
+ "endpoints": [
101
+ { "type": "supermarket" }
102
+ ]
103
+ }
104
+ ```
105
+
106
+ ### Supermarket "Behind the Firewall"
107
+
108
+ Please note: this is unnecessary. You may point your Berksfile at "https://your-supermarket-install.example.com" instead.
109
+
110
+
111
+ ```json
97
112
  {
98
113
  "endpoints": [
99
114
  {
100
- "type": "opscode",
115
+ "type": "supermarket",
101
116
  "options": {
102
- "url": "http://cookbooks.opscode.com/api/v1"
117
+ "url": "https://your-supermarket-install.example.com/"
103
118
  }
104
119
  }
105
120
  ]
data/lib/berkshelf/api.rb CHANGED
@@ -2,13 +2,14 @@ require 'berkshelf/api/version'
2
2
  require 'celluloid'
3
3
  require 'hashie'
4
4
  require 'ridley'
5
- require 'faraday'
6
5
  require 'json'
7
6
 
8
7
  require_relative 'api/core_ext'
9
8
 
10
9
  module Berkshelf
11
10
  module API
11
+ USER_AGENT = "Berkshelf API v#{Berkshelf::API::VERSION}".freeze
12
+
12
13
  require_relative 'api/errors'
13
14
  require_relative 'api/logging'
14
15
  require_relative 'api/mixin'
@@ -20,7 +20,7 @@ module Berkshelf::API
20
20
  @building = false
21
21
 
22
22
  Application.config.endpoints.each_with_index do |endpoint, index|
23
- endpoint_options = endpoint.options.to_hash.deep_symbolize_keys
23
+ endpoint_options = (endpoint.options || {}).to_hash.deep_symbolize_keys
24
24
  @worker_supervisor.supervise(CacheBuilder::Worker[endpoint.type], endpoint_options.merge(priority: index))
25
25
  end
26
26
  end
@@ -18,7 +18,7 @@ module Berkshelf::API
18
18
  log.warn "dependencies is *STRONGLY FROWNED UPON* and potentially *DANGEROUS*."
19
19
  log.warn ""
20
20
  log.warn "Please consider setting up a release process for the cookbooks you wish to retrieve from this"
21
- log.warn "filepathe where the cookbook is uploaded into a Hosted Chef organization, an internal"
21
+ log.warn "filepath where the cookbook is uploaded into a Hosted Chef organization, an internal"
22
22
  log.warn "Chef Server, or the community site, and then replace this endpoint with a chef_server endpoint."
23
23
  super(options)
24
24
  end
@@ -0,0 +1,47 @@
1
+ module Berkshelf::API
2
+ class CacheBuilder
3
+ module Worker
4
+ class Supermarket < Worker::Base
5
+ worker_type 'supermarket'
6
+
7
+ # @param [Hash] options
8
+ # see {API::SiteConnector::Supermarket.new} for options
9
+ def initialize(options = {})
10
+ @connection = Berkshelf::API::SiteConnector::Supermarket.new(options)
11
+ super
12
+ end
13
+
14
+ # @return [Array<RemoteCookbook>]
15
+ # The list of cookbooks this builder can find
16
+ def cookbooks
17
+ connection.universe.inject([]) do |list, (name, versions)|
18
+ versions.each do |version, info|
19
+ list << RemoteCookbook.new(name, version, self.class.worker_type, connection.api_url, priority, info)
20
+ end
21
+
22
+ list
23
+ end
24
+ end
25
+
26
+ # Return the metadata of the given RemoteCookbook. If the metadata could not be found or parsed
27
+ # nil is returned.
28
+ #
29
+ # @param [RemoteCookbook] remote
30
+ #
31
+ # @return [Ridley::Chef::Cookbook::Metadata, nil]
32
+ def metadata(remote)
33
+ Ridley::Chef::Cookbook::Metadata.from_hash(
34
+ 'name' => remote.name,
35
+ 'version' => remote.version,
36
+ 'dependencies' => remote.info['dependencies'] || {},
37
+ )
38
+ end
39
+
40
+ private
41
+
42
+ attr_accessor :connection
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -19,10 +19,7 @@ module Berkshelf::API
19
19
  type: Array,
20
20
  default: [
21
21
  {
22
- type: "opscode",
23
- options: {
24
- url: 'http://cookbooks.opscode.com/api/v1'
25
- }
22
+ type: "supermarket"
26
23
  }
27
24
  ]
28
25
 
@@ -1,5 +1,21 @@
1
1
  module Berkshelf::API
2
- class RemoteCookbook < Struct.new(:name, :version, :location_type, :location_path, :priority)
2
+ class RemoteCookbook
3
+ attr_accessor :name
4
+ attr_accessor :version
5
+ attr_accessor :location_type
6
+ attr_accessor :location_path
7
+ attr_accessor :priority
8
+ attr_accessor :info
9
+
10
+ def initialize(name, version, location_type, location_path, priority, info = {})
11
+ @name = name
12
+ @version = version
13
+ @location_type = location_type
14
+ @location_path = location_path
15
+ @priority = priority
16
+ @info = info
17
+ end
18
+
3
19
  def hash
4
20
  "#{name}|#{version}".hash
5
21
  end
@@ -10,7 +10,7 @@ module Berkshelf::API
10
10
  options[:platforms] ||= Hash.new
11
11
  options[:dependencies] ||= Hash.new
12
12
  cookbook = RemoteCookbook.new(name, version,
13
- CacheBuilder::Worker::Opscode.worker_type, SiteConnector::Opscode::V1_API)
13
+ CacheBuilder::Worker::Supermarket.worker_type, SiteConnector::Supermarket::V1_API)
14
14
  metadata = Ridley::Chef::Cookbook::Metadata.new
15
15
  options[:platforms].each { |name, version| metadata.supports(name, version) }
16
16
  options[:dependencies].each { |name, constraint| metadata.depends(name, constraint) }
@@ -1,7 +1,7 @@
1
1
  module Berkshelf
2
2
  module API
3
3
  module SiteConnector
4
- require_relative 'site_connector/opscode'
4
+ require_relative 'site_connector/supermarket'
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,67 @@
1
+ require 'open-uri'
2
+ require 'retryable'
3
+ require 'archive'
4
+ require 'tempfile'
5
+
6
+ module OpenURI
7
+ class << self
8
+ #
9
+ # The is a bug in Ruby's implementation of OpenURI that prevents redirects
10
+ # from HTTP -> HTTPS. That should totally be a valid redirect, so we
11
+ # override that method here and call it a day.
12
+ #
13
+ # Note: this does NOT permit HTTPS -> HTTP redirects, as that would be a
14
+ # major security hole in the fabric of space-time!
15
+ #
16
+ def redirectable?(uri1, uri2)
17
+ a, b = uri1.scheme.downcase, uri2.scheme.downcase
18
+
19
+ a == b || (a == 'http' && b == 'https')
20
+ end
21
+ end
22
+ end
23
+
24
+ module Berkshelf::API
25
+ module SiteConnector
26
+ class Supermarket
27
+ include Berkshelf::API::Logging
28
+
29
+ # The default API server
30
+ V1_API = 'https://supermarket.getchef.com/'.freeze
31
+
32
+ # The timeout for the HTTP request
33
+ TIMEOUT = 15
34
+
35
+ # @return [String]
36
+ attr_reader :api_url
37
+
38
+ # @option options [String] :url ({V1_API})
39
+ # url of community site
40
+ def initialize(options = {})
41
+ @api_url = options[:url] || V1_API
42
+ end
43
+
44
+ # @return [Hash]
45
+ def universe
46
+ universe_url = URI.parse(File.join(api_url, 'universe.json')).to_s
47
+
48
+ log.debug "Loading universe from `#{universe_url}'..."
49
+
50
+ Timeout.timeout(TIMEOUT) do
51
+ response = open(universe_url, 'User-Agent' => USER_AGENT)
52
+ JSON.parse(response.read)
53
+ end
54
+ rescue JSON::ParserError => e
55
+ log.error "Failed to parse JSON: #{e}"
56
+ rescue Timeout::Error
57
+ log.error "Failed to get `#{universe_url}' in #{TIMEOUT} seconds!"
58
+ rescue SocketError,
59
+ Errno::ECONNREFUSED,
60
+ Errno::ECONNRESET,
61
+ Errno::ENETUNREACH,
62
+ OpenURI::HTTPError => e
63
+ log.error "Failed to get `#{universe_url}': #{e}"
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,5 @@
1
1
  module Berkshelf
2
2
  module API
3
- VERSION = "1.4.0"
3
+ VERSION = "2.0.0"
4
4
  end
5
5
  end
@@ -2,9 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe Berkshelf::API::Application do
4
4
  describe "ClassMethods" do
5
- subject { described_class }
6
-
7
- its(:registry) { should be_a(Celluloid::Registry) }
5
+ describe '.registry' do
6
+ it 'returns a Celluloid::Registry' do
7
+ expect(described_class.registry).to be_a(Celluloid::Registry)
8
+ end
9
+ end
8
10
 
9
11
  describe "::configure" do
10
12
  let(:options) { Hash.new }
@@ -22,7 +24,7 @@ describe Berkshelf::API::Application do
22
24
  generated.save
23
25
  configure
24
26
 
25
- expect(described_class.config.endpoints).to have(1).item
27
+ expect(described_class.config.endpoints.size).to eq(1)
26
28
  end
27
29
 
28
30
  context "if the file cannot be found or loaded" do
@@ -1,9 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Berkshelf::API::CacheBuilder::Worker::ChefServer do
4
- describe "ClassMethods" do
5
- subject { described_class }
6
- its(:worker_type) { should eql("chef_server") }
4
+ describe '.worker_type' do
5
+ it 'is chef_server' do
6
+ expect(described_class.worker_type).to eq('chef_server')
7
+ end
7
8
  end
8
9
 
9
10
  subject do
@@ -22,7 +23,7 @@ describe Berkshelf::API::CacheBuilder::Worker::ChefServer do
22
23
  end
23
24
 
24
25
  it "returns an array containing an item for each cookbook on the server" do
25
- expect(subject.cookbooks).to have(4).items
26
+ expect(subject.cookbooks.size).to eq(4)
26
27
  end
27
28
 
28
29
  it "returns an array of RemoteCookbooks" do
@@ -1,9 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Berkshelf::API::CacheBuilder::Worker::FileStore do
4
- describe "ClassMethods" do
5
- subject { described_class }
6
- its(:worker_type) { should eql("file_store") }
4
+ describe '.worker_type' do
5
+ it 'is file_store' do
6
+ expect(described_class.worker_type).to eq('file_store')
7
+ end
7
8
  end
8
9
 
9
10
  subject do
@@ -14,7 +15,7 @@ describe Berkshelf::API::CacheBuilder::Worker::FileStore do
14
15
 
15
16
  describe "#cookbooks" do
16
17
  it "returns an array containing an item for each valid cookbook on the server" do
17
- expect(subject.cookbooks).to have(1).items
18
+ expect(subject.cookbooks.size).to eq(1)
18
19
  end
19
20
 
20
21
  it "returns an array of RemoteCookbooks" do
@@ -1,9 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Berkshelf::API::CacheBuilder::Worker::Github do
4
- describe "ClassMethods" do
5
- subject { described_class }
6
- its(:worker_type) { should eql("github") }
4
+ describe '.worker_type' do
5
+ it 'is github' do
6
+ expect(described_class.worker_type).to eq('github')
7
+ end
7
8
  end
8
9
 
9
10
  let(:connection) do
@@ -11,28 +12,23 @@ describe Berkshelf::API::CacheBuilder::Worker::Github do
11
12
  end
12
13
 
13
14
  let(:good_tag) do
14
- tag = double('good_tag')
15
- tag.stub(:name) { 'v1.0.0' }
16
- tag
15
+ double('good_tag', name: 'v1.0.0')
17
16
  end
18
17
 
19
18
  let(:bad_tag) do
20
- tag = double('good_tag')
21
- tag.stub(:name) { 'beta2' }
22
- tag
19
+ double('good_tag', name: 'beta2')
23
20
  end
24
21
 
25
22
  let :contents do
26
- contents = double('contents')
27
- contents.stub(:content) { 'dmVyc2lvbiAiMS4wLjAi' }
28
- contents
23
+ double('contents', content: 'dmVyc2lvbiAiMS4wLjAi')
29
24
  end
30
25
 
31
26
  let(:repo) do
32
- repo = double('repo')
33
- repo.stub(:full_name) { 'opscode-cookbooks/apt' }
34
- repo.stub(:name) { 'apt' }
35
- repo
27
+ double('repo',
28
+ name: 'apt',
29
+ full_name: 'opscode-cookbooks/apt',
30
+ html_url: 'https://github.com/opscode-cookbooks/apt',
31
+ )
36
32
  end
37
33
 
38
34
  let(:repos) do
@@ -55,7 +51,7 @@ describe Berkshelf::API::CacheBuilder::Worker::Github do
55
51
  end
56
52
 
57
53
  it "returns an array containing an item for each valid cookbook on the server" do
58
- expect(subject.cookbooks).to have(1).items
54
+ expect(subject.cookbooks.size).to eq(1)
59
55
  end
60
56
 
61
57
  it "returns an array of RemoteCookbooks" do
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ module Berkshelf
4
+ module API
5
+ describe CacheBuilder::Worker::Supermarket do
6
+ describe '.worker_type' do
7
+ it 'is supermarket' do
8
+ expect(described_class.worker_type).to eq('supermarket')
9
+ end
10
+ end
11
+
12
+ let(:location_path) { SiteConnector::Supermarket::V1_API }
13
+ let(:location_type) { described_class.worker_type }
14
+ let(:priority) { 1 }
15
+
16
+ let(:universe) do
17
+ {
18
+ 'chicken' => {
19
+ '1.0' => {
20
+ 'location_type' => 'supermarket',
21
+ 'location_path' => location_path,
22
+ 'download_url' => "#{location_path}/cookbooks/chicken/versions/1.0/download",
23
+ },
24
+ '2.0' => {
25
+ 'location_type' => 'supermarket',
26
+ 'location_path' => location_path,
27
+ 'download_url' => "#{location_path}/cookbooks/chicken/versions/2.0/download",
28
+ }
29
+ },
30
+ 'tuna' => {
31
+ '3.0.0' => {
32
+ 'location_type' => 'supermarket',
33
+ 'location_path' => location_path,
34
+ 'download_url' => "#{location_path}/cookbooks/tuna/versions/3.0.0/download",
35
+ },
36
+ '3.0.1' => {
37
+ 'location_type' => 'supermarket',
38
+ 'location_path' => location_path,
39
+ 'download_url' => "#{location_path}/cookbooks/tuna/versions/3.0.1/download",
40
+ }
41
+ }
42
+ }
43
+ end
44
+
45
+ let(:connection) do
46
+ double(SiteConnector::Supermarket,
47
+ universe: universe,
48
+ api_url: location_path,
49
+ )
50
+ end
51
+
52
+ before do
53
+ allow(subject.wrapped_object).to receive(:connection)
54
+ .and_return(connection)
55
+ end
56
+
57
+ subject do
58
+ CacheManager.start
59
+ described_class.new(priority: priority)
60
+ end
61
+
62
+ it_behaves_like "a human-readable string"
63
+
64
+ describe "#cookbooks" do
65
+ it "returns an array of RemoteCookbooks described by the server" do
66
+ expected = [
67
+ RemoteCookbook.new('chicken', '1.0', location_type, location_path, priority),
68
+ RemoteCookbook.new('chicken', '2.0', location_type, location_path, priority),
69
+ RemoteCookbook.new('tuna', '3.0.0', location_type, location_path, priority),
70
+ RemoteCookbook.new('tuna', '3.0.1', location_type, location_path, priority),
71
+ ]
72
+
73
+ result = subject.cookbooks
74
+ expect(result).to be_a(Array)
75
+
76
+ expect(result[0].name).to eq('chicken')
77
+ expect(result[0].version).to eq('1.0')
78
+ expect(result[0].location_type).to eq(location_type)
79
+ expect(result[0].location_path).to eq(location_path)
80
+ expect(result[0].priority).to eq(priority)
81
+
82
+ expect(result[1].name).to eq('chicken')
83
+ expect(result[1].version).to eq('2.0')
84
+ expect(result[1].location_type).to eq(location_type)
85
+ expect(result[1].location_path).to eq(location_path)
86
+ expect(result[1].priority).to eq(priority)
87
+
88
+ expect(result[2].name).to eq('tuna')
89
+ expect(result[2].version).to eq('3.0.0')
90
+ expect(result[2].location_type).to eq(location_type)
91
+ expect(result[2].location_path).to eq(location_path)
92
+ expect(result[2].priority).to eq(priority)
93
+
94
+ expect(result[3].name).to eq('tuna')
95
+ expect(result[3].version).to eq('3.0.1')
96
+ expect(result[3].location_type).to eq(location_type)
97
+ expect(result[3].location_path).to eq(location_path)
98
+ expect(result[3].priority).to eq(priority)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end