cookbook-omnifetch 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c87b779ed374b94c79ed6d60c92d4acdacca050
4
- data.tar.gz: c5f55d263dd9c5ccc81b96be560664d6d4ac882a
3
+ metadata.gz: 64e4ca89bc3c10a6c4bf4e224bb39f5657d1cd12
4
+ data.tar.gz: c16cfaa9e0b3c9eb655faa3d12a293b6ec166931
5
5
  SHA512:
6
- metadata.gz: 948bbab575f6693876e197978b3866b9489dc24c64b3860b94fee60cb5187d96fc3101559067aa163d0b44911aa90c10bc20f1a33de6d14e626cd41198337519
7
- data.tar.gz: 1ad1cac27d2b6a6c84f01c8155a7dcf11f14f5af56c45dd842c50e6e86ff35369e501f81a9914646c07bfa60e9c47cb463506ce5d1b4ef5c23bc885bde277d5c
6
+ metadata.gz: 8e3354f49b0ed00fd5ab23f6de8dd8994419bb8e15c1d32b5dc85f960c46c1d20af45b8f2ca4cbb0e96a812a57e9a8bc04bba1907a319c13e9a6a323eda673be
7
+ data.tar.gz: f7841d6c9081275526ba60ece1fd3f349b24d812d9c2bba66110a3c1da6800d0e35e2da1a304f7601e4fda6849e9f78f49c8a127f3c3fcaf4f33dc1f919db3dd
@@ -8,6 +8,7 @@ require "cookbook-omnifetch/path"
8
8
  require "cookbook-omnifetch/artifactserver"
9
9
  require "cookbook-omnifetch/artifactory"
10
10
  require "cookbook-omnifetch/chef_server"
11
+ require "cookbook-omnifetch/chef_server_artifact"
11
12
 
12
13
  module CookbookOmnifetch
13
14
 
@@ -107,6 +108,22 @@ module CookbookOmnifetch
107
108
  integration.storage_path
108
109
  end
109
110
 
111
+ # Returns the number of threads that will be used when downloading cookbooks
112
+ # from a Chef Server. The default is 1.
113
+ #
114
+ # NOTE: This should only be changed if the `http_client` passed in to a
115
+ # ChefServerLocation or ChefServerArtifactLocation is thread-safe. In
116
+ # particular, the `Chef::ServerAPI` class is NOT THREAD SAFE. Chef Client uses
117
+ # thread-local storage to create one instance of `Chef::ServerAPI` per-thread
118
+ # when used in threaded code.
119
+ #
120
+ # When a properly thread-safe HTTP client is used, this can be configured to
121
+ # a larger value to reduce the time needed to download cookbooks from a Chef
122
+ # Server.
123
+ def self.chef_server_download_concurrency
124
+ integration.chef_server_download_concurrency
125
+ end
126
+
110
127
  # Returns true or false if the given path contains a Chef Cookbook
111
128
  #
112
129
  # @param [#to_s] path
@@ -1,35 +1,7 @@
1
1
  require "cookbook-omnifetch/base"
2
+ require "cookbook-omnifetch/metadata_based_installer"
2
3
 
3
4
  module CookbookOmnifetch
4
- class CookbookMetadata
5
-
6
- FILE_TYPES = [
7
- :resources,
8
- :providers,
9
- :recipes,
10
- :definitions,
11
- :libraries,
12
- :attributes,
13
- :files,
14
- :templates,
15
- :root_files,
16
- ].freeze
17
-
18
- def initialize(metadata)
19
- @metadata = metadata
20
- end
21
-
22
- def files(&block)
23
- FILE_TYPES.each do |type|
24
- next unless @metadata.has_key?(type.to_s)
25
-
26
- @metadata[type.to_s].each do |file|
27
- yield file["url"], file["path"]
28
- end
29
- end
30
- end
31
- end
32
-
33
5
  class ChefServerLocation < BaseLocation
34
6
 
35
7
  attr_reader :cookbook_version
@@ -42,27 +14,20 @@ module CookbookOmnifetch
42
14
  @uri ||= options[:chef_server]
43
15
  end
44
16
 
45
- def repo_host
46
- @host ||= URI.parse(uri).host
47
- end
48
-
49
17
  def cookbook_name
50
18
  dependency.name
51
19
  end
52
20
 
53
- def install
54
- FileUtils.mkdir_p(staging_root) unless staging_root.exist?
55
- md = http_client.get("/cookbooks/#{cookbook_name}/#{cookbook_version}")
56
- CookbookMetadata.new(md).files do |url, path|
57
- stage = staging_path.join(path)
58
- FileUtils.mkdir_p(File.dirname(stage))
21
+ def url_path
22
+ "/cookbooks/#{cookbook_name}/#{cookbook_version}"
23
+ end
59
24
 
60
- http_client.streaming_request(url) do |tempfile|
61
- tempfile.close
62
- FileUtils.mv(tempfile.path, stage)
63
- end
64
- end
65
- FileUtils.mv(staging_path, install_path)
25
+ def installer
26
+ MetadataBasedInstaller.new(http_client: http_client, url_path: url_path, install_path: install_path)
27
+ end
28
+
29
+ def install
30
+ installer.install
66
31
  end
67
32
 
68
33
  # Determine if this revision is installed.
@@ -92,22 +57,5 @@ module CookbookOmnifetch
92
57
  "#{dependency.name}-#{cookbook_version}"
93
58
  end
94
59
 
95
- # The path where tarballs are downloaded to and unzipped. On certain platforms
96
- # you have a better chance of getting an atomic move if your temporary working
97
- # directory is on the same device/volume as the destination. To support this,
98
- # we use a staging directory located under the cache path under the rather mild
99
- # assumption that everything under the cache path is going to be on one device.
100
- #
101
- # Do not create anything under this directory that isn't randomly named and
102
- # remember to release your files once you are done.
103
- #
104
- # @return [Pathname]
105
- def staging_root
106
- Pathname.new(CookbookOmnifetch.cache_path).join(".cache_tmp", "artifactserver")
107
- end
108
-
109
- def staging_path
110
- staging_root.join(cache_key)
111
- end
112
60
  end
113
61
  end
@@ -0,0 +1,72 @@
1
+ require "cookbook-omnifetch/base"
2
+ require "cookbook-omnifetch/metadata_based_installer"
3
+
4
+ module CookbookOmnifetch
5
+ # This location allows fetching from the `cookbook_artifacts/` API where Chef
6
+ # Server stores cookbooks for policyfile use when they're uploaded via `chef push`.
7
+ #
8
+ # End users likely won't have much use for this; it's intended to facilitate
9
+ # included policies when including a policy stored on a chef server and
10
+ # cookbooks cannot be installed from the original source based on the
11
+ # information in the included policy.
12
+ class ChefServerArtifactLocation < BaseLocation
13
+
14
+ attr_reader :cookbook_identifier
15
+ attr_reader :uri
16
+
17
+ def initialize(dependency, options = {})
18
+ super
19
+ @cookbook_identifier = options[:identifier]
20
+ @http_client = options[:http_client]
21
+ @uri ||= options[:chef_server_artifact]
22
+ end
23
+
24
+ def repo_host
25
+ @host ||= URI.parse(uri).host
26
+ end
27
+
28
+ def cookbook_name
29
+ dependency.name
30
+ end
31
+
32
+ def url_path
33
+ "/cookbook_artifacts/#{cookbook_name}/#{cookbook_identifier}"
34
+ end
35
+
36
+ def installer
37
+ MetadataBasedInstaller.new(http_client: http_client, url_path: url_path, install_path: install_path)
38
+ end
39
+
40
+ def install
41
+ installer.install
42
+ end
43
+
44
+ # Determine if this revision is installed.
45
+ #
46
+ # @return [Boolean]
47
+ def installed?
48
+ install_path.exist?
49
+ end
50
+
51
+ def http_client
52
+ @http_client
53
+ end
54
+
55
+ # The path where this cookbook would live in the store, if it were
56
+ # installed.
57
+ #
58
+ # @return [Pathname, nil]
59
+ def install_path
60
+ @install_path ||= CookbookOmnifetch.storage_path.join(cache_key)
61
+ end
62
+
63
+ def lock_data
64
+ { "chef_server" => uri, "server_identifier" => cookbook_identifier }
65
+ end
66
+
67
+ def cache_key
68
+ "#{dependency.name}-#{cookbook_identifier}"
69
+ end
70
+
71
+ end
72
+ end
@@ -35,10 +35,15 @@ module CookbookOmnifetch
35
35
  configurable :shell_out_class
36
36
  configurable :cached_cookbook_class
37
37
 
38
+ # Number of threads to use when downloading from a Chef Server. See
39
+ # commentary in cookbook_omnifetch.rb
40
+ configurable :chef_server_download_concurrency
41
+
38
42
  def initialize
39
43
  self.class.configurables.each do |configurable|
40
44
  instance_variable_set("@#{configurable}".to_sym, NullValue.new)
41
45
  end
46
+ @chef_server_download_concurrency = 1
42
47
  end
43
48
 
44
49
  end
@@ -0,0 +1,94 @@
1
+ require "cookbook-omnifetch/threaded_job_queue"
2
+
3
+ module CookbookOmnifetch
4
+
5
+ class MetadataBasedInstaller
6
+ class CookbookMetadata
7
+
8
+ FILE_TYPES = [
9
+ :resources,
10
+ :providers,
11
+ :recipes,
12
+ :definitions,
13
+ :libraries,
14
+ :attributes,
15
+ :files,
16
+ :templates,
17
+ :root_files,
18
+ ].freeze
19
+
20
+ def initialize(metadata)
21
+ @metadata = metadata
22
+ end
23
+
24
+ def files(&block)
25
+ FILE_TYPES.each do |type|
26
+ next unless @metadata.has_key?(type.to_s)
27
+
28
+ @metadata[type.to_s].each do |file|
29
+ yield file["url"], file["path"]
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ attr_reader :http_client
36
+ attr_reader :url_path
37
+ attr_reader :install_path
38
+ attr_reader :slug
39
+
40
+ def initialize(http_client:, url_path:, install_path:)
41
+ @http_client = http_client
42
+ @url_path = url_path
43
+ @install_path = install_path
44
+ @slug = Kernel.rand(1_000_000_000).to_s
45
+ end
46
+
47
+ def install
48
+ FileUtils.rm_rf(staging_path) # ensure we have a clean dir, just in case
49
+ FileUtils.mkdir_p(staging_root) unless staging_root.exist?
50
+ md = http_client.get(url_path)
51
+
52
+ queue = ThreadedJobQueue.new
53
+
54
+ CookbookMetadata.new(md).files do |url, path|
55
+ stage = staging_path.join(path)
56
+ FileUtils.mkdir_p(File.dirname(stage))
57
+
58
+ queue << lambda do |_lock|
59
+ http_client.streaming_request(url) do |tempfile|
60
+ tempfile.close
61
+ FileUtils.mv(tempfile.path, stage)
62
+ end
63
+ end
64
+ end
65
+
66
+ queue.process(CookbookOmnifetch.chef_server_download_concurrency)
67
+
68
+ FileUtils.mv(staging_path, install_path)
69
+ end
70
+
71
+ # The path where files are downloaded to. On certain platforms you have a
72
+ # better chance of getting an atomic move if your temporary working
73
+ # directory is on the same device/volume as the destination. To support
74
+ # this, we use a staging directory located under the cache path under the
75
+ # rather mild assumption that everything under the cache path is going to
76
+ # be on one device.
77
+ #
78
+ # @return [Pathname]
79
+ def staging_root
80
+ Pathname.new(CookbookOmnifetch.cache_path).join(".cache_tmp", "metadata-installer")
81
+ end
82
+
83
+ def staging_path
84
+ staging_root.join(staging_cache_key)
85
+ end
86
+
87
+ # Convert the URL to a safe name for a file and append our random slug.
88
+ # This helps us avoid colliding in the case that there are multiple
89
+ # processes installing the same cookbook at the same time.
90
+ def staging_cache_key
91
+ "#{url_path.gsub(/[^[:alnum:]]/, "_")}_#{slug}"
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,46 @@
1
+ # Copyright:: Copyright 2014-2016, Chef Software Inc.
2
+ # License:: Apache License, Version 2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module CookbookOmnifetch
17
+
18
+ # This class is copied from the Chef codebase:
19
+ # https://github.com/chef/chef/blob/7f0b5150c32994b4ad593505172c5834a984b087/lib/chef/util/threaded_job_queue.rb
20
+ #
21
+ # We do not re-use the code from Chef because we do not want to introduce a
22
+ # dependency on Chef in this library.
23
+ class ThreadedJobQueue
24
+ def initialize
25
+ @queue = Queue.new
26
+ @lock = Mutex.new
27
+ end
28
+
29
+ def <<(job)
30
+ @queue << job
31
+ end
32
+
33
+ def process(concurrency = 10)
34
+ workers = (1..concurrency).map do
35
+ Thread.new do
36
+ loop do
37
+ fn = @queue.pop
38
+ fn.arity == 1 ? fn.call(@lock) : fn.call
39
+ end
40
+ end
41
+ end
42
+ workers.each { |worker| self << Thread.method(:exit) }
43
+ workers.each { |worker| worker.join }
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module CookbookOmnifetch
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -0,0 +1,74 @@
1
+ require "spec_helper"
2
+ require "cookbook-omnifetch/chef_server.rb"
3
+
4
+ RSpec.describe CookbookOmnifetch::ChefServerArtifactLocation do
5
+
6
+ let(:http_client) { double("Http Client") }
7
+
8
+ let(:test_root) { "/some/fake/path" }
9
+
10
+ let(:storage_path) { File.join(test_root, "storage") }
11
+
12
+ let(:dependency) { double("Dependency", name: cookbook_name) }
13
+
14
+ let(:cookbook_name) { "example" }
15
+
16
+ let(:cookbook_identifier) { "467dc855408ce8b74f991c5dc2fd72a6aa369b60" }
17
+
18
+ let(:url) { "https://chef.example.com/organizations/example" }
19
+
20
+ let(:options) { { chef_server_artifact: url, identifier: cookbook_identifier, http_client: http_client } }
21
+
22
+ let(:expected_cache_key) { "example-467dc855408ce8b74f991c5dc2fd72a6aa369b60" }
23
+
24
+ subject(:chef_server_artifact_location) { described_class.new(dependency, options) }
25
+
26
+ before do
27
+ allow(CookbookOmnifetch).to receive(:storage_path).and_return(Pathname.new(storage_path))
28
+ end
29
+
30
+ it "has a URI" do
31
+ expect(chef_server_artifact_location.uri).to eq(url)
32
+ end
33
+
34
+ it "has an HTTP client" do
35
+ expect(chef_server_artifact_location.http_client).to eq(http_client)
36
+ end
37
+
38
+ it "has a metadata_based_installer" do
39
+ installer = chef_server_artifact_location.installer
40
+ expect(installer).to be_a(CookbookOmnifetch::MetadataBasedInstaller)
41
+ expect(installer.http_client).to eq(http_client)
42
+ expect(installer.url_path).to eq("/cookbook_artifacts/example/467dc855408ce8b74f991c5dc2fd72a6aa369b60")
43
+ expect(installer.install_path.to_s).to eq(File.join(storage_path, expected_cache_key))
44
+ end
45
+
46
+ it "has a cache key containing the site URI and version" do
47
+ expect(chef_server_artifact_location.cache_key).to eq(expected_cache_key)
48
+ end
49
+
50
+ it "has an identifier" do
51
+ expect(chef_server_artifact_location.cookbook_identifier).to eq(cookbook_identifier)
52
+ end
53
+
54
+ it "provides lock data as a Hash" do
55
+ expected_data = {
56
+ "chef_server" => url,
57
+ "server_identifier" => cookbook_identifier,
58
+ }
59
+ expect(chef_server_artifact_location.lock_data).to eq(expected_data)
60
+ end
61
+
62
+ describe "when installing" do
63
+
64
+ let(:installer) { instance_double("CookbookOmnifetch::MetadataBasedInstaller") }
65
+
66
+ it "delegates to the MetadataBasedInstaller" do
67
+ allow(chef_server_artifact_location).to receive(:installer).and_return(installer)
68
+ expect(installer).to receive(:install)
69
+ chef_server_artifact_location.install
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -1,98 +1,72 @@
1
1
  require "spec_helper"
2
2
  require "cookbook-omnifetch/chef_server.rb"
3
3
 
4
- module CookbookOmnifetch
5
- METADATA = {
6
- "recipes" => [
7
- { "name" => "default.rb", "path" => "recipes/default.rb", "checksum" => "a6be794cdd2eb44d38fdf17f792a0d0d", "specificity" => "default", "url" => "https://example.com/recipes/default.rb" },
8
- ],
9
- "root_files" => [
10
- { "name" => "metadata.rb", "path" => "metadata.rb", "checksum" => "5b346119e5e41ab99500608decac8dca", "specificity" => "default", "url" => "https://example.com/metadata.rb" },
11
- ],
12
- }
13
-
14
- describe CookbookMetadata do
15
- let(:cb_metadata) { CookbookMetadata.new(METADATA) }
16
-
17
- it "yields a set of paths and urls" do
18
- expect { |b| cb_metadata.files(&b) }.to yield_successive_args(["https://example.com/recipes/default.rb", "recipes/default.rb"], ["https://example.com/metadata.rb", "metadata.rb"])
19
- end
20
- end
21
-
22
- describe ChefServerLocation do
4
+ RSpec.describe CookbookOmnifetch::ChefServerLocation do
23
5
 
24
- let(:http_client) { double("Http Client") }
6
+ let(:http_client) { double("Http Client") }
25
7
 
26
- let(:cb_metadata) { CookbookMetadata.new(METADATA) }
8
+ let(:test_root) { "/some/fake/path" }
27
9
 
28
- let(:test_root) { Dir.mktmpdir(nil) }
10
+ let(:storage_path) { File.join(test_root, "storage") }
29
11
 
30
- let(:storage_path) { File.join(test_root, "storage") }
12
+ let(:dependency) { double("Dependency", name: cookbook_name) }
31
13
 
32
- let(:cache_path) { File.join(test_root, "cache") }
14
+ let(:cookbook_name) { "example" }
33
15
 
34
- let(:constraint) { double("Constraint") }
16
+ let(:cookbook_version) { "0.5.0" }
35
17
 
36
- let(:dependency) { double("Dependency", name: cookbook_name, constraint: constraint) }
18
+ let(:url) { "https://chef.example.com/organizations/example" }
37
19
 
38
- let(:cookbook_name) { "example" }
39
- let(:cookbook_version) { "0.5.0" }
20
+ let(:options) { { chef_server: url, version: cookbook_version, http_client: http_client } }
40
21
 
41
- let(:url) { "https://chef.example.com/organizations/example" }
22
+ subject(:chef_server_location) { described_class.new(dependency, options) }
42
23
 
43
- let(:cookbook_fixture_path) { fixtures_path.join("cookbooks/example_cookbook") }
24
+ before do
25
+ allow(CookbookOmnifetch).to receive(:storage_path).and_return(Pathname.new(storage_path))
26
+ end
44
27
 
45
- let(:remote_path) { File.join(test_root, "remote") }
46
- let(:options) { { chef_server: url, version: cookbook_version, http_client: http_client } }
28
+ it "has a URI" do
29
+ expect(chef_server_location.uri).to eq(url)
30
+ end
47
31
 
48
- let(:cookbook_files) { %w{. .. metadata.rb recipes} }
49
- subject(:chef_server_location) { described_class.new(dependency, options) }
32
+ it "has an HTTP client" do
33
+ expect(chef_server_location.http_client).to eq(http_client)
34
+ end
50
35
 
51
- before do
52
- allow(CookbookOmnifetch).to receive(:storage_path).and_return(Pathname.new(storage_path))
53
- allow(CookbookOmnifetch).to receive(:cache_path).and_return(cache_path)
54
- allow_any_instance_of(File).to receive(:close).and_return(true)
55
- FileUtils.cp_r(cookbook_fixture_path, remote_path)
56
- FileUtils.mkdir_p(storage_path)
57
- end
36
+ it "has a metadata_based_installer" do
37
+ installer = chef_server_location.installer
38
+ expect(installer).to be_a(CookbookOmnifetch::MetadataBasedInstaller)
39
+ expect(installer.http_client).to eq(http_client)
40
+ expect(installer.url_path).to eq("/cookbooks/example/0.5.0")
41
+ expect(installer.install_path.to_s).to eq(File.join(storage_path, "example-0.5.0"))
42
+ end
58
43
 
59
- after do
60
- FileUtils.rm_r(test_root)
61
- end
44
+ it "has a cache key containing the site URI and version" do
45
+ expect(chef_server_location.cache_key).to eq("example-0.5.0")
46
+ end
62
47
 
63
- it "has a URI" do
64
- expect(chef_server_location.uri).to eq(url)
65
- end
48
+ it "has an exact version" do
49
+ expect(chef_server_location.cookbook_version).to eq("0.5.0")
50
+ end
66
51
 
67
- it "has a cache key containing the site URI and version" do
68
- expect(chef_server_location.cache_key).to eq("example-0.5.0")
69
- end
52
+ it "provides lock data as a Hash" do
53
+ expected_data = {
54
+ "chef_server" => url,
55
+ "version" => "0.5.0",
56
+ }
57
+ expect(chef_server_location.lock_data).to eq(expected_data)
58
+ end
70
59
 
71
- it "has an exact version" do
72
- expect(chef_server_location.cookbook_version).to eq("0.5.0")
73
- end
60
+ describe "when installing" do
74
61
 
75
- it "installs the cookbook to the desired install path" do
76
- expect(http_client).to receive(:get).with("/cookbooks/example/0.5.0").and_return(METADATA)
77
- expect(http_client).to receive(:streaming_request).twice do |url, &block|
78
- path = url.split("/", 4)[3]
79
- path = File.join(remote_path, path)
80
- block.call(File.open(path))
81
- end
62
+ let(:installer) { instance_double("CookbookOmnifetch::MetadataBasedInstaller") }
82
63
 
64
+ it "delegates to the MetadataBasedInstaller" do
65
+ allow(chef_server_location).to receive(:installer).and_return(installer)
66
+ expect(installer).to receive(:install)
83
67
  chef_server_location.install
84
-
85
- expect(Dir).to exist(chef_server_location.install_path)
86
- expect(Dir.entries(chef_server_location.install_path)).to match_array(cookbook_files)
87
- end
88
-
89
- it "provides lock data as a Hash" do
90
- expected_data = {
91
- "chef_server" => url,
92
- "version" => "0.5.0",
93
- }
94
- expect(chef_server_location.lock_data).to eq(expected_data)
95
68
  end
96
69
 
97
70
  end
71
+
98
72
  end
@@ -0,0 +1,137 @@
1
+ require "spec_helper"
2
+ require "cookbook-omnifetch/metadata_based_installer.rb"
3
+
4
+ RSpec.shared_context "sample_metadata" do
5
+
6
+ let(:raw_metadata) do
7
+ {
8
+ "recipes" => [
9
+ { "name" => "default.rb", "path" => "recipes/default.rb", "checksum" => "a6be794cdd2eb44d38fdf17f792a0d0d", "specificity" => "default", "url" => "https://example.com/recipes/default.rb" },
10
+ ],
11
+ "root_files" => [
12
+ { "name" => "metadata.rb", "path" => "metadata.rb", "checksum" => "5b346119e5e41ab99500608decac8dca", "specificity" => "default", "url" => "https://example.com/metadata.rb" },
13
+ ],
14
+ }
15
+
16
+ end
17
+ end
18
+
19
+ RSpec.describe CookbookOmnifetch::MetadataBasedInstaller::CookbookMetadata do
20
+
21
+ include_context "sample_metadata"
22
+
23
+ subject(:cb_metadata) { described_class.new(raw_metadata) }
24
+
25
+ it "yields a set of paths and urls" do
26
+ expect { |b| cb_metadata.files(&b) }.to yield_successive_args(["https://example.com/recipes/default.rb", "recipes/default.rb"], ["https://example.com/metadata.rb", "metadata.rb"])
27
+ end
28
+ end
29
+
30
+ RSpec.describe CookbookOmnifetch::MetadataBasedInstaller do
31
+
32
+ include_context "sample_metadata"
33
+
34
+ let(:url_path) { "/cookbooks/example/0.5.0" }
35
+
36
+ let(:http_client) do
37
+ double("Http Client")
38
+ end
39
+
40
+ let(:recipe_url) do
41
+ raw_metadata["recipes"][0]["url"]
42
+ end
43
+
44
+ let(:recipe_path) do
45
+ raw_metadata["recipes"][0]["path"]
46
+ end
47
+
48
+ let(:recipe_filehandle) do
49
+ File.open(File.join(remote_path, recipe_path))
50
+ end
51
+
52
+ let(:root_file_url) do
53
+ raw_metadata["root_files"][0]["url"]
54
+ end
55
+
56
+ let(:root_file_path) do
57
+ raw_metadata["root_files"][0]["path"]
58
+ end
59
+
60
+ let(:root_file_filehandle) do
61
+ File.open(File.join(remote_path, root_file_path))
62
+ end
63
+
64
+ let(:cookbook_fixture_path) { fixtures_path.join("cookbooks/example_cookbook") }
65
+
66
+ let(:test_root) { Dir.mktmpdir(nil) }
67
+
68
+ let(:cache_path) { File.join(test_root, "cache") }
69
+
70
+ let(:remote_path) { File.join(test_root, "remote") }
71
+
72
+ let(:install_path) { File.join(test_root, "install_path") }
73
+
74
+ let(:cookbook_files) { %w{metadata.rb recipes recipes/default.rb} }
75
+
76
+ let(:expected_installed_files) do
77
+ cookbook_files.map do |file|
78
+ File.join(install_path, file)
79
+ end
80
+ end
81
+
82
+ subject(:installer) do
83
+ described_class.new(http_client: http_client,
84
+ url_path: url_path,
85
+ install_path: install_path)
86
+ end
87
+
88
+ before do
89
+ FileUtils.cp_r(cookbook_fixture_path, remote_path)
90
+
91
+ allow(CookbookOmnifetch).to receive(:cache_path).and_return(cache_path)
92
+ end
93
+
94
+ after do
95
+ FileUtils.rm_r(test_root)
96
+ end
97
+
98
+ it "stages the download to a randomized location" do
99
+ Kernel.srand(0)
100
+ expected_path = Pathname.new(cache_path).join(".cache_tmp/metadata-installer/_cookbooks_example_0_5_0_209652396")
101
+
102
+ expect(installer.staging_path).to eq(expected_path)
103
+
104
+ next_installer = described_class.new(http_client: http_client,
105
+ url_path: url_path,
106
+ install_path: install_path)
107
+
108
+ next_expected_path = Pathname.new(cache_path).join(".cache_tmp/metadata-installer/_cookbooks_example_0_5_0_398764591")
109
+ expect(next_installer.staging_path).to eq(next_expected_path)
110
+ end
111
+
112
+ describe "installing the cookbook" do
113
+
114
+ before do
115
+ expect(http_client).to receive(:get).
116
+ with(url_path).
117
+ and_return(raw_metadata)
118
+ expect(http_client).to receive(:streaming_request).
119
+ with(recipe_url).
120
+ and_yield(recipe_filehandle)
121
+ expect(http_client).to receive(:streaming_request).
122
+ with(root_file_url).
123
+ and_yield(root_file_filehandle)
124
+ end
125
+
126
+ it "installs the cookbook to the desired install path" do
127
+ expect(Dir).to_not exist(install_path)
128
+
129
+ installer.install
130
+
131
+ expect(Dir).to exist(install_path)
132
+ expect(Dir.glob("#{install_path}/**/*")).to match_array(expected_installed_files)
133
+ end
134
+
135
+ end
136
+
137
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cookbook-omnifetch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamie Winsor
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2017-05-17 00:00:00.000000000 Z
16
+ date: 2017-11-03 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: mixlib-archive
@@ -82,11 +82,14 @@ files:
82
82
  - lib/cookbook-omnifetch/artifactserver.rb
83
83
  - lib/cookbook-omnifetch/base.rb
84
84
  - lib/cookbook-omnifetch/chef_server.rb
85
+ - lib/cookbook-omnifetch/chef_server_artifact.rb
85
86
  - lib/cookbook-omnifetch/exceptions.rb
86
87
  - lib/cookbook-omnifetch/git.rb
87
88
  - lib/cookbook-omnifetch/github.rb
88
89
  - lib/cookbook-omnifetch/integration.rb
90
+ - lib/cookbook-omnifetch/metadata_based_installer.rb
89
91
  - lib/cookbook-omnifetch/path.rb
92
+ - lib/cookbook-omnifetch/threaded_job_queue.rb
90
93
  - lib/cookbook-omnifetch/version.rb
91
94
  - spec/fixtures/cookbooks/example_cookbook-0.5.0/README.md
92
95
  - spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb
@@ -102,9 +105,11 @@ files:
102
105
  - spec/unit/artifactory_spec.rb
103
106
  - spec/unit/artifactserver_spec.rb
104
107
  - spec/unit/base_spec.rb
108
+ - spec/unit/chef_server_artifact_spec.rb
105
109
  - spec/unit/chef_server_spec.rb
106
110
  - spec/unit/exceptions_spec.rb
107
111
  - spec/unit/git_spec.rb
112
+ - spec/unit/metadata_based_installer_spec.rb
108
113
  - spec/unit/path_spec.rb
109
114
  homepage: http://www.getchef.com/
110
115
  licenses:
@@ -126,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
131
  version: '0'
127
132
  requirements: []
128
133
  rubyforge_project:
129
- rubygems_version: 2.6.11
134
+ rubygems_version: 2.6.13
130
135
  signing_key:
131
136
  specification_version: 4
132
137
  summary: Library code to fetch Chef cookbooks from a variety of sources to a local
@@ -146,7 +151,9 @@ test_files:
146
151
  - spec/unit/artifactory_spec.rb
147
152
  - spec/unit/artifactserver_spec.rb
148
153
  - spec/unit/base_spec.rb
154
+ - spec/unit/chef_server_artifact_spec.rb
149
155
  - spec/unit/chef_server_spec.rb
150
156
  - spec/unit/exceptions_spec.rb
151
157
  - spec/unit/git_spec.rb
158
+ - spec/unit/metadata_based_installer_spec.rb
152
159
  - spec/unit/path_spec.rb