berkshelf 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/.rbenv-version +1 -0
- data/Gemfile +3 -0
- data/Guardfile +23 -0
- data/LICENSE +22 -0
- data/README.rdoc +102 -0
- data/Thorfile +47 -0
- data/berkshelf.gemspec +39 -0
- data/features/config.sample.yml +3 -0
- data/features/init_command.feature +40 -0
- data/features/install.feature +55 -0
- data/features/lockfile.feature +22 -0
- data/features/step_definitions/chef_server_steps.rb +13 -0
- data/features/step_definitions/cli_steps.rb +56 -0
- data/features/step_definitions/filesystem_steps.rb +79 -0
- data/features/support/env.rb +55 -0
- data/features/update.feature +23 -0
- data/features/upload_command.feature +43 -0
- data/features/without.feature +25 -0
- data/lib/berkshelf.rb +90 -0
- data/lib/berkshelf/berksfile.rb +170 -0
- data/lib/berkshelf/cached_cookbook.rb +253 -0
- data/lib/berkshelf/cookbook_source.rb +139 -0
- data/lib/berkshelf/cookbook_source/git_location.rb +54 -0
- data/lib/berkshelf/cookbook_source/path_location.rb +27 -0
- data/lib/berkshelf/cookbook_source/site_location.rb +206 -0
- data/lib/berkshelf/cookbook_store.rb +77 -0
- data/lib/berkshelf/core_ext.rb +3 -0
- data/lib/berkshelf/core_ext/file.rb +14 -0
- data/lib/berkshelf/core_ext/kernel.rb +33 -0
- data/lib/berkshelf/core_ext/pathname.rb +24 -0
- data/lib/berkshelf/downloader.rb +92 -0
- data/lib/berkshelf/dsl.rb +39 -0
- data/lib/berkshelf/errors.rb +20 -0
- data/lib/berkshelf/generator_files/Berksfile.erb +3 -0
- data/lib/berkshelf/generator_files/chefignore +44 -0
- data/lib/berkshelf/git.rb +70 -0
- data/lib/berkshelf/init_generator.rb +38 -0
- data/lib/berkshelf/lockfile.rb +42 -0
- data/lib/berkshelf/resolver.rb +176 -0
- data/lib/berkshelf/tx_result.rb +12 -0
- data/lib/berkshelf/tx_result_set.rb +37 -0
- data/lib/berkshelf/uploader.rb +153 -0
- data/lib/berkshelf/version.rb +3 -0
- data/lib/chef/knife/berks_init.rb +29 -0
- data/lib/chef/knife/berks_install.rb +27 -0
- data/lib/chef/knife/berks_update.rb +23 -0
- data/lib/chef/knife/berks_upload.rb +39 -0
- data/spec/fixtures/Berksfile +3 -0
- data/spec/fixtures/cookbooks/example_cookbook-0.5.0/README.md +12 -0
- data/spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb +6 -0
- data/spec/fixtures/cookbooks/example_cookbook-0.5.0/recipes/default.rb +8 -0
- data/spec/fixtures/cookbooks/example_cookbook/README.md +12 -0
- data/spec/fixtures/cookbooks/example_cookbook/metadata.rb +6 -0
- data/spec/fixtures/cookbooks/example_cookbook/recipes/default.rb +8 -0
- data/spec/fixtures/cookbooks/invalid_ruby_files-1.0.0/recipes/default.rb +1 -0
- data/spec/fixtures/cookbooks/invalid_template_files-1.0.0/templates/default/broken.erb +1 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/README.md +77 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/attributes/default.rb +65 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/definitions/nginx_site.rb +35 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/files/default/mime.types +73 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/files/ubuntu/mime.types +73 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/libraries/nginxlib.rb +1 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/metadata.rb +91 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/providers/defprovider.rb +1 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/recipes/default.rb +59 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/resources/defresource.rb +1 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/nginx.pill.erb +15 -0
- data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/plugins/nginx.rb.erb +66 -0
- data/spec/fixtures/lockfile_spec/with_lock/Berksfile +1 -0
- data/spec/fixtures/lockfile_spec/without_lock/Berksfile.lock +5 -0
- data/spec/spec_helper.rb +92 -0
- data/spec/support/chef_api.rb +27 -0
- data/spec/support/matchers/file_system_matchers.rb +115 -0
- data/spec/support/matchers/filepath_matchers.rb +19 -0
- data/spec/unit/berkshelf/cached_cookbook_spec.rb +420 -0
- data/spec/unit/berkshelf/cookbook_source/git_location_spec.rb +59 -0
- data/spec/unit/berkshelf/cookbook_source/path_location_spec.rb +34 -0
- data/spec/unit/berkshelf/cookbook_source/site_location_spec.rb +166 -0
- data/spec/unit/berkshelf/cookbook_source_spec.rb +194 -0
- data/spec/unit/berkshelf/cookbook_store_spec.rb +71 -0
- data/spec/unit/berkshelf/cookbookfile_spec.rb +160 -0
- data/spec/unit/berkshelf/downloader_spec.rb +82 -0
- data/spec/unit/berkshelf/dsl_spec.rb +42 -0
- data/spec/unit/berkshelf/git_spec.rb +63 -0
- data/spec/unit/berkshelf/init_generator_spec.rb +52 -0
- data/spec/unit/berkshelf/lockfile_spec.rb +25 -0
- data/spec/unit/berkshelf/resolver_spec.rb +126 -0
- data/spec/unit/berkshelf/tx_result_set_spec.rb +77 -0
- data/spec/unit/berkshelf/tx_result_spec.rb +21 -0
- data/spec/unit/berkshelf/uploader_spec.rb +71 -0
- data/spec/unit/berkshelf_spec.rb +29 -0
- metadata +411 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
module DSL
|
3
|
+
@@active_group = nil
|
4
|
+
|
5
|
+
def cookbook(*args)
|
6
|
+
source = CookbookSource.new(*args)
|
7
|
+
source.add_group(@@active_group) if @@active_group
|
8
|
+
add_source(source)
|
9
|
+
end
|
10
|
+
|
11
|
+
def group(*args)
|
12
|
+
@@active_group = args
|
13
|
+
yield
|
14
|
+
@@active_group = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def metadata(options = {})
|
18
|
+
path = options[:path] || File.expand_path('.')
|
19
|
+
|
20
|
+
metadata_file = Berkshelf.find_metadata(path)
|
21
|
+
|
22
|
+
unless metadata_file
|
23
|
+
raise CookbookNotFound, "No 'metadata.rb' found at #{path}"
|
24
|
+
end
|
25
|
+
|
26
|
+
metadata = Chef::Cookbook::Metadata.new
|
27
|
+
metadata.from_file(metadata_file.to_s)
|
28
|
+
|
29
|
+
name = if metadata.name.empty? || metadata.name.nil?
|
30
|
+
File.basename(File.dirname(metadata_file))
|
31
|
+
else
|
32
|
+
metadata.name
|
33
|
+
end
|
34
|
+
|
35
|
+
source = CookbookSource.new(name, :path => File.dirname(metadata_file))
|
36
|
+
add_source(source)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
class BerkshelfError < StandardError
|
3
|
+
class << self
|
4
|
+
def status_code(code)
|
5
|
+
define_method(:status_code) { code }
|
6
|
+
define_singleton_method(:status_code) { code }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class BerksfileNotFound < BerkshelfError; status_code(100); end
|
12
|
+
class NoVersionForConstraints < BerkshelfError; status_code(101); end
|
13
|
+
class DownloadFailure < BerkshelfError; status_code(102); end
|
14
|
+
class CookbookNotFound < BerkshelfError; status_code(103); end
|
15
|
+
class GitError < BerkshelfError; status_code(104); end
|
16
|
+
class DuplicateSourceDefined < BerkshelfError; status_code(105); end
|
17
|
+
class NoSolution < BerkshelfError; status_code(106); end
|
18
|
+
class CookbookSyntaxError < BerkshelfError; status_code(107); end
|
19
|
+
class UploadFailure < BerkshelfError; status_code(108); end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Put files/directories that should be ignored in this file.
|
2
|
+
# Lines that start with '# ' are comments.
|
3
|
+
|
4
|
+
## OS
|
5
|
+
.DS_Store
|
6
|
+
Icon?
|
7
|
+
nohup.out
|
8
|
+
|
9
|
+
## EDITORS
|
10
|
+
\#*
|
11
|
+
.#*
|
12
|
+
*~
|
13
|
+
*.sw[a-z]
|
14
|
+
*.bak
|
15
|
+
REVISION
|
16
|
+
TAGS*
|
17
|
+
tmtags
|
18
|
+
*_flymake.*
|
19
|
+
*_flymake
|
20
|
+
*.tmproj
|
21
|
+
.project
|
22
|
+
.settings
|
23
|
+
mkmf.log
|
24
|
+
|
25
|
+
## COMPILED
|
26
|
+
a.out
|
27
|
+
*.o
|
28
|
+
*.pyc
|
29
|
+
*.so
|
30
|
+
|
31
|
+
## OTHER SCM
|
32
|
+
*/.bzr/*
|
33
|
+
*/.hg/*
|
34
|
+
*/.svn/*
|
35
|
+
|
36
|
+
## Don't send rspecs up in cookbook
|
37
|
+
.watchr
|
38
|
+
.rspec
|
39
|
+
spec/*
|
40
|
+
spec/fixtures/*
|
41
|
+
features/*
|
42
|
+
|
43
|
+
# Berkshelf
|
44
|
+
Berksfile
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
class Git
|
3
|
+
class << self
|
4
|
+
def git(*command)
|
5
|
+
out = quietly {
|
6
|
+
%x{ #{git_cmd} #{command.join(' ')} }
|
7
|
+
}
|
8
|
+
error_check
|
9
|
+
|
10
|
+
out.chomp
|
11
|
+
end
|
12
|
+
|
13
|
+
def clone(uri, destination = Dir.mktmpdir)
|
14
|
+
git("clone", uri, destination.to_s)
|
15
|
+
|
16
|
+
error_check
|
17
|
+
|
18
|
+
destination
|
19
|
+
end
|
20
|
+
|
21
|
+
def checkout(repo_path, ref)
|
22
|
+
Dir.chdir repo_path do
|
23
|
+
git("checkout", "-q", ref)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def rev_parse(repo_path)
|
28
|
+
Dir.chdir repo_path do
|
29
|
+
git("rev-parse", "HEAD")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# This is to defeat aliases/shell functions called 'git' and a number of
|
35
|
+
# other problems.
|
36
|
+
#
|
37
|
+
def find_git
|
38
|
+
git_path = nil
|
39
|
+
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
40
|
+
potential_path = File.join(path, 'git')
|
41
|
+
if File.executable?(potential_path)
|
42
|
+
git_path = potential_path
|
43
|
+
break
|
44
|
+
end
|
45
|
+
potential_path = File.join(path, 'git.exe')
|
46
|
+
if File.executable?(potential_path)
|
47
|
+
git_path = potential_path
|
48
|
+
break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
unless git_path
|
53
|
+
raise "Could not find git. Please ensure it is in your path."
|
54
|
+
end
|
55
|
+
|
56
|
+
return git_path
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def git_cmd
|
62
|
+
@git_cmd ||= find_git
|
63
|
+
end
|
64
|
+
|
65
|
+
def error_check
|
66
|
+
raise Berkshelf::GitError, "Did not succeed executing git; check the output above." unless $?.success?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'thor/group'
|
2
|
+
|
3
|
+
module Berkshelf
|
4
|
+
# @author Jamie Winsor <jamie@vialstudios.com>
|
5
|
+
class InitGenerator < Thor::Group
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
class_option :path,
|
9
|
+
:type => :string,
|
10
|
+
:required => true
|
11
|
+
|
12
|
+
class_option :metadata_entry,
|
13
|
+
:type => :boolean,
|
14
|
+
:default => false
|
15
|
+
|
16
|
+
class_option :chefignore,
|
17
|
+
:type => :boolean,
|
18
|
+
:default => false
|
19
|
+
|
20
|
+
def self.source_root
|
21
|
+
File.expand_path(File.join(File.dirname(__FILE__), "generator_files"))
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate
|
25
|
+
template "Berksfile.erb", File.join(target_path, "Berksfile")
|
26
|
+
|
27
|
+
if options[:chefignore]
|
28
|
+
copy_file "chefignore", File.join(target_path, ".chefignore")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def target_path
|
35
|
+
@target_path ||= File.expand_path(options[:path])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
class Lockfile
|
3
|
+
class << self
|
4
|
+
def remove!
|
5
|
+
FileUtils.rm_f DEFAULT_FILENAME
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
DEFAULT_FILENAME = "#{Berkshelf::DEFAULT_FILENAME}.lock".freeze
|
10
|
+
|
11
|
+
attr_reader :sources
|
12
|
+
|
13
|
+
def initialize(sources)
|
14
|
+
@sources = Array(sources)
|
15
|
+
end
|
16
|
+
|
17
|
+
def write(filename = DEFAULT_FILENAME)
|
18
|
+
content = sources.map { |source| get_source_definition(source) }.join("\n")
|
19
|
+
File.open(filename, "wb") { |f| f.write content }
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove!
|
23
|
+
self.class.remove!
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def get_source_definition(source)
|
29
|
+
definition = "cookbook '#{source.name}'"
|
30
|
+
|
31
|
+
if source.location.is_a?(CookbookSource::GitLocation)
|
32
|
+
definition += ", :git => '#{source.location.uri}', :ref => '#{source.location.branch || 'HEAD'}'"
|
33
|
+
elsif source.location.is_a?(CookbookSource::PathLocation)
|
34
|
+
definition += ", :path => '#{source.location.path}'"
|
35
|
+
else
|
36
|
+
definition += ", :locked_version => '#{source.locked_version}'"
|
37
|
+
end
|
38
|
+
|
39
|
+
return definition
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
# @author Jamie Winsor <jamie@vialstudios.com>
|
3
|
+
class Resolver
|
4
|
+
extend Forwardable
|
5
|
+
include DepSelector
|
6
|
+
|
7
|
+
def_delegators :@graph, :package, :packages
|
8
|
+
|
9
|
+
def initialize(downloader, sources = Array.new)
|
10
|
+
@downloader = downloader
|
11
|
+
@graph = DependencyGraph.new
|
12
|
+
@sources = Hash.new
|
13
|
+
|
14
|
+
# Dependencies need to be added AFTER the sources. If they are
|
15
|
+
# not, then one of the dependencies of a source that is added
|
16
|
+
# may take precedence over an explicitly set source that appears
|
17
|
+
# later in the iterator.
|
18
|
+
Array(sources).each do |source|
|
19
|
+
add_source(source, false)
|
20
|
+
end
|
21
|
+
add_sources_dependencies
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [Berkshelf::CookbookSource] source
|
25
|
+
# source to add
|
26
|
+
# @param [Boolean] include_dependencies
|
27
|
+
# if true, after adding the source the dependencies defined in the
|
28
|
+
# sources metadata will be added to the graph and downloaded
|
29
|
+
#
|
30
|
+
# @return [DepSelector::PackageVersion]
|
31
|
+
def add_source(source, include_dependencies = true)
|
32
|
+
raise DuplicateSourceDefined if has_source?(source.name)
|
33
|
+
|
34
|
+
set_source(source.name, source)
|
35
|
+
|
36
|
+
install_or_use_source(source)
|
37
|
+
|
38
|
+
package = add_package(source.name)
|
39
|
+
package_version = add_version(package, Version.new(source.metadata.version))
|
40
|
+
|
41
|
+
add_dependencies(package_version, source.dependencies) if include_dependencies
|
42
|
+
|
43
|
+
package_version
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [DepSelector::PackageVersion] parent_pkgver
|
47
|
+
# the PackageVersion you would like to add the given dependencies to. In this case
|
48
|
+
# the package version is a version of a Cookbook.
|
49
|
+
# @param [Hash] dependencies
|
50
|
+
# A hash containing Cookbook names for keys and version constraint strings for
|
51
|
+
# values. This is the same format obtained by sending the 'dependencies' message
|
52
|
+
# to an instance of Chef::Cookbook::Metadata.
|
53
|
+
#
|
54
|
+
# Example:
|
55
|
+
# {
|
56
|
+
# "build-essential" => ">= 0.0.0",
|
57
|
+
# "ohai" => "~> 1.0.2"
|
58
|
+
# }
|
59
|
+
def add_dependencies(parent_pkgver, dependencies)
|
60
|
+
dependencies.each do |name, constraint|
|
61
|
+
dep_package = add_package(name)
|
62
|
+
parent_pkgver.dependencies << Dependency.new(dep_package, VersionConstraint.new(constraint))
|
63
|
+
|
64
|
+
unless has_source?(name)
|
65
|
+
source = CookbookSource.new(name, constraint)
|
66
|
+
|
67
|
+
install_or_use_source(source)
|
68
|
+
|
69
|
+
dep_pkgver = add_version(dep_package, Version.new(source.metadata.version))
|
70
|
+
add_dependencies(dep_pkgver, source.dependencies)
|
71
|
+
set_source(source.name, source)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [Array<Berkshelf::CookbookSource>]
|
77
|
+
# an array of CookbookSources that are currently added to this resolver
|
78
|
+
def sources
|
79
|
+
@sources.collect { |name, source| source }
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Hash]
|
83
|
+
# a hash containing package names - in this case Cookbook names - as keys and
|
84
|
+
# their locked version as values.
|
85
|
+
#
|
86
|
+
# Example:
|
87
|
+
# {
|
88
|
+
# "nginx" => 0.101.0,
|
89
|
+
# "build-essential" => 1.0.2,
|
90
|
+
# "runit" => 0.15.0,
|
91
|
+
# "bluepill" => 1.0.4,
|
92
|
+
# "ohai" => 1.0.2
|
93
|
+
# }
|
94
|
+
def resolve
|
95
|
+
quietly { selector.find_solution(solution_constraints) }
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param [#to_s] source
|
99
|
+
# name of the source to return
|
100
|
+
#
|
101
|
+
# @return [Berkshelf::CookbookSource]
|
102
|
+
def [](source)
|
103
|
+
@sources[source.to_s]
|
104
|
+
end
|
105
|
+
alias_method :get_source, :[]
|
106
|
+
|
107
|
+
# @param [#to_s] source
|
108
|
+
# the source to test if the resolver has added
|
109
|
+
def has_source?(source)
|
110
|
+
!get_source(source).nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
attr_reader :downloader
|
116
|
+
attr_reader :graph
|
117
|
+
|
118
|
+
# @param [#to_s] source
|
119
|
+
# name of the source to set
|
120
|
+
# @param [CookbookSource] value
|
121
|
+
# source to set as value
|
122
|
+
def []=(source, value)
|
123
|
+
@sources[source.to_s] = value
|
124
|
+
end
|
125
|
+
alias_method :set_source, :[]=
|
126
|
+
|
127
|
+
def install_or_use_source(source)
|
128
|
+
if downloader.downloaded?(source)
|
129
|
+
msg = "Using #{source.name} (#{source.metadata.version})"
|
130
|
+
|
131
|
+
if source.location.is_a?(CookbookSource::PathLocation)
|
132
|
+
msg << " at #{source.location}"
|
133
|
+
end
|
134
|
+
|
135
|
+
Berkshelf.ui.info msg
|
136
|
+
else
|
137
|
+
downloader.download!(source)
|
138
|
+
Berkshelf.ui.info "Installing #{source.name} (#{source.local_version}) from #{source.location}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def selector
|
143
|
+
Selector.new(graph)
|
144
|
+
end
|
145
|
+
|
146
|
+
def solution_constraints
|
147
|
+
constraints = graph.packages.collect do |name, package|
|
148
|
+
SolutionConstraint.new(package)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Add the dependencies of each source to the graph
|
153
|
+
def add_sources_dependencies
|
154
|
+
sources.each do |source|
|
155
|
+
package_version = package(source.name)[Version.new(source.metadata.version)]
|
156
|
+
add_dependencies(package_version, source.dependencies)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# @param [String] name
|
161
|
+
# name of the package to add to the graph
|
162
|
+
def add_package(name)
|
163
|
+
graph.package(name)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Add a version to a package
|
167
|
+
#
|
168
|
+
# @param [DepSelector::Package] package
|
169
|
+
# the package to add a version to
|
170
|
+
# @param [DepSelector::Version] version
|
171
|
+
# the version to add the the package
|
172
|
+
def add_version(package, version)
|
173
|
+
package.add_version(version)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|