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 +4 -4
- data/lib/cookbook-omnifetch.rb +17 -0
- data/lib/cookbook-omnifetch/chef_server.rb +10 -62
- data/lib/cookbook-omnifetch/chef_server_artifact.rb +72 -0
- data/lib/cookbook-omnifetch/integration.rb +5 -0
- data/lib/cookbook-omnifetch/metadata_based_installer.rb +94 -0
- data/lib/cookbook-omnifetch/threaded_job_queue.rb +46 -0
- data/lib/cookbook-omnifetch/version.rb +1 -1
- data/spec/unit/chef_server_artifact_spec.rb +74 -0
- data/spec/unit/chef_server_spec.rb +45 -71
- data/spec/unit/metadata_based_installer_spec.rb +137 -0
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64e4ca89bc3c10a6c4bf4e224bb39f5657d1cd12
|
4
|
+
data.tar.gz: c16cfaa9e0b3c9eb655faa3d12a293b6ec166931
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e3354f49b0ed00fd5ab23f6de8dd8994419bb8e15c1d32b5dc85f960c46c1d20af45b8f2ca4cbb0e96a812a57e9a8bc04bba1907a319c13e9a6a323eda673be
|
7
|
+
data.tar.gz: f7841d6c9081275526ba60ece1fd3f349b24d812d9c2bba66110a3c1da6800d0e35e2da1a304f7601e4fda6849e9f78f49c8a127f3c3fcaf4f33dc1f919db3dd
|
data/lib/cookbook-omnifetch.rb
CHANGED
@@ -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
|
54
|
-
|
55
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
6
|
+
let(:http_client) { double("Http Client") }
|
25
7
|
|
26
|
-
|
8
|
+
let(:test_root) { "/some/fake/path" }
|
27
9
|
|
28
|
-
|
10
|
+
let(:storage_path) { File.join(test_root, "storage") }
|
29
11
|
|
30
|
-
|
12
|
+
let(:dependency) { double("Dependency", name: cookbook_name) }
|
31
13
|
|
32
|
-
|
14
|
+
let(:cookbook_name) { "example" }
|
33
15
|
|
34
|
-
|
16
|
+
let(:cookbook_version) { "0.5.0" }
|
35
17
|
|
36
|
-
|
18
|
+
let(:url) { "https://chef.example.com/organizations/example" }
|
37
19
|
|
38
|
-
|
39
|
-
let(:cookbook_version) { "0.5.0" }
|
20
|
+
let(:options) { { chef_server: url, version: cookbook_version, http_client: http_client } }
|
40
21
|
|
41
|
-
|
22
|
+
subject(:chef_server_location) { described_class.new(dependency, options) }
|
42
23
|
|
43
|
-
|
24
|
+
before do
|
25
|
+
allow(CookbookOmnifetch).to receive(:storage_path).and_return(Pathname.new(storage_path))
|
26
|
+
end
|
44
27
|
|
45
|
-
|
46
|
-
|
28
|
+
it "has a URI" do
|
29
|
+
expect(chef_server_location.uri).to eq(url)
|
30
|
+
end
|
47
31
|
|
48
|
-
|
49
|
-
|
32
|
+
it "has an HTTP client" do
|
33
|
+
expect(chef_server_location.http_client).to eq(http_client)
|
34
|
+
end
|
50
35
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
48
|
+
it "has an exact version" do
|
49
|
+
expect(chef_server_location.cookbook_version).to eq("0.5.0")
|
50
|
+
end
|
66
51
|
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
expect(chef_server_location.cookbook_version).to eq("0.5.0")
|
73
|
-
end
|
60
|
+
describe "when installing" do
|
74
61
|
|
75
|
-
|
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.
|
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-
|
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.
|
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
|