berktacular 1.2.1 → 1.2.2

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: 2b550dd28f7c682c8659d022921d89a65785053a
4
- data.tar.gz: c1c53aeeb4858818ccddb394229c2f38d8d3caa8
3
+ metadata.gz: dd96380cb5328cdcb55bb90c68f82bbf7c880d88
4
+ data.tar.gz: 3acd725fb400db94b90e9d5c715b93e3e827ff23
5
5
  SHA512:
6
- metadata.gz: 638e0ef1f4a61e6754482c2b371dc1167a4671d27e4c297867ef2f1674fa82381e49f3cf37e2c3bdca30b8ad02fc9bd85a166c98d88276808f23d803272eeceb
7
- data.tar.gz: 548d71a31fd89eb2781657a40fe85f075e0b3a0faf4956cdad663f3212ce77e538a111a4fcdd5d80f1b785fba75f17c5d5f3052f74822de32c800b5b1f69daaa
6
+ metadata.gz: 237987cab59af28eb1bbceb9be83025cce24202bce5f6a077ea430a631099926c975a9e7039a3b18a9c87c03f0ee301d4b01c806900dd8dafb57f032990c61a9
7
+ data.tar.gz: ce2bd2304cad516b5c0cea0718fd29f26d6fc419039017c2dd5dd653f7694a0e76d74b715462e8272269a125e7ce00b6b0e25f857a8a8aef7155e0e89e96ec4e
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.1
1
+ 1.2.2
data/bin/berktacular CHANGED
@@ -18,6 +18,7 @@ upload = false
18
18
  berks_conf = nil
19
19
  knife_conf = nil
20
20
  workdir = nil
21
+ parent_env_dir = nil
21
22
  verbose = false
22
23
  source_list = []
23
24
  versions_only = false
@@ -69,6 +70,9 @@ options = OptionParser.new do |opts|
69
70
  workdir = w
70
71
  preserve = true
71
72
  end
73
+ opts.on("--parent-env-directory DIR", String, "Directory to search for parent environments. Defaults to the directory of the env file") do |t|
74
+ parent_env_dir = t
75
+ end
72
76
  opts.on("-m PATH", "--multi-cookbook-dir PATH", String,
73
77
  "Treat the given directory as a multi-cookbook directory, and allow referring to cookbooks " \
74
78
  "under this directory when the 'rel' configuration parameter is specified and the version " \
@@ -161,7 +165,8 @@ b = Berktacular::Berksfile.new(
161
165
  github_token: github_token,
162
166
  verbose: verbose,
163
167
  source_list: source_list,
164
- multi_cookbook_dir: multi_cookbook_dir
168
+ multi_cookbook_dir: multi_cookbook_dir,
169
+ parent_env_dir: parent_env_dir
165
170
  )
166
171
  b.check_updates if check
167
172
  puts "#{b}" if printit
@@ -46,7 +46,8 @@ module Berktacular
46
46
  source_list: opts.has_key?(:source_list) ? opts[:source_list] : [],
47
47
  multi_cookbook_dir: opts.has_key?(:multi_cookbook_dir) ? opts[:multi_cookbook_dir] : nil,
48
48
  versions_only: opts.has_key?(:versions_only) ? opts[:versions_only] : false,
49
- max_depth: opts.has_key?(:max_depth) ? opts[:max_depth] : 10
49
+ max_depth: opts.has_key?(:max_depth) ? opts[:max_depth] : 10,
50
+ parent_env_dir: opts.has_key?(:parent_env_dir) ? opts[:parent_env_dir] : nil
50
51
  }
51
52
  @counter = 0
52
53
  @env_hash = expand_env_file(env_path)
@@ -254,10 +255,11 @@ module Berktacular
254
255
  raise "Environment file '#{env_file}' does not exist!"
255
256
  end
256
257
  if env.has_key?("parent")
258
+ parent_env_dir = @opts[:parent_env_dir].nil? ? File.dirname(env_file) : @opts[:parent_env_dir]
257
259
  parent = env["parent"]
258
260
  if !File.exists?(parent)
259
261
  parent = File.join(
260
- File.dirname(env_file),
262
+ parent_env_dir,
261
263
  parent
262
264
  )
263
265
  end
@@ -0,0 +1,274 @@
1
+ require 'ostruct'
2
+ require 'json'
3
+ require 'solve'
4
+
5
+ # Taken from http://stackoverflow.com/a/25990044
6
+ class ::Hash
7
+ def deep_merge(second)
8
+ merger = proc { |key, v1, v2|
9
+ Hash === v1 && Hash === v2 ?
10
+ v1.merge(v2, &merger) :
11
+ [:undefined, nil, :nil].include?(v2) ? v1 : v2
12
+ }
13
+ self.merge(second, &merger)
14
+ end
15
+ end
16
+
17
+ module Berktacular
18
+
19
+ # This class represents a Berksfile
20
+
21
+ class Berksfile
22
+
23
+ # @!attribute [r] name
24
+ # @return [String] the name of the environment.
25
+ # @!attribute [r] description
26
+ # @return [String] a description of the enviroment.
27
+ # @!attribute [r] installed
28
+ # @return [Hash] a hash of installed cookbook directories.
29
+ # @!attribute [r] missing_deps
30
+ # @return [Hash] a hash of cookbooks missing dependencies after calling verify.
31
+ attr_reader :name, :description, :installed, :missing_deps
32
+
33
+ # Creates a new Berksfile from a chef environment file.
34
+ #
35
+ # @param environment [Hash] a parsed JSON chef environment config.
36
+ # @option opts [String] :github_token (nil) the github token to use.
37
+ # @option opts [True,False] :upgrade (False) whether or not to check for upgraded cookbooks.
38
+ # @option opts [True,False] :verbose (False) be more verbose.
39
+ # @option opts [Array<String>] :source_list additional Berkshelf API sources to include in the
40
+ # generated Berksfile.
41
+ def initialize( env_path, opts = {})
42
+ @opts = {
43
+ upgrade: opts.has_key?(:upgrade) ? opts[:upgrade] : false,
44
+ github_token: opts.has_key?(:github_token) ? opts[:github_token] : nil,
45
+ verbose: opts.has_key?(:verbose) ? opts[:verbose] : false,
46
+ source_list: opts.has_key?(:source_list) ? opts[:source_list] : [],
47
+ multi_cookbook_dir: opts.has_key?(:multi_cookbook_dir) ? opts[:multi_cookbook_dir] : nil,
48
+ versions_only: opts.has_key?(:versions_only) ? opts[:versions_only] : false,
49
+ max_depth: opts.has_key?(:max_depth) ? opts[:max_depth] : 10
50
+ }
51
+ @counter = 0
52
+ @env_hash = expand_env_file(env_path)
53
+
54
+ @name = @env_hash['name'] || nil
55
+ @description = @env_hash['description'] || nil
56
+ @cookbook_versions = @env_hash['cookbook_versions'] || {}
57
+ @cookbook_locations = @env_hash['cookbook_locations'] || {}
58
+ @installed = {}
59
+ # only connect once, pass the client to each cookbook. and only if needed
60
+ connect_to_git if @opts[:upgrade]
61
+ @opts[:source_list] = (@opts[:source_list] + ["https://supermarket.chef.io"]).uniq
62
+ end
63
+
64
+ # @return [Hash] representation of the env_file.
65
+ def env_file
66
+ if @opts[:upgrade]
67
+ cookbooks.each do |book|
68
+ @env_hash['cookbook_versions'][book.name] = book.version_specifier
69
+ end
70
+ end
71
+ @env_hash
72
+ end
73
+
74
+ # @return [String] representation of the env_file in pretty json.
75
+ def env_file_json
76
+ if @opts[:upgrade]
77
+ cookbooks.each do |book|
78
+ @env_hash['cookbook_versions'][book.name] = book.version_specifier
79
+ end
80
+ end
81
+ JSON.pretty_generate(@env_hash)
82
+ end
83
+
84
+ # @param workdir [String] the directory in which to install. If nil, Berktacular.best_temp_dir is used.
85
+ # @return [String] the directory path where the cookbooks were installed.
86
+ def install(workdir = nil)
87
+ if workdir
88
+ FileUtils.mkdir_p(workdir)
89
+ else
90
+ workdir = Berktacular.best_temp_dir
91
+ end
92
+ unless @installed[workdir]
93
+ # remove the Berksfile.lock if it exists (it shouldn't).
94
+ berksfile = File.join(workdir, "Berksfile")
95
+ lck = berksfile + ".lock"
96
+ cookbooks = File.join(workdir, "cookbooks")
97
+ FileUtils.rm(lck) if File.exists? lck
98
+ File.write(berksfile, self)
99
+ Berktacular.run_command("berks vendor -d --berksfile #{berksfile} #{cookbooks}")
100
+ @installed[workdir] = {berksfile: berksfile, lck: lck, cookbooks: cookbooks}
101
+ end
102
+ workdir
103
+ end
104
+
105
+ # @params workdir [String] the directory in which to install. If nil, Berktacular.best_temp_dir is used.
106
+ # @return [True,False] the status of the verify.
107
+ def verify(workdir = nil)
108
+ require 'ridley'
109
+ @missing_deps = {}
110
+ workdir = install(workdir)
111
+ versions = {}
112
+ dependencies = {}
113
+ Dir["#{@installed[workdir][:cookbooks]}/*"].each do |cookbook_dir|
114
+ next unless File.directory?(cookbook_dir)
115
+ metadata_candidates = ['rb', 'json'].map {|ext| File.join(cookbook_dir, "metadata.#{ext}") }
116
+ metadata_path = metadata_candidates.find {|f| File.exists?(f) }
117
+ raise "Metadata file not found: #{metadata_candidates}" if metadata_path.nil?
118
+ metadata =
119
+ metadata_path =~ /\.json$/ ? metadata_from_json(IO.read(metadata_path)) :
120
+ Ridley::Chef::Cookbook::Metadata.from_file(metadata_path)
121
+ cookbook_name = metadata.name
122
+ name_from_path = File.basename(cookbook_dir)
123
+ unless cookbook_name == name_from_path
124
+ if cookbook_name.empty?
125
+ puts "Cookbook #{name_from_path} has no name specified in metadata.rb"
126
+ cookbook_name = name_from_path
127
+ else
128
+ warn "Cookbook name from metadata.rb does not match the directory name!",
129
+ "metadata.rb: '#{cookbook_name}'",
130
+ "cookbook directory name: '#{name_from_path}'"
131
+ end
132
+ end
133
+ versions[cookbook_name] = metadata.version
134
+ dependencies[cookbook_name] = metadata.dependencies
135
+ end
136
+ errors = false
137
+ dependencies.each do |name, deps|
138
+ deps.each do |dep_name, constraint|
139
+ actual_version = versions[dep_name]
140
+ if !actual_version
141
+ @missing_deps[name] = "#{name}-#{versions[name]} depends on #{dep_name} which was not installed!"
142
+ warn @missing_deps[name]
143
+ errors = true
144
+ elsif constraint != [] # some cookbooks have '[]' as a dependency in their json metadata
145
+ constraint_obj = begin
146
+ Semverse::Constraint.new(constraint)
147
+ rescue Semverse::InvalidConstraintFormat => ex
148
+ warn "Could not parse version constraint '#{constraint}' " +
149
+ "for dependency '#{dep_name}' of cookbook '#{name}'"
150
+ raise ex
151
+ end
152
+
153
+ unless constraint_obj.satisfies?(actual_version)
154
+ @missing_deps[name] = "#{name}-#{versions[name]} depends on #{dep_name} #{constraint} but #{dep_name} is #{actual_version}!"
155
+ warn @missing_deps[name]
156
+ errors = true
157
+ end
158
+ end
159
+ end
160
+ end
161
+ !errors
162
+ end
163
+
164
+ # @param berks_conf [String] path to the berkshelf config file to use.
165
+ # @param knife_conf [String] path to the knife config file to use.
166
+ # @param workdir [String] Path to use as the working directory.
167
+ # @default Berktacular.best_temp_dir
168
+ # @return [True] or raise on error.
169
+ def upload(berks_conf, knife_conf, workdir=nil)
170
+ raise "No berks config, required for upload" unless berks_conf && File.exists?(berks_conf)
171
+ raise "No knife config, required for upload" unless knife_conf && File.exists?(knife_conf)
172
+ workdir = install(workdir)
173
+ new_env_file = File.join(workdir, @name + ".json")
174
+ File.write(new_env_file, env_file_json)
175
+ Berktacular.run_command("berks upload --berksfile #{@installed[workdir][:berksfile]} -c #{berks_conf}")
176
+ Berktacular.run_command("knife environment from file #{new_env_file} -c #{knife_conf}")
177
+ end
178
+
179
+ # param workdir [String,nil] the workdir to remove. If nil, remove all installed working directories.
180
+ def clean(workdir = nil)
181
+ if workdir
182
+ Fileutils.rm_r(workdir)
183
+ @installed.delete(workdir)
184
+ else
185
+ # clean them all
186
+ @installed.keys.each { |d| FileUtils.rm_r(d) }
187
+ @installed = {}
188
+ end
189
+ end
190
+
191
+ # @param [IO] where to write the data.
192
+ def print_berksfile( io = STDOUT )
193
+ io.puts to_s
194
+ end
195
+
196
+ # @return [String] the berksfile as a String object
197
+ def to_s
198
+ str = ''
199
+ str << "# Name: '#{@name}'\n" if @name
200
+ str << "# Description: #{@description}\n\n" if @description
201
+ str << "# This file is auto-generated, changes will be overwritten\n"
202
+ str << "# Modify the .json environment file and regenerate this Berksfile to make changes.\n\n"
203
+
204
+ @opts[:source_list].each do |source_url|
205
+ str << "source '#{source_url}'\n"
206
+ end
207
+ str << "\n"
208
+ cookbooks.each { |l| str << l.to_s << "\n" }
209
+ str
210
+ end
211
+
212
+ # @return [Array] a list of Cookbook objects for this environment.
213
+ def cookbooks
214
+ @cookbooks ||= @cookbook_versions.sort.map do |book, version|
215
+ Cookbook.new(book, version, @cookbook_locations[book], @opts)
216
+ end
217
+ end
218
+
219
+ # print out the cookbooks that have newer version available on github.
220
+ def check_updates
221
+ connect_to_git
222
+ cookbooks.each do |b|
223
+ candidates = b.check_updates
224
+ next unless candidates.any?
225
+ puts "Cookbook: #{b.name} (auto upgrade: #{b.auto_upgrade ? 'enabled' : 'disabled'})",
226
+ "\tCurrent: #{b.version_number}",
227
+ "\tUpdates: #{candidates.join(", ")}"
228
+ end
229
+ end
230
+
231
+ private
232
+
233
+ # connect to github using the token in @opts[:github_token].
234
+ # @return [Octokit::Client] a connected github client.
235
+ def connect_to_git
236
+ raise "No token given, can't connect to git" unless @opts[:github_token]
237
+ puts "Connecting to git with supplied github_token" if @opts[:verbose]
238
+ require 'octokit'
239
+ @opts[:git_client] ||= Octokit::Client.new(
240
+ access_token: @opts[:github_token],
241
+ auto_paginate: true
242
+ )
243
+ end
244
+
245
+ # recursively expand env_file @opts[:max_depth] times.
246
+ # @return [Hash] of merged env_file
247
+ def expand_env_file(env_file)
248
+ raise "Exceeded max depth!" if @counter > @opts[:max_depth]
249
+ @counter += 1
250
+ env = {}
251
+ if File.exists?(env_file)
252
+ env = JSON.parse( File.read(env_file) )
253
+ else
254
+ raise "Environment file '#{env_file}' does not exist!"
255
+ end
256
+ if env.has_key?("parent")
257
+ parent = env["parent"]
258
+ if !File.exists?(parent)
259
+ parent = File.join(
260
+ File.dirname(env_file),
261
+ parent
262
+ )
263
+ end
264
+ env = expand_env_file( parent ).deep_merge( env )
265
+ end
266
+ env
267
+ end
268
+
269
+ def metadata_from_json(json_str)
270
+ OpenStruct.new(JSON.parse(json_str))
271
+ end
272
+
273
+ end
274
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berktacular
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Harvey-Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-09 00:00:00.000000000 Z
11
+ date: 2016-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: solve
@@ -143,6 +143,7 @@ files:
143
143
  - bin/berktacular
144
144
  - lib/berktacular.rb
145
145
  - lib/berktacular/berksfile.rb
146
+ - lib/berktacular/berksfile.rb.jeff
146
147
  - lib/berktacular/cookbook.rb
147
148
  - lib/berktacular/version.rb
148
149
  homepage: https://rubygems.org/gems/berktacular
@@ -165,9 +166,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
166
  version: '0'
166
167
  requirements: []
167
168
  rubyforge_project:
168
- rubygems_version: 2.4.8
169
+ rubygems_version: 2.6.6
169
170
  signing_key:
170
171
  specification_version: 4
171
172
  summary: Parse chef env files, generates a Berksfile and verifies it.
172
173
  test_files: []
173
- has_rdoc: