cookbook-omnifetch 0.6.0 → 0.7.0

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