batali 0.2.10 → 0.2.12

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 26d1612a3534961100e76d695b09186fdfd8fa31
4
- data.tar.gz: 4e7804e3d98b4cfe9f9fde3be6a78357945cffd6
3
+ metadata.gz: aa18c8e69b93424e57b8317616ce7724cb9978ef
4
+ data.tar.gz: 7f1399c6617d115a1d80150e955872c90ea7bcce
5
5
  SHA512:
6
- metadata.gz: dd028fa13dd2eb38f133d309f6c402add91bd70665dd9c55dec1db2da13312765e43bbd499eac4550e4aba292169d94a80c03fe4965eca5244c07892dffbe9c5
7
- data.tar.gz: e2c1f1f064380fabc81c4366477af941fc60172cbf00840e649a2c7aa5d9501b0b84ea05c71ad013a9dd540fbfcaee6e7e6c54cf5523f53f1eca5a9fbffd239d
6
+ metadata.gz: ab46ecf666ecb4e50e8a235cfb67eb5b6288d4e0b0292854eb6ca6252b5abc54ca3ed1bcc1091962f9d96d53768bc51812fdef57e10e5ab2996c8803f7d4196b
7
+ data.tar.gz: ab32f312eade8efe395430641ae97539fe05e21b255b8a27149a19e946a4b7c00ae04c58615f17f1e8e81b29125aa771e211e88d68fdcb4f808eba66875f51e8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # v0.2.12
2
+ * Update home directory path generation to use `Dir.home`
3
+ * Detect resolution type from manifest files
4
+ * Add support for chefignore file (also allows `.chefignore`)
5
+ * Introduce automatic cookbook constraint detection
6
+
1
7
  # v0.2.10
2
8
  * Parse full metadata file on path sources to properly discover all deps (#26)
3
9
  * Allow source to be optionally defined for Units
data/README.md CHANGED
@@ -235,6 +235,28 @@ the dry run option to see what upgrades are available without actually changing
235
235
  $ batali resolve --no-least-impact --dry-run
236
236
  ```
237
237
 
238
+ ### Automatic cookbook discovery
239
+
240
+ Tired of tracking constraints in multiple places when using Chef Environment `cookbook_versions`
241
+ for environment specific constraints? Let Batali manage it for you! Define your `Batali` file
242
+ to enable automatic discovery:
243
+
244
+ ```ruby
245
+ Batali.define do
246
+ source 'https://example.com'
247
+ discover true
248
+ end
249
+ ```
250
+
251
+ That's it! Now you can resolve for the infrastructure:
252
+
253
+ ```
254
+ $ batali resolve --infrastructure
255
+ ```
256
+
257
+ which will generate a resulting manifest that includes all required cookbook versions to
258
+ satisfiy constraints defined by all environments.
259
+
238
260
  ## Configuration
239
261
 
240
262
  Batali can be configured via the `.batali` file. The contents of the file can be in YAML,
data/batali.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.require_path = 'lib'
12
12
  s.license = 'Apache 2.0'
13
13
  s.add_runtime_dependency 'attribute_struct', '~> 0.2.14'
14
- s.add_runtime_dependency 'grimoire', '~> 0.2.3'
14
+ s.add_runtime_dependency 'grimoire', '~> 0.2.6'
15
15
  s.add_runtime_dependency 'bogo', '~> 0.1.20'
16
16
  s.add_runtime_dependency 'bogo-cli', '~> 0.1.18'
17
17
  s.add_runtime_dependency 'bogo-config', '~> 0.1.10'
data/lib/batali/b_file.rb CHANGED
@@ -84,6 +84,7 @@ module Batali
84
84
  attribute :cookbook, Cookbook, :multiple => true, :required => true, :coerce => BFile.cookbook_coerce
85
85
  end
86
86
 
87
+ attribute :discover, [TrueClass, FalseClass], :required => true, :default => false
87
88
  attribute :restrict, Restriction, :multiple => true, :coerce => lambda{|v|
88
89
  Restriction.new(:cookbook => v.first, :source => v.last.to_smash[:source])
89
90
  }
@@ -104,6 +105,156 @@ module Batali
104
105
  ckbk
105
106
  }
106
107
 
108
+ # Search environments for cookbooks and restraints
109
+ #
110
+ # @return [TrueClass]
111
+ def auto_discover!(environment=nil)
112
+ debug 'Starting cookbook auto-discovery'
113
+ unless(discover)
114
+ raise 'Attempting to perform auto-discovery but auto-discovery is not enabled!'
115
+ end
116
+ environment_items = Dir.glob(File.join(File.dirname(path), 'environments', '*.{json,rb}')).map do |e_path|
117
+ result = parse_environment(e_path)
118
+ if(result[:name] && result[:cookbooks])
119
+ Smash.new(
120
+ result[:name] => result[:cookbooks]
121
+ )
122
+ end
123
+ end.compact.inject(Smash.new){|m,n| m.merge(n)}
124
+ environment_items.each do |e_name, items|
125
+ next if environment && e_name != environment
126
+ debug "Discovery processing of environment: #{e_name}"
127
+ items.each do |ckbk_name, constraints|
128
+ ckbk = cookbook.detect do |c|
129
+ c.name == ckbk_name
130
+ end
131
+ if(ckbk)
132
+ new_constraints = ckbk.constraint.dup
133
+ new_constraints += constraints
134
+ requirement = UnitRequirement.new(*new_constraints)
135
+ new_constraints = flatten_constraints(requirement.requirements)
136
+ debug "Discovery merged constraints for #{ckbk.name}: #{new_constraints.inspect}"
137
+ ckbk.constraint.replace(new_constraints)
138
+ else
139
+ debug "Discovery added cookbook #{ckbk_name}: #{constraints.inspect}"
140
+ cookbook.push(
141
+ Cookbook.new(
142
+ :name => ckbk_name,
143
+ :constraint => constraints
144
+ )
145
+ )
146
+ end
147
+ end
148
+ end
149
+ debug 'Completed cookbook auto-discovery'
150
+ true
151
+ end
152
+
153
+ protected
154
+
155
+ # Convert constraint for merging
156
+ #
157
+ # @param constraint [String]
158
+ # @param [Array<String>]
159
+ def convert_constraint(constraint)
160
+ comp, ver = constraint.split(' ', 2).map(&:strip)
161
+ if(comp == '~>')
162
+ ver = UnitVersion.new(ver)
163
+ [">= #{ver}", "< #{ver.bump}"]
164
+ else
165
+ [constraint]
166
+ end
167
+ end
168
+
169
+ # Consume list of constraints and generate compressed list that
170
+ # satisfies all defined constraints.
171
+ #
172
+ # @param constraints [Array<Array<String, UnitVersion>>]
173
+ # @return [Array<Array<String, UnitVersion>>]
174
+ # @note if an explict constraint is provided, only it will be
175
+ # returned
176
+ def flatten_constraints(constraints)
177
+ grouped = constraints.group_by(&:first)
178
+ grouped = Smash[
179
+ grouped.map do |comp, items|
180
+ versions = items.map(&:last)
181
+ if(comp.start_with?('>'))
182
+ [comp, [versions.min]]
183
+ elsif(comp.start_with?('<'))
184
+ [comp, [versions.max]]
185
+ else
186
+ [comp, versions]
187
+ end
188
+ end
189
+ ]
190
+ if(grouped['='])
191
+ grouped['>='] ||= []
192
+ grouped['<='] ||= []
193
+ grouped['='].each do |ver|
194
+ grouped['>='] << ver
195
+ grouped['<='] << ver
196
+ end
197
+ grouped.delete('=')
198
+ end
199
+ if(grouped['>'] || grouped['>='])
200
+ if(grouped['>='] && (grouped['>'].nil? || grouped['>='].min <= grouped['>'].min))
201
+ grouped['>='] = [grouped['>='].min]
202
+ grouped.delete('>')
203
+ else
204
+ grouped['>'] = [grouped['>'].min]
205
+ grouped.delete('>=')
206
+ end
207
+ end
208
+ if(grouped['<'] || grouped['<='])
209
+ if(grouped['<='] && (grouped['<'].nil? || grouped['<='].max >= grouped['<'].max))
210
+ grouped['<='] = [grouped['<='].max]
211
+ grouped.delete('<')
212
+ else
213
+ grouped['<'] = [grouped['<'].max]
214
+ grouped.delete('<=')
215
+ end
216
+ end
217
+ grouped.map do |comp, vers|
218
+ vers.map do |version|
219
+ "#{comp} #{version}"
220
+ end
221
+ end.flatten
222
+ end
223
+
224
+ # Read environment file and return defined cookbook constraints
225
+ #
226
+ # @param path [String] path to environment
227
+ # @return [Smash]
228
+ def parse_environment(path)
229
+ case File.extname(path)
230
+ when '.json'
231
+ env = MultiJson.load(
232
+ File.read(path)
233
+ ).to_smash
234
+ when '.rb'
235
+ struct = Struct.new
236
+ struct.set_state!(:value_collapse => true)
237
+ struct.instance_eval(File.read(path), path, 1)
238
+ env = struct._dump.to_smash
239
+ else
240
+ raise "Unexpected file format encountered! (#{File.extname(path)})"
241
+ end
242
+ Smash.new(
243
+ :name => env[:name],
244
+ :cookbooks => Smash[
245
+ env.fetch(
246
+ :cookbook_versions,
247
+ Smash.new
248
+ ).map{|k,v| [k, v.to_s.split(',')]}
249
+ ]
250
+ )
251
+ end
252
+
253
+ # Proxy debug output
254
+ def debug(s)
255
+ Batali.debug(s)
256
+ end
257
+
107
258
  end
108
259
 
109
260
  end
@@ -28,7 +28,7 @@ module Batali
28
28
  end
29
29
  asset_path = unit.source.asset
30
30
  final_path = File.join(install_path, unit.name)
31
- if(config[:infrastructure])
31
+ if(config[:infrastructure] || (config[:infrastructure].nil? && manifest.infrastructure))
32
32
  final_path << "-#{unit.version}"
33
33
  end
34
34
  begin
@@ -29,7 +29,7 @@ module Batali
29
29
  :system => system,
30
30
  :score_keeper => score_keeper
31
31
  )
32
- if(config[:infrastructure])
32
+ if(config[:infrastructure] || (config[:infrastructure].nil? && manifest.infrastructure))
33
33
  infrastructure_resolution(solv)
34
34
  else
35
35
  single_path_resolution(solv)
@@ -7,8 +7,10 @@ module Batali
7
7
  class Update < Batali::Command
8
8
 
9
9
  def execute!
10
- Resolve.new(options.merge(:ui => ui), arguments).execute!
11
- Install.new(options.merge(:ui => ui), arguments).execute!
10
+ Resolve.new(opts.merge(:ui => ui), arguments).execute!
11
+ if(opts[:install])
12
+ Install.new(opts.merge(:ui => ui, :install => {}), arguments).execute!
13
+ end
12
14
  end
13
15
 
14
16
  end
@@ -26,7 +26,11 @@ module Batali
26
26
  # TODO: Add directory traverse searching
27
27
  path = config.fetch(:file, File.join(Dir.pwd, 'Batali'))
28
28
  ui.verbose "Loading Batali file from: #{path}"
29
- BFile.new(path)
29
+ bfile = BFile.new(path)
30
+ if(bfile.discover)
31
+ bfile.auto_discover!
32
+ end
33
+ bfile
30
34
  end
31
35
  end
32
36
 
@@ -46,7 +50,7 @@ module Batali
46
50
  # @return [String] path to local cache
47
51
  def cache_directory(*args)
48
52
  memoize(['cache_directory', *args].join('_')) do
49
- directory = config.fetch(:cache_directory, File.expand_path('~/.batali/cache'))
53
+ directory = config.fetch(:cache_directory, File.join(Dir.home, '.batali/cache'))
50
54
  ui.debug "Cache directory to persist cookbooks: #{directory}"
51
55
  unless(args.empty?)
52
56
  directory = File.join(directory, *args.map(&:to_s))
data/lib/batali/git.rb CHANGED
@@ -47,7 +47,7 @@ module Batali
47
47
  klass.class_eval do
48
48
  attribute :url, String, :required => true, :equivalent => true
49
49
  attribute :ref, String, :required => true, :equivalent => true
50
- attribute :cache, String, :default => File.expand_path('~/.batali/cache/git')
50
+ attribute :cache, String, :default => File.join(Dir.home, '.batali/cache/git')
51
51
  end
52
52
  end
53
53
 
@@ -19,7 +19,7 @@ module Batali
19
19
  attribute :endpoint, String, :required => true
20
20
  attribute :force_update, [TrueClass, FalseClass], :required => true, :default => false
21
21
  attribute :update_interval, Integer, :required => true, :default => 60
22
- attribute :cache, String, :default => File.expand_path('~/.batali/cache/remote_site'), :required => true
22
+ attribute :cache, String, :default => File.join(Dir.home, '.batali/cache/remote_site'), :required => true
23
23
 
24
24
  def initialize(*_)
25
25
  super
@@ -8,6 +8,11 @@ module Batali
8
8
  # Path based source
9
9
  class Path < Source
10
10
 
11
+ # @return [Array<String>] default ignore globs
12
+ DEFAULT_IGNORE = ['.git*']
13
+ # @return [Array<String>] valid ignore file names
14
+ IGNORE_FILE = ['chefignore', '.chefignore']
15
+
11
16
  include Bogo::Memoization
12
17
 
13
18
  attribute :path, String, :required => true, :equivalent => true
@@ -16,7 +21,24 @@ module Batali
16
21
  def asset
17
22
  memoize(:asset) do
18
23
  dir = Dir.mktmpdir
19
- FileUtils.cp_r(File.join(path, '.'), dir)
24
+ chefignore = IGNORE_FILE.map do |c_name|
25
+ c_path = File.join(path, c_name)
26
+ c_path if File.exists?(c_path)
27
+ end.compact.first
28
+ chefignore = chefignore ? File.readlines(chefignore) : []
29
+ chefignore += DEFAULT_IGNORE
30
+ chefignore.uniq!
31
+ files_to_copy = Dir.glob(File.join(path, '{.[^.]*,**}', '**', '{*,*.*,.*}'))
32
+ files_to_copy = files_to_copy.map do |file_path|
33
+ next unless File.file?(file_path)
34
+ relative_path = file_path.sub("#{path}/", '')
35
+ relative_path unless chefignore.detect{|ig| File.fnmatch(ig, relative_path)}
36
+ end.compact
37
+ files_to_copy.each do |relative_path|
38
+ new_path = File.join(dir, relative_path)
39
+ FileUtils.mkdir_p(File.dirname(new_path))
40
+ FileUtils.cp(File.join(path, relative_path), new_path)
41
+ end
20
42
  dir
21
43
  end
22
44
  end
@@ -44,7 +44,7 @@ module Batali
44
44
  def cache_directory
45
45
  memoize(:cache_directory) do
46
46
  unless(@cache)
47
- @cache = File.expand_path('~/.batali/cache/remote_site')
47
+ @cache = File.join(Dir.home, '.batali/cache/remote_site')
48
48
  end
49
49
  ['entitystore', 'metastore'].each do |leaf|
50
50
  FileUtils.mkdir_p(File.join(cache, leaf))
@@ -1,4 +1,4 @@
1
1
  module Batali
2
2
  # Current version
3
- VERSION = Gem::Version.new('0.2.10')
3
+ VERSION = Gem::Version.new('0.2.12')
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: batali
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.10
4
+ version: 0.2.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-27 00:00:00.000000000 Z
11
+ date: 2015-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: attribute_struct
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.2.3
33
+ version: 0.2.6
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.2.3
40
+ version: 0.2.6
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bogo
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -228,4 +228,3 @@ signing_key:
228
228
  specification_version: 4
229
229
  summary: Magic
230
230
  test_files: []
231
- has_rdoc: