-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from chef/tm/chef_server_source
Implement a Chef Server fetcher
- Loading branch information
Showing
2 changed files
with
183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
require 'cookbook-omnifetch/base' | ||
|
||
module CookbookOmnifetch | ||
class CookbookMetadata | ||
|
||
FILE_TYPES = [ | ||
:resources, | ||
:providers, | ||
:recipes, | ||
:definitions, | ||
:libraries, | ||
:attributes, | ||
:files, | ||
:templates, | ||
:root_files | ||
].freeze | ||
|
||
def initialize(metadata) | ||
@metadata = metadata | ||
end | ||
|
||
def files(&block) | ||
FILE_TYPES.each do |type| | ||
next unless @metadata.has_key?(type.to_s) | ||
|
||
@metadata[type.to_s].each do |file| | ||
yield file["url"], file["path"] | ||
end | ||
end | ||
end | ||
end | ||
|
||
class ChefserverLocation < BaseLocation | ||
|
||
attr_reader :cookbook_version | ||
|
||
def initialize(dependency, options = {}) | ||
super | ||
@cookbook_version = options[:version] | ||
@http_client = options[:http_client] | ||
@uri ||= options[:artifactserver] | ||
end | ||
|
||
def repo_host | ||
@host ||= URI.parse(uri).host | ||
end | ||
|
||
def cookbook_name | ||
dependency.name | ||
end | ||
|
||
def install | ||
FileUtils.mkdir_p(staging_root) unless staging_root.exist? | ||
md = http_client.get("/cookbooks/#{cookbook_name}/#{cookbook_version}") | ||
CookbookMetadata.new(md).files do |url, path| | ||
stage = staging_path.join(path) | ||
FileUtils.mkdir_p(File.dirname(stage)) | ||
|
||
http_client.streaming_request(url) do |tempfile| | ||
tempfile.close | ||
FileUtils.mv(tempfile.path, stage) | ||
end | ||
end | ||
FileUtils.mv(staging_path, install_path) | ||
end | ||
|
||
# Determine if this revision is installed. | ||
# | ||
# @return [Boolean] | ||
def installed? | ||
install_path.exist? | ||
end | ||
|
||
def http_client | ||
@http_client | ||
end | ||
|
||
# The path where this cookbook would live in the store, if it were | ||
# installed. | ||
# | ||
# @return [Pathname, nil] | ||
def install_path | ||
@install_path ||= CookbookOmnifetch.storage_path.join(cache_key) | ||
end | ||
|
||
def cache_key | ||
"#{dependency.name}-#{cookbook_version}" | ||
end | ||
|
||
# The path where tarballs are downloaded to and unzipped. On certain platforms | ||
# you have a better chance of getting an atomic move if your temporary working | ||
# directory is on the same device/volume as the destination. To support this, | ||
# we use a staging directory located under the cache path under the rather mild | ||
# assumption that everything under the cache path is going to be on one device. | ||
# | ||
# Do not create anything under this directory that isn't randomly named and | ||
# remember to release your files once you are done. | ||
# | ||
# @return [Pathname] | ||
def staging_root | ||
Pathname.new(CookbookOmnifetch.cache_path).join('.cache_tmp', 'artifactserver') | ||
end | ||
|
||
def staging_path | ||
staging_root.join(cache_key) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
require "spec_helper" | ||
require "cookbook-omnifetch/chef_server.rb" | ||
|
||
module CookbookOmnifetch | ||
METADATA = { | ||
"recipes" => [ | ||
{"name"=>"default.rb", "path"=>"recipes/default.rb", "checksum"=>"a6be794cdd2eb44d38fdf17f792a0d0d", "specificity"=>"default", "url"=>"https://example.com/recipes/default.rb"}, | ||
], | ||
"root_files"=>[ | ||
{"name"=>"metadata.rb", "path"=>"metadata.rb", "checksum"=>"5b346119e5e41ab99500608decac8dca", "specificity"=>"default", "url"=>"https://example.com/metadata.rb"}, | ||
], | ||
} | ||
|
||
describe CookbookMetadata do | ||
let(:cb_metadata) { CookbookMetadata.new(METADATA) } | ||
|
||
it "yields a set of paths and urls" do | ||
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"]) | ||
end | ||
end | ||
|
||
describe ChefserverLocation do | ||
|
||
let(:http_client) { double("Http Client") } | ||
|
||
let(:cb_metadata) { CookbookMetadata.new(METADATA) } | ||
|
||
let(:test_root) { Dir.mktmpdir(nil) } | ||
|
||
let(:storage_path) { File.join(test_root, "storage") } | ||
|
||
let(:cache_path) { File.join(test_root, "cache") } | ||
|
||
let(:constraint) { double("Constraint") } | ||
|
||
let(:dependency) { double("Dependency", name: cookbook_name, constraint: constraint) } | ||
|
||
let(:cookbook_name) { "example" } | ||
let(:cookbook_version) { "0.5.0" } | ||
|
||
let(:cookbook_fixture_path) { fixtures_path.join("cookbooks/example_cookbook") } | ||
|
||
let(:remote_path) { File.join(test_root, "remote") } | ||
let(:options) { {version: cookbook_version, http_client: http_client } } | ||
|
||
let(:cookbook_files) { %w". .. metadata.rb recipes" } | ||
subject(:chef_server_location) { described_class.new(dependency, options) } | ||
|
||
before do | ||
allow(CookbookOmnifetch).to receive(:storage_path).and_return(Pathname.new(storage_path)) | ||
allow(CookbookOmnifetch).to receive(:cache_path).and_return(cache_path) | ||
allow_any_instance_of(File).to receive(:close).and_return(true) | ||
FileUtils.cp_r(cookbook_fixture_path, remote_path) | ||
FileUtils.mkdir_p(storage_path) | ||
end | ||
|
||
after do | ||
FileUtils.rm_r(test_root) | ||
end | ||
|
||
it "installs the cookbook to the desired install path" do | ||
expect(http_client).to receive(:get).with("/cookbooks/example/0.5.0").and_return(METADATA) | ||
expect(http_client).to receive(:streaming_request).twice do |url, &block| | ||
path = url.split("/", 4)[3] | ||
path = File.join(remote_path, path) | ||
block.call(File.open(path)) | ||
end | ||
|
||
chef_server_location.install | ||
|
||
expect(Dir).to exist(chef_server_location.install_path) | ||
expect(Dir.entries(chef_server_location.install_path)).to match_array(cookbook_files) | ||
end | ||
end | ||
end |