Skip to content

Commit

Permalink
Merge pull request #6 from chef/tm/chef_server_source
Browse files Browse the repository at this point in the history
Implement a Chef Server fetcher
  • Loading branch information
thommay authored Sep 2, 2016
2 parents 110c09e + 8904afb commit 1f8a177
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 0 deletions.
108 changes: 108 additions & 0 deletions lib/cookbook-omnifetch/chef_server.rb
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
75 changes: 75 additions & 0 deletions spec/unit/chef_server_spec.rb
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

0 comments on commit 1f8a177

Please sign in to comment.