berktacular 1.2.1 → 1.2.2

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: 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: