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