batali 0.2.10 → 0.2.12

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