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 +4 -4
- data/VERSION +1 -1
- data/bin/berktacular +6 -1
- data/lib/berktacular/berksfile.rb +4 -2
- data/lib/berktacular/berksfile.rb.jeff +274 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd96380cb5328cdcb55bb90c68f82bbf7c880d88
|
4
|
+
data.tar.gz: 3acd725fb400db94b90e9d5c715b93e3e827ff23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 237987cab59af28eb1bbceb9be83025cce24202bce5f6a077ea430a631099926c975a9e7039a3b18a9c87c03f0ee301d4b01c806900dd8dafb57f032990c61a9
|
7
|
+
data.tar.gz: ce2bd2304cad516b5c0cea0718fd29f26d6fc419039017c2dd5dd653f7694a0e76d74b715462e8272269a125e7ce00b6b0e25f857a8a8aef7155e0e89e96ec4e
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.2.
|
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
|
-
|
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.
|
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:
|
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.
|
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:
|