pdk 1.10.0 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +50 -1
- data/lib/pdk.rb +16 -1
- data/lib/pdk/analytics.rb +28 -0
- data/lib/pdk/analytics/client/google_analytics.rb +138 -0
- data/lib/pdk/analytics/client/noop.rb +23 -0
- data/lib/pdk/analytics/util.rb +17 -0
- data/lib/pdk/cli.rb +37 -0
- data/lib/pdk/cli/build.rb +2 -0
- data/lib/pdk/cli/bundle.rb +2 -1
- data/lib/pdk/cli/convert.rb +2 -0
- data/lib/pdk/cli/exec.rb +28 -1
- data/lib/pdk/cli/new/class.rb +2 -0
- data/lib/pdk/cli/new/defined_type.rb +2 -0
- data/lib/pdk/cli/new/module.rb +2 -0
- data/lib/pdk/cli/new/provider.rb +2 -0
- data/lib/pdk/cli/new/task.rb +2 -0
- data/lib/pdk/cli/test.rb +0 -1
- data/lib/pdk/cli/test/unit.rb +13 -10
- data/lib/pdk/cli/update.rb +21 -0
- data/lib/pdk/cli/util.rb +35 -0
- data/lib/pdk/cli/util/interview.rb +7 -1
- data/lib/pdk/cli/validate.rb +9 -2
- data/lib/pdk/config.rb +94 -0
- data/lib/pdk/config/errors.rb +5 -0
- data/lib/pdk/config/json.rb +23 -0
- data/lib/pdk/config/namespace.rb +273 -0
- data/lib/pdk/config/validator.rb +31 -0
- data/lib/pdk/config/value.rb +94 -0
- data/lib/pdk/config/yaml.rb +31 -0
- data/lib/pdk/generate/module.rb +3 -2
- data/lib/pdk/logger.rb +21 -1
- data/lib/pdk/module/build.rb +58 -0
- data/lib/pdk/module/convert.rb +1 -1
- data/lib/pdk/module/metadata.rb +1 -0
- data/lib/pdk/module/templatedir.rb +24 -5
- data/lib/pdk/module/update_manager.rb +2 -2
- data/lib/pdk/report/event.rb +3 -3
- data/lib/pdk/template_file.rb +1 -1
- data/lib/pdk/tests/unit.rb +10 -12
- data/lib/pdk/util.rb +9 -0
- data/lib/pdk/util/bundler.rb +5 -9
- data/lib/pdk/util/filesystem.rb +37 -0
- data/lib/pdk/util/puppet_version.rb +1 -1
- data/lib/pdk/util/ruby_version.rb +16 -6
- data/lib/pdk/util/template_uri.rb +72 -43
- data/lib/pdk/util/version.rb +1 -1
- data/lib/pdk/util/windows.rb +1 -0
- data/lib/pdk/util/windows/api_types.rb +0 -7
- data/lib/pdk/util/windows/file.rb +1 -1
- data/lib/pdk/util/windows/string.rb +1 -1
- data/lib/pdk/validate/base_validator.rb +8 -6
- data/lib/pdk/validate/puppet/puppet_syntax.rb +1 -1
- data/lib/pdk/validate/ruby/rubocop.rb +1 -1
- data/lib/pdk/version.rb +1 -1
- data/locales/pdk.pot +223 -114
- metadata +103 -50
@@ -0,0 +1,31 @@
|
|
1
|
+
module PDK
|
2
|
+
class Config
|
3
|
+
# A collection of predefined validators for use with {PDK::Config::Value}.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# value :enabled do
|
7
|
+
# validate PDK::Config::Validator.boolean
|
8
|
+
# end
|
9
|
+
module Validator
|
10
|
+
# @return [Hash{Symbol => [Proc,String]}] a {PDK::Config::Value}
|
11
|
+
# validator that ensures that the value is either a TrueClass or
|
12
|
+
# FalseClass.
|
13
|
+
def self.boolean
|
14
|
+
{
|
15
|
+
proc: ->(value) { [true, false].include?(value) },
|
16
|
+
message: _('must be a boolean true or false'),
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Hash{Symbol => [Proc,String]}] a {PDK::Config::Value}
|
21
|
+
# validator that ensures that the value is a String that matches the
|
22
|
+
# regex for a version 4 UUID.
|
23
|
+
def self.uuid
|
24
|
+
{
|
25
|
+
proc: ->(value) { value.match(%r{\A\h{8}(?:-\h{4}){3}-\h{12}\z}) },
|
26
|
+
message: _('must be a version 4 UUID'),
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module PDK
|
2
|
+
class Config
|
3
|
+
# A class for describing the value of a {PDK::Config} setting.
|
4
|
+
#
|
5
|
+
# Generally, this is never instantiated manually, but is instead
|
6
|
+
# instantiated by passing a block to {PDK::Config::Namespace#value}.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# PDK::Config::Namespace.new('analytics') do
|
11
|
+
# value :disabled do
|
12
|
+
# validate PDK::Config::Validator.boolean
|
13
|
+
# default_to { false }
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
class Value
|
17
|
+
# Initialises an empty value definition.
|
18
|
+
#
|
19
|
+
# @param name [String,Symbol] the name of the value.
|
20
|
+
def initialize(name)
|
21
|
+
@name = name
|
22
|
+
@validators = []
|
23
|
+
end
|
24
|
+
|
25
|
+
# Assign a validator to the value.
|
26
|
+
#
|
27
|
+
# @param validator [Hash{Symbol => [Proc,String]}]
|
28
|
+
# @option validator [Proc] :proc a lambda that takes the value to be
|
29
|
+
# validated as the argument and returns `true` if the value is valid.
|
30
|
+
# @option validator [String] :message a description of what the validator
|
31
|
+
# is testing for, that is displayed to the user as part of the error
|
32
|
+
# message for invalid values.
|
33
|
+
#
|
34
|
+
# @raise [ArgumentError] if not passed a Hash.
|
35
|
+
# @raise [ArgumentError] if the Hash doesn't have a `:proc` key that
|
36
|
+
# contains a Proc.
|
37
|
+
# @raise [ArgumentError] if the Hash doesn't have a `:message` key that
|
38
|
+
# contains a String.
|
39
|
+
#
|
40
|
+
# @return [nil]
|
41
|
+
def validate(validator)
|
42
|
+
raise ArgumentError, _('validator must be a Hash') unless validator.is_a?(Hash)
|
43
|
+
raise ArgumentError, _('the :proc key must contain a Proc') unless validator.key?(:proc) && validator[:proc].is_a?(Proc)
|
44
|
+
raise ArgumentError, _('the :message key must contain a String') unless validator.key?(:message) && validator[:message].is_a?(String)
|
45
|
+
|
46
|
+
@validators << validator
|
47
|
+
end
|
48
|
+
|
49
|
+
# Validate a value against the assigned validators.
|
50
|
+
#
|
51
|
+
# @param key [String] the name of the value being validated.
|
52
|
+
# @param value [Object] the value being validated.
|
53
|
+
#
|
54
|
+
# @raise [ArgumentError] if any of the assigned validators fail to
|
55
|
+
# validate the value.
|
56
|
+
#
|
57
|
+
# @return [nil]
|
58
|
+
def validate!(key, value)
|
59
|
+
@validators.each do |validator|
|
60
|
+
next if validator[:proc].call(value)
|
61
|
+
|
62
|
+
raise ArgumentError, _('%{key} %{message}') % {
|
63
|
+
key: key,
|
64
|
+
message: validator[:message],
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Assign a default value.
|
70
|
+
#
|
71
|
+
# @param block [Proc] a block that is lazy evaluated when necessary in
|
72
|
+
# order to determine the default value.
|
73
|
+
#
|
74
|
+
# @return [nil]
|
75
|
+
def default_to(&block)
|
76
|
+
raise ArgumentError, _('must be passed a block') unless block_given?
|
77
|
+
@default_to = block
|
78
|
+
end
|
79
|
+
|
80
|
+
# Evaluate the default value block.
|
81
|
+
#
|
82
|
+
# @return [Object,nil] the result of evaluating the block given to
|
83
|
+
# {#default_to}, or `nil` if the value has no default.
|
84
|
+
def default
|
85
|
+
default? ? @default_to.call : nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [Boolean] true if the value has a default value block.
|
89
|
+
def default?
|
90
|
+
!@default_to.nil?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'pdk/config/namespace'
|
2
|
+
|
3
|
+
module PDK
|
4
|
+
class Config
|
5
|
+
class YAML < Namespace
|
6
|
+
def parse_data(data, filename)
|
7
|
+
return {} if data.nil? || data.empty?
|
8
|
+
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
::YAML.safe_load(data, [Symbol], [], true)
|
12
|
+
rescue Psych::SyntaxError => e
|
13
|
+
raise PDK::Config::LoadError, _('Syntax error when loading %{file}: %{error}') % {
|
14
|
+
file: filename,
|
15
|
+
error: "#{e.problem} #{e.context}",
|
16
|
+
}
|
17
|
+
rescue Psych::DisallowedClass => e
|
18
|
+
raise PDK::Config::LoadError, _('Unsupported class in %{file}: %{error}') % {
|
19
|
+
file: filename,
|
20
|
+
error: e.message,
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def serialize_data(data)
|
25
|
+
require 'yaml'
|
26
|
+
|
27
|
+
::YAML.dump(data)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/pdk/generate/module.rb
CHANGED
@@ -124,7 +124,7 @@ module PDK
|
|
124
124
|
defaults['name'] = "#{opts[:username]}-#{opts[:module_name]}" unless opts[:module_name].nil?
|
125
125
|
defaults['author'] = PDK.answers['author'] unless PDK.answers['author'].nil?
|
126
126
|
defaults['license'] = PDK.answers['license'] unless PDK.answers['license'].nil?
|
127
|
-
defaults['license'] = opts[:license] if opts.key?
|
127
|
+
defaults['license'] = opts[:license] if opts.key?(:license)
|
128
128
|
|
129
129
|
metadata = PDK::Module::Metadata.new(defaults)
|
130
130
|
module_interview(metadata, opts) unless opts[:'skip-interview']
|
@@ -199,6 +199,7 @@ module PDK
|
|
199
199
|
question: _('What operating systems does this module support?'),
|
200
200
|
help: _('Use the up and down keys to move between the choices, space to select and enter to continue.'),
|
201
201
|
required: true,
|
202
|
+
type: :multi_select,
|
202
203
|
choices: PDK::Module::Metadata::OPERATING_SYSTEMS,
|
203
204
|
default: PDK::Module::Metadata::DEFAULT_OPERATING_SYSTEMS.map do |os_name|
|
204
205
|
# tty-prompt uses a 1-index
|
@@ -252,7 +253,7 @@ module PDK
|
|
252
253
|
else
|
253
254
|
questions.reject! { |q| q[:name] == 'module_name' } if opts.key?(:module_name)
|
254
255
|
questions.reject! { |q| q[:name] == 'license' } if opts.key?(:license)
|
255
|
-
questions.reject! { |q| q[:forge_only] } unless opts
|
256
|
+
questions.reject! { |q| q[:forge_only] } unless opts[:'full-interview']
|
256
257
|
end
|
257
258
|
|
258
259
|
interview.add_questions(questions)
|
data/lib/pdk/logger.rb
CHANGED
@@ -6,17 +6,37 @@ module PDK
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class Logger < ::Logger
|
9
|
+
WRAP_COLUMN_LIMIT = 78
|
10
|
+
|
9
11
|
def initialize
|
10
12
|
super(STDERR)
|
13
|
+
@sent_messages = {}
|
11
14
|
|
12
15
|
# TODO: Decide on output format.
|
13
16
|
self.formatter = proc do |severity, _datetime, _progname, msg|
|
14
|
-
"pdk (#{severity}):
|
17
|
+
prefix = "pdk (#{severity}): "
|
18
|
+
if msg.is_a?(Hash)
|
19
|
+
if msg.fetch(:wrap, false)
|
20
|
+
wrap_pattern = %r{(.{1,#{WRAP_COLUMN_LIMIT - prefix.length}})(\s+|\Z)}
|
21
|
+
"#{prefix}#{msg[:text].gsub(wrap_pattern, "\\1\n#{' ' * prefix.length}")}\n"
|
22
|
+
else
|
23
|
+
"#{prefix}#{msg[:text]}\n"
|
24
|
+
end
|
25
|
+
else
|
26
|
+
"#{prefix}#{msg}\n"
|
27
|
+
end
|
15
28
|
end
|
16
29
|
|
17
30
|
self.level = ::Logger::INFO
|
18
31
|
end
|
19
32
|
|
33
|
+
def warn_once(*args)
|
34
|
+
hash = args.inspect.hash
|
35
|
+
return if (@sent_messages[::Logger::WARN] ||= {}).key?(hash)
|
36
|
+
@sent_messages[::Logger::WARN][hash] = true
|
37
|
+
warn(*args)
|
38
|
+
end
|
39
|
+
|
20
40
|
def enable_debug_output
|
21
41
|
self.level = ::Logger::DEBUG
|
22
42
|
end
|
data/lib/pdk/module/build.rb
CHANGED
@@ -120,8 +120,14 @@ module PDK
|
|
120
120
|
elsif File.symlink?(path)
|
121
121
|
warn_symlink(path)
|
122
122
|
else
|
123
|
+
validate_ustar_path!(relative_path.to_path)
|
123
124
|
FileUtils.cp(path, dest_path, preserve: true)
|
124
125
|
end
|
126
|
+
rescue ArgumentError => e
|
127
|
+
raise PDK::CLI::ExitWithError, _(
|
128
|
+
'%{message} Please rename the file or exclude it from the package ' \
|
129
|
+
'by adding it to the .pdkignore file in your module.',
|
130
|
+
) % { message: e.message }
|
125
131
|
end
|
126
132
|
|
127
133
|
# Check if the given path matches one of the patterns listed in the
|
@@ -152,6 +158,58 @@ module PDK
|
|
152
158
|
}
|
153
159
|
end
|
154
160
|
|
161
|
+
# Checks if the path length will fit into the POSIX.1-1998 (ustar) tar
|
162
|
+
# header format.
|
163
|
+
#
|
164
|
+
# POSIX.1-2001 (which allows paths of infinite length) was adopted by GNU
|
165
|
+
# tar in 2004 and is supported by minitar 0.7 and above. Unfortunately
|
166
|
+
# much of the Puppet ecosystem still uses minitar 0.6.1.
|
167
|
+
#
|
168
|
+
# POSIX.1-1998 tar format does not allow for paths greater than 256 bytes,
|
169
|
+
# or paths that can't be split into a prefix of 155 bytes (max) and
|
170
|
+
# a suffix of 100 bytes (max).
|
171
|
+
#
|
172
|
+
# This logic was pretty much copied from the private method
|
173
|
+
# {Archive::Tar::Minitar::Writer#split_name}.
|
174
|
+
#
|
175
|
+
# @param path [String] the relative path to be added to the tar file.
|
176
|
+
#
|
177
|
+
# @raise [ArgumentError] if the path is too long or could not be split.
|
178
|
+
#
|
179
|
+
# @return [nil]
|
180
|
+
def validate_ustar_path!(path)
|
181
|
+
if path.bytesize > 256
|
182
|
+
raise ArgumentError, _("The path '%{path}' is longer than 256 bytes.") % {
|
183
|
+
path: path,
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
if path.bytesize <= 100
|
188
|
+
prefix = ''
|
189
|
+
else
|
190
|
+
parts = path.split(File::SEPARATOR)
|
191
|
+
newpath = parts.pop
|
192
|
+
nxt = ''
|
193
|
+
|
194
|
+
loop do
|
195
|
+
nxt = parts.pop || ''
|
196
|
+
break if newpath.bytesize + 1 + nxt.bytesize >= 100
|
197
|
+
newpath = File.join(nxt, newpath)
|
198
|
+
end
|
199
|
+
|
200
|
+
prefix = File.join(*parts, nxt)
|
201
|
+
path = newpath
|
202
|
+
end
|
203
|
+
|
204
|
+
return unless path.bytesize > 100 || prefix.bytesize > 155
|
205
|
+
|
206
|
+
raise ArgumentError, _(
|
207
|
+
"'%{path}' could not be split at a directory separator into two " \
|
208
|
+
'parts, the first having a maximum length of 155 bytes and the ' \
|
209
|
+
'second having a maximum length of 100 bytes.',
|
210
|
+
) % { path: path }
|
211
|
+
end
|
212
|
+
|
155
213
|
# Creates a gzip compressed tarball of the build directory.
|
156
214
|
#
|
157
215
|
# If the destination package already exists, it will be removed before
|
data/lib/pdk/module/convert.rb
CHANGED
data/lib/pdk/module/metadata.rb
CHANGED
@@ -108,6 +108,7 @@ module PDK
|
|
108
108
|
raise ArgumentError, _('Invalid JSON in metadata.json: %{msg}') % { msg: e.message }
|
109
109
|
end
|
110
110
|
|
111
|
+
data['template-url'] = PDK::Util::TemplateURI.default_template_uri.metadata_format if PDK::Util.package_install? && data['template-url'] == PDK::Util::TemplateURI::PACKAGED_TEMPLATE_KEYWORD
|
111
112
|
new(data)
|
112
113
|
end
|
113
114
|
|
@@ -9,6 +9,7 @@ module PDK
|
|
9
9
|
module Module
|
10
10
|
class TemplateDir
|
11
11
|
attr_accessor :module_metadata
|
12
|
+
attr_reader :uri
|
12
13
|
|
13
14
|
# Initialises the TemplateDir object with the path or URL to the template
|
14
15
|
# and the block of code to run to be run while the template is available.
|
@@ -61,7 +62,7 @@ module PDK
|
|
61
62
|
}
|
62
63
|
end
|
63
64
|
end
|
64
|
-
@
|
65
|
+
@uri = uri
|
65
66
|
|
66
67
|
@init = init
|
67
68
|
@moduleroot_dir = File.join(@path, 'moduleroot')
|
@@ -74,6 +75,9 @@ module PDK
|
|
74
75
|
|
75
76
|
@module_metadata = module_metadata
|
76
77
|
|
78
|
+
template_type = uri.default? ? 'default' : 'custom'
|
79
|
+
PDK.analytics.event('TemplateDir', 'initialize', label: template_type)
|
80
|
+
|
77
81
|
yield self
|
78
82
|
ensure
|
79
83
|
# If we cloned a git repo to get the template, remove the clone once
|
@@ -94,7 +98,7 @@ module PDK
|
|
94
98
|
def metadata
|
95
99
|
{
|
96
100
|
'pdk-version' => PDK::Util::Version.version_string,
|
97
|
-
'template-url' =>
|
101
|
+
'template-url' => uri.metadata_format,
|
98
102
|
'template-ref' => cache_template_ref(@path),
|
99
103
|
}
|
100
104
|
end
|
@@ -212,6 +216,7 @@ module PDK
|
|
212
216
|
raise ArgumentError, _("The template at '%{path}' does not contain a 'moduleroot_init/' directory, which indicates you are using an older style of template. Before continuing please use the --template-url flag when running the pdk new commands to pass a new style template.") % { path: @path }
|
213
217
|
# rubocop:enable Metrics/LineLength Style/GuardClause
|
214
218
|
end
|
219
|
+
# rubocop:enable Style/GuardClause
|
215
220
|
end
|
216
221
|
|
217
222
|
# Get a list of template files in the template directory.
|
@@ -254,14 +259,28 @@ module PDK
|
|
254
259
|
|
255
260
|
if @config.nil?
|
256
261
|
conf_defaults = read_config(config_path)
|
257
|
-
sync_config = read_config(sync_config_path) unless sync_config_path.nil?
|
262
|
+
@sync_config = read_config(sync_config_path) unless sync_config_path.nil?
|
258
263
|
@config = conf_defaults
|
259
|
-
@config.deep_merge!(sync_config, knockout_prefix: '---') unless sync_config.nil?
|
264
|
+
@config.deep_merge!(@sync_config, knockout_prefix: '---') unless @sync_config.nil?
|
260
265
|
end
|
261
266
|
file_config = @config.fetch(:global, {})
|
262
267
|
file_config['module_metadata'] = @module_metadata
|
263
268
|
file_config.merge!(@config.fetch(dest_path, {})) unless dest_path.nil?
|
264
|
-
file_config.merge!(@config)
|
269
|
+
file_config.merge!(@config).tap do |c|
|
270
|
+
if uri.default?
|
271
|
+
file_value = if c['unmanaged']
|
272
|
+
'unmanaged'
|
273
|
+
elsif c['delete']
|
274
|
+
'deleted'
|
275
|
+
elsif @sync_config && @sync_config.key?(dest_path)
|
276
|
+
'customized'
|
277
|
+
else
|
278
|
+
'default'
|
279
|
+
end
|
280
|
+
|
281
|
+
PDK.analytics.event('TemplateDir', 'file', label: dest_path, value: file_value)
|
282
|
+
end
|
283
|
+
end
|
265
284
|
end
|
266
285
|
|
267
286
|
# Generates a hash of data from a given yaml file location.
|
@@ -73,7 +73,7 @@ module PDK
|
|
73
73
|
def changed?(path)
|
74
74
|
changes[:added].any? { |add| add[:path] == path } ||
|
75
75
|
changes[:removed].include?(path) ||
|
76
|
-
changes[:modified].
|
76
|
+
changes[:modified].key?(path)
|
77
77
|
end
|
78
78
|
|
79
79
|
# Apply any pending changes stored in the UpdateManager to the module.
|
@@ -174,7 +174,7 @@ module PDK
|
|
174
174
|
|
175
175
|
diffs = Diff::LCS.diff(old_lines, new_lines)
|
176
176
|
|
177
|
-
return
|
177
|
+
return if diffs.empty?
|
178
178
|
|
179
179
|
file_mtime = File.stat(path).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
|
180
180
|
now = Time.now.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
|
data/lib/pdk/report/event.rb
CHANGED
@@ -267,7 +267,7 @@ module PDK
|
|
267
267
|
# @return [Integer] the provided value, converted into an Integer if
|
268
268
|
# necessary.
|
269
269
|
def sanitise_line(value)
|
270
|
-
return
|
270
|
+
return if value.nil?
|
271
271
|
|
272
272
|
valid_types = [String, Integer]
|
273
273
|
if RUBY_VERSION.split('.')[0..1].join('.').to_f < 2.4
|
@@ -292,7 +292,7 @@ module PDK
|
|
292
292
|
# @return [Integer] the provided value, converted into an Integer if
|
293
293
|
# necessary.
|
294
294
|
def sanitise_column(value)
|
295
|
-
return
|
295
|
+
return if value.nil?
|
296
296
|
|
297
297
|
valid_types = [String, Integer]
|
298
298
|
if RUBY_VERSION.split('.')[0..1].join('.').to_f < 2.4
|
@@ -317,7 +317,7 @@ module PDK
|
|
317
317
|
#
|
318
318
|
# @return [Array] Array of stack trace lines with less relevant lines excluded
|
319
319
|
def sanitise_trace(value)
|
320
|
-
return
|
320
|
+
return if value.nil?
|
321
321
|
|
322
322
|
valid_types = [Array]
|
323
323
|
|