berkshelf 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. data/.gitignore +20 -0
  2. data/.rbenv-version +1 -0
  3. data/Gemfile +3 -0
  4. data/Guardfile +23 -0
  5. data/LICENSE +22 -0
  6. data/README.rdoc +102 -0
  7. data/Thorfile +47 -0
  8. data/berkshelf.gemspec +39 -0
  9. data/features/config.sample.yml +3 -0
  10. data/features/init_command.feature +40 -0
  11. data/features/install.feature +55 -0
  12. data/features/lockfile.feature +22 -0
  13. data/features/step_definitions/chef_server_steps.rb +13 -0
  14. data/features/step_definitions/cli_steps.rb +56 -0
  15. data/features/step_definitions/filesystem_steps.rb +79 -0
  16. data/features/support/env.rb +55 -0
  17. data/features/update.feature +23 -0
  18. data/features/upload_command.feature +43 -0
  19. data/features/without.feature +25 -0
  20. data/lib/berkshelf.rb +90 -0
  21. data/lib/berkshelf/berksfile.rb +170 -0
  22. data/lib/berkshelf/cached_cookbook.rb +253 -0
  23. data/lib/berkshelf/cookbook_source.rb +139 -0
  24. data/lib/berkshelf/cookbook_source/git_location.rb +54 -0
  25. data/lib/berkshelf/cookbook_source/path_location.rb +27 -0
  26. data/lib/berkshelf/cookbook_source/site_location.rb +206 -0
  27. data/lib/berkshelf/cookbook_store.rb +77 -0
  28. data/lib/berkshelf/core_ext.rb +3 -0
  29. data/lib/berkshelf/core_ext/file.rb +14 -0
  30. data/lib/berkshelf/core_ext/kernel.rb +33 -0
  31. data/lib/berkshelf/core_ext/pathname.rb +24 -0
  32. data/lib/berkshelf/downloader.rb +92 -0
  33. data/lib/berkshelf/dsl.rb +39 -0
  34. data/lib/berkshelf/errors.rb +20 -0
  35. data/lib/berkshelf/generator_files/Berksfile.erb +3 -0
  36. data/lib/berkshelf/generator_files/chefignore +44 -0
  37. data/lib/berkshelf/git.rb +70 -0
  38. data/lib/berkshelf/init_generator.rb +38 -0
  39. data/lib/berkshelf/lockfile.rb +42 -0
  40. data/lib/berkshelf/resolver.rb +176 -0
  41. data/lib/berkshelf/tx_result.rb +12 -0
  42. data/lib/berkshelf/tx_result_set.rb +37 -0
  43. data/lib/berkshelf/uploader.rb +153 -0
  44. data/lib/berkshelf/version.rb +3 -0
  45. data/lib/chef/knife/berks_init.rb +29 -0
  46. data/lib/chef/knife/berks_install.rb +27 -0
  47. data/lib/chef/knife/berks_update.rb +23 -0
  48. data/lib/chef/knife/berks_upload.rb +39 -0
  49. data/spec/fixtures/Berksfile +3 -0
  50. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/README.md +12 -0
  51. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb +6 -0
  52. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/recipes/default.rb +8 -0
  53. data/spec/fixtures/cookbooks/example_cookbook/README.md +12 -0
  54. data/spec/fixtures/cookbooks/example_cookbook/metadata.rb +6 -0
  55. data/spec/fixtures/cookbooks/example_cookbook/recipes/default.rb +8 -0
  56. data/spec/fixtures/cookbooks/invalid_ruby_files-1.0.0/recipes/default.rb +1 -0
  57. data/spec/fixtures/cookbooks/invalid_template_files-1.0.0/templates/default/broken.erb +1 -0
  58. data/spec/fixtures/cookbooks/nginx-0.100.5/README.md +77 -0
  59. data/spec/fixtures/cookbooks/nginx-0.100.5/attributes/default.rb +65 -0
  60. data/spec/fixtures/cookbooks/nginx-0.100.5/definitions/nginx_site.rb +35 -0
  61. data/spec/fixtures/cookbooks/nginx-0.100.5/files/default/mime.types +73 -0
  62. data/spec/fixtures/cookbooks/nginx-0.100.5/files/ubuntu/mime.types +73 -0
  63. data/spec/fixtures/cookbooks/nginx-0.100.5/libraries/nginxlib.rb +1 -0
  64. data/spec/fixtures/cookbooks/nginx-0.100.5/metadata.rb +91 -0
  65. data/spec/fixtures/cookbooks/nginx-0.100.5/providers/defprovider.rb +1 -0
  66. data/spec/fixtures/cookbooks/nginx-0.100.5/recipes/default.rb +59 -0
  67. data/spec/fixtures/cookbooks/nginx-0.100.5/resources/defresource.rb +1 -0
  68. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/nginx.pill.erb +15 -0
  69. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/plugins/nginx.rb.erb +66 -0
  70. data/spec/fixtures/lockfile_spec/with_lock/Berksfile +1 -0
  71. data/spec/fixtures/lockfile_spec/without_lock/Berksfile.lock +5 -0
  72. data/spec/spec_helper.rb +92 -0
  73. data/spec/support/chef_api.rb +27 -0
  74. data/spec/support/matchers/file_system_matchers.rb +115 -0
  75. data/spec/support/matchers/filepath_matchers.rb +19 -0
  76. data/spec/unit/berkshelf/cached_cookbook_spec.rb +420 -0
  77. data/spec/unit/berkshelf/cookbook_source/git_location_spec.rb +59 -0
  78. data/spec/unit/berkshelf/cookbook_source/path_location_spec.rb +34 -0
  79. data/spec/unit/berkshelf/cookbook_source/site_location_spec.rb +166 -0
  80. data/spec/unit/berkshelf/cookbook_source_spec.rb +194 -0
  81. data/spec/unit/berkshelf/cookbook_store_spec.rb +71 -0
  82. data/spec/unit/berkshelf/cookbookfile_spec.rb +160 -0
  83. data/spec/unit/berkshelf/downloader_spec.rb +82 -0
  84. data/spec/unit/berkshelf/dsl_spec.rb +42 -0
  85. data/spec/unit/berkshelf/git_spec.rb +63 -0
  86. data/spec/unit/berkshelf/init_generator_spec.rb +52 -0
  87. data/spec/unit/berkshelf/lockfile_spec.rb +25 -0
  88. data/spec/unit/berkshelf/resolver_spec.rb +126 -0
  89. data/spec/unit/berkshelf/tx_result_set_spec.rb +77 -0
  90. data/spec/unit/berkshelf/tx_result_spec.rb +21 -0
  91. data/spec/unit/berkshelf/uploader_spec.rb +71 -0
  92. data/spec/unit/berkshelf_spec.rb +29 -0
  93. 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,3 @@
1
+ <% if options[:metadata_entry] -%>
2
+ metadata
3
+ <% 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
@@ -0,0 +1,12 @@
1
+ module Berkshelf
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class TXResult < Struct.new(:status, :message, :source)
4
+ def failed?
5
+ status == :error
6
+ end
7
+
8
+ def success?
9
+ status == :ok
10
+ end
11
+ end
12
+ end