berkshelf 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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