pdk 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +201 -0
- data/README.md +270 -0
- data/exe/pdk +5 -0
- data/lib/pdk.rb +8 -0
- data/lib/pdk/cli.rb +80 -0
- data/lib/pdk/cli/errors.rb +12 -0
- data/lib/pdk/cli/exec.rb +51 -0
- data/lib/pdk/cli/input.rb +28 -0
- data/lib/pdk/cli/new.rb +30 -0
- data/lib/pdk/cli/new/class.rb +45 -0
- data/lib/pdk/cli/new/module.rb +55 -0
- data/lib/pdk/cli/test.rb +23 -0
- data/lib/pdk/cli/tests/unit.rb +52 -0
- data/lib/pdk/cli/util/option_normalizer.rb +44 -0
- data/lib/pdk/cli/util/option_validator.rb +74 -0
- data/lib/pdk/cli/validate.rb +81 -0
- data/lib/pdk/generate.rb +3 -0
- data/lib/pdk/generators/module.rb +139 -0
- data/lib/pdk/generators/puppet_class.rb +51 -0
- data/lib/pdk/generators/puppet_object.rb +213 -0
- data/lib/pdk/i18n.rb +4 -0
- data/lib/pdk/logger.rb +25 -0
- data/lib/pdk/module/metadata.rb +88 -0
- data/lib/pdk/module/templatedir.rb +231 -0
- data/lib/pdk/report.rb +38 -0
- data/lib/pdk/template_file.rb +87 -0
- data/lib/pdk/tests/unit.rb +21 -0
- data/lib/pdk/util.rb +16 -0
- data/lib/pdk/validate.rb +12 -0
- data/lib/pdk/validators/base_validator.rb +14 -0
- data/lib/pdk/validators/metadata.rb +17 -0
- data/lib/pdk/validators/puppet_lint.rb +17 -0
- data/lib/pdk/validators/puppet_parser.rb +17 -0
- data/lib/pdk/validators/ruby_lint.rb +17 -0
- data/lib/pdk/version.rb +3 -0
- data/locales/config.yaml +21 -0
- data/locales/de/pdk.po +250 -0
- data/locales/pdk.pot +215 -0
- metadata +92 -12
data/lib/pdk/i18n.rb
ADDED
data/lib/pdk/logger.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module PDK
|
4
|
+
def self.logger
|
5
|
+
@logger ||= PDK::Logger.new
|
6
|
+
end
|
7
|
+
|
8
|
+
class Logger < ::Logger
|
9
|
+
def initialize
|
10
|
+
# TODO: Decide where log output goes, probably stderr?
|
11
|
+
super(STDOUT)
|
12
|
+
|
13
|
+
# TODO: Decide on output format.
|
14
|
+
self.formatter = proc do |severity,datetime,progname,msg|
|
15
|
+
"pdk (#{severity}): #{msg}\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
self.level = ::Logger::INFO
|
19
|
+
end
|
20
|
+
|
21
|
+
def enable_debug_output
|
22
|
+
self.level = ::Logger::DEBUG
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module PDK
|
4
|
+
module Module
|
5
|
+
class Metadata
|
6
|
+
|
7
|
+
attr_accessor :data
|
8
|
+
|
9
|
+
DEFAULTS = {
|
10
|
+
'name' => nil,
|
11
|
+
'version' => nil,
|
12
|
+
'author' => nil,
|
13
|
+
'summary' => nil,
|
14
|
+
'license' => 'Apache-2.0',
|
15
|
+
'source' => '',
|
16
|
+
'project_page' => nil,
|
17
|
+
'issues_url' => nil,
|
18
|
+
'dependencies' => Set.new.freeze,
|
19
|
+
'data_provider' => nil,
|
20
|
+
}
|
21
|
+
|
22
|
+
def initialize(params = {})
|
23
|
+
@data = DEFAULTS.dup
|
24
|
+
update!(params) if params
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.from_file(metadata_json_path)
|
28
|
+
unless File.file?(metadata_json_path)
|
29
|
+
raise ArgumentError, _("'%{file}' does not exist or is not a file") % {file: metadata_json_path}
|
30
|
+
end
|
31
|
+
|
32
|
+
unless File.readable?(metadata_json_path)
|
33
|
+
raise ArgumentError, _("Unable to open '%{file}' for reading") % {file: metadata_json_path}
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
data = JSON.parse(File.read(metadata_json_path))
|
38
|
+
rescue JSON::JSONError => e
|
39
|
+
raise ArgumentError, _("Invalid JSON in metadata.json: %{msg}") % {msg: e.message}
|
40
|
+
end
|
41
|
+
|
42
|
+
new(data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def update!(data)
|
46
|
+
# TODO: validate all data
|
47
|
+
process_name(data) if data['name']
|
48
|
+
@data.merge!(data)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_json
|
53
|
+
JSON.pretty_generate(@data)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Do basic validation and parsing of the name parameter.
|
59
|
+
def process_name(data)
|
60
|
+
validate_name(data['name'])
|
61
|
+
author, module_name = data['name'].split(/[-\/]/, 2)
|
62
|
+
|
63
|
+
data['author'] ||= author if @data['author'] == DEFAULTS['author']
|
64
|
+
end
|
65
|
+
|
66
|
+
# Validates that the given module name is both namespaced and well-formed.
|
67
|
+
def validate_name(name)
|
68
|
+
return if name =~ /\A[a-z0-9]+[-\/][a-z][a-z0-9_]*\Z/i
|
69
|
+
|
70
|
+
namespace, modname = name.split(/[-\/]/, 2)
|
71
|
+
modname = :namespace_missing if namespace == ''
|
72
|
+
|
73
|
+
err = case modname
|
74
|
+
when nil, '', :namespace_missing
|
75
|
+
"the field must be a dash-separated username and module name"
|
76
|
+
when /[^a-z0-9_]/i
|
77
|
+
"the module name contains non-alphanumeric (or underscore) characters"
|
78
|
+
when /^[^a-z]/i
|
79
|
+
"the module name must begin with a letter"
|
80
|
+
else
|
81
|
+
"the namespace contains non-alphanumeric characters"
|
82
|
+
end
|
83
|
+
|
84
|
+
raise ArgumentError, "Invalid 'name' field in metadata.json: #{err}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pdk/util'
|
3
|
+
require 'pdk/cli/exec'
|
4
|
+
require 'pdk/cli/errors'
|
5
|
+
require 'pdk/template_file'
|
6
|
+
|
7
|
+
module PDK
|
8
|
+
module Module
|
9
|
+
class TemplateDir
|
10
|
+
# Initialises the TemplateDir object with the path or URL to the template
|
11
|
+
# and the block of code to run to be run while the template is available.
|
12
|
+
#
|
13
|
+
# The template directory is only guaranteed to be available on disk
|
14
|
+
# within the scope of the block passed to this method.
|
15
|
+
#
|
16
|
+
# @param path_or_url [String] The path to a directory to use as the
|
17
|
+
# template or a URL to a git repository.
|
18
|
+
# @yieldparam self [PDK::Module::TemplateDir] The initialised object with
|
19
|
+
# the template available on disk.
|
20
|
+
#
|
21
|
+
# @example Using a git repository as a template
|
22
|
+
# PDK::Module::TemplateDir.new('https://github.com/puppetlabs/pdk-module-template') do |t|
|
23
|
+
# t.render do |filename, content|
|
24
|
+
# File.open(filename, 'w') do |file|
|
25
|
+
# file.write(content)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @raise [PDK::CLI::FatalError] If the template is a git repository and
|
31
|
+
# the git binary is unavailable.
|
32
|
+
# @raise [PDK::CLI::FatalError] If the template is a git repository and
|
33
|
+
# the git clone operation fails.
|
34
|
+
# @raise [ArgumentError] (see #validate_module_template!)
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def initialize(path_or_url, &block)
|
38
|
+
if File.directory?(path_or_url)
|
39
|
+
@path = path_or_url
|
40
|
+
else
|
41
|
+
# If path_or_url isn't a directory on disk, we assume that it is
|
42
|
+
# a remote git repository.
|
43
|
+
|
44
|
+
# @todo When switching this over to using rugged, cache the cloned
|
45
|
+
# template repo in `%AppData%` or `$XDG_CACHE_DIR` and update before
|
46
|
+
# use.
|
47
|
+
temp_dir = PDK::Util.make_tmpdir_name('pdk-module-template')
|
48
|
+
|
49
|
+
clone_result = PDK::CLI::Exec.git('clone', path_or_url, temp_dir)
|
50
|
+
unless clone_result[:exit_code] == 0
|
51
|
+
PDK.logger.error clone_result[:stdout]
|
52
|
+
PDK.logger.error clone_result[:stderr]
|
53
|
+
raise PDK::CLI::FatalError, _("Unable to clone git repository '%{repo}' to '%{dest}'") % {:repo => path_or_url, :dest => temp_dir}
|
54
|
+
end
|
55
|
+
@path = temp_dir
|
56
|
+
@repo = path_or_url
|
57
|
+
end
|
58
|
+
|
59
|
+
@moduleroot_dir = File.join(@path, 'moduleroot')
|
60
|
+
@object_dir = File.join(@path, 'object_templates')
|
61
|
+
validate_module_template!
|
62
|
+
|
63
|
+
yield self
|
64
|
+
ensure
|
65
|
+
# If we cloned a git repo to get the template, remove the clone once
|
66
|
+
# we're done with it.
|
67
|
+
if @repo
|
68
|
+
FileUtils.remove_dir(@path)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Retrieve identifying metadata for the template.
|
73
|
+
#
|
74
|
+
# For git repositories, this will return the URL to the repository and
|
75
|
+
# a reference to the HEAD.
|
76
|
+
#
|
77
|
+
# @return [Hash{String => String}] A hash of identifying metadata.
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def metadata
|
81
|
+
if @repo
|
82
|
+
ref_result = PDK::CLI::Exec.git('--git-dir', File.join(@path, '.git'), 'describe', '--all', '--long')
|
83
|
+
if ref_result[:exit_code] == 0
|
84
|
+
{'template-url' => @repo, 'template-ref' => ref_result[:stdout].strip}
|
85
|
+
else
|
86
|
+
{}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Loop through the files in the template, yielding each rendered file to
|
92
|
+
# the supplied block.
|
93
|
+
#
|
94
|
+
# @yieldparam dest_path [String] The path of the destination file,
|
95
|
+
# relative to the root of the module.
|
96
|
+
# @yieldparam dest_content [String] The rendered content of the
|
97
|
+
# destination file.
|
98
|
+
#
|
99
|
+
# @raise [PDK::CLI::FatalError] If the template fails to render.
|
100
|
+
#
|
101
|
+
# @return [void]
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def render(&block)
|
105
|
+
files_in_template.each do |template_file|
|
106
|
+
PDK.logger.debug(_("Rendering '%{template}'...") % {:template => template_file})
|
107
|
+
dest_path = template_file.sub(/\.erb\Z/, '')
|
108
|
+
|
109
|
+
begin
|
110
|
+
dest_content = PDK::TemplateFile.new(File.join(@moduleroot_dir, template_file), {:configs => config_for(dest_path)}).render
|
111
|
+
rescue => e
|
112
|
+
error_msg = _(
|
113
|
+
"Failed to render template '%{template}'\n" +
|
114
|
+
"%{exception}: %{message}"
|
115
|
+
) % {:template => template_file, :exception => e.class, :message => e.message}
|
116
|
+
raise PDK::CLI::FatalError, error_msg
|
117
|
+
end
|
118
|
+
|
119
|
+
yield dest_path, dest_content
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Searches the template directory for template files that can be used to
|
124
|
+
# render files for the specified object type.
|
125
|
+
#
|
126
|
+
# @param object_type [Symbol] The object type, e.g. (`:class`,
|
127
|
+
# `:defined_type`, `:fact`, etc).
|
128
|
+
#
|
129
|
+
# @return [Hash{Symbol => String}] if the templates are available in the
|
130
|
+
# template dir, otherwise `nil`. The returned hash can contain two keys,
|
131
|
+
# :object contains the path on disk to the template for the object, :spec
|
132
|
+
# contains the path on disk to the template for the object's spec file
|
133
|
+
# (if available).
|
134
|
+
#
|
135
|
+
# @api public
|
136
|
+
def object_template_for(object_type)
|
137
|
+
object_path = File.join(@object_dir, "#{object_type.to_s}.erb")
|
138
|
+
spec_path = File.join(@object_dir, "#{object_type.to_s}_spec.erb")
|
139
|
+
|
140
|
+
if File.file?(object_path) && File.readable?(object_path)
|
141
|
+
result = {object: object_path}
|
142
|
+
result[:spec] = spec_path if File.file?(spec_path) && File.readable?(spec_path)
|
143
|
+
result
|
144
|
+
else
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Generate a hash of data to be used when rendering object templates.
|
150
|
+
#
|
151
|
+
# Read `config_defaults.yml` from the root of the template directory (if
|
152
|
+
# it exists) build a hash of values from the value of the `:global`
|
153
|
+
# key.
|
154
|
+
#
|
155
|
+
# @return [Hash] The data that will be available to the template via the
|
156
|
+
# `@configs` instance variable.
|
157
|
+
#
|
158
|
+
# @api private
|
159
|
+
def object_config
|
160
|
+
config_for(nil)
|
161
|
+
end
|
162
|
+
private
|
163
|
+
# Validate the content of the template directory.
|
164
|
+
#
|
165
|
+
# @raise [ArgumentError] If the specified path is not a directory.
|
166
|
+
# @raise [ArgumentError] If the template directory does not contain
|
167
|
+
# a directory called 'moduleroot'.
|
168
|
+
#
|
169
|
+
# @return [void]
|
170
|
+
#
|
171
|
+
# @api private
|
172
|
+
def validate_module_template!
|
173
|
+
unless File.directory?(@path)
|
174
|
+
raise ArgumentError, _("The specified template '%{path}' is not a directory") % {:path => @path}
|
175
|
+
end
|
176
|
+
|
177
|
+
unless File.directory?(@moduleroot_dir)
|
178
|
+
raise ArgumentError, _("The template at '%{path}' does not contain a moduleroot directory") % {:path => @path}
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Get a list of template files in the template directory.
|
183
|
+
#
|
184
|
+
# @return [Array[String]] An array of file names, relative to the
|
185
|
+
# `moduleroot` directory.
|
186
|
+
#
|
187
|
+
# @api private
|
188
|
+
def files_in_template
|
189
|
+
@files ||= Dir.glob(File.join(@moduleroot_dir, "**", "*"), File::FNM_DOTMATCH).select { |template_path|
|
190
|
+
File.file?(template_path) && !File.symlink?(template_path)
|
191
|
+
}.map { |template_path|
|
192
|
+
template_path.sub(/\A#{Regexp.escape(@moduleroot_dir)}#{Regexp.escape(File::SEPARATOR)}/, '')
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
# Generate a hash of data to be used when rendering the specified
|
197
|
+
# template.
|
198
|
+
#
|
199
|
+
# Read `config_defaults.yml` from the root of the template directory (if
|
200
|
+
# it exists) build a hash of values by merging the value of the `:global`
|
201
|
+
# key with the value of the key that matches `dest_path`.
|
202
|
+
#
|
203
|
+
# @param dest_path [String] The destination path of the file that the
|
204
|
+
# data is for, relative to the root of the module.
|
205
|
+
#
|
206
|
+
# @return [Hash] The data that will be available to the template via the
|
207
|
+
# `@configs` instance variable.
|
208
|
+
#
|
209
|
+
# @api private
|
210
|
+
def config_for(dest_path)
|
211
|
+
if @config.nil?
|
212
|
+
config_path = File.join(@path, 'config_defaults.yml')
|
213
|
+
|
214
|
+
if File.file?(config_path) && File.readable?(config_path)
|
215
|
+
begin
|
216
|
+
@config = YAML.load(File.read(config_path))
|
217
|
+
rescue
|
218
|
+
PDK.logger.warn(_("'%{file}' is not a valid YAML file") % {:file => config_path})
|
219
|
+
@config = {}
|
220
|
+
end
|
221
|
+
else
|
222
|
+
@config = {}
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
file_config = @config.fetch(:global, {})
|
227
|
+
file_config.merge(@config.fetch(dest_path, {})) unless dest_path.nil?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
data/lib/pdk/report.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module PDK
|
2
|
+
class Report
|
3
|
+
def initialize(path, format = nil)
|
4
|
+
@path = path
|
5
|
+
@format = format || self.class.default_format
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.formats
|
9
|
+
@report_formats ||= ['junit', 'text'].freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.default_format
|
13
|
+
'junit'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.default_target
|
17
|
+
'stdout' # TODO: actually write to stdout
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(text)
|
21
|
+
if @format == 'junit'
|
22
|
+
report = prepare_junit(text)
|
23
|
+
elsif @format == 'text'
|
24
|
+
report = prepare_text(text)
|
25
|
+
end
|
26
|
+
|
27
|
+
File.open(@path, 'a') { |f| f.write(report) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def prepare_junit(text)
|
31
|
+
"junit: #{text}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def prepare_text(text)
|
35
|
+
"text: #{text}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module PDK
|
4
|
+
class TemplateFile < OpenStruct
|
5
|
+
# Initialises the TemplateFile object with the path to the template file
|
6
|
+
# and the data to be used when rendering the template.
|
7
|
+
#
|
8
|
+
# @param template_file [String] The path on disk to the template file.
|
9
|
+
# @param data [Hash{Symbol => Object}] The data that should be provided to
|
10
|
+
# the template when rendering.
|
11
|
+
# @option data [Object] :configs The value of this key will be provided to
|
12
|
+
# the template as an instance variable `@configs` in order to maintain
|
13
|
+
# compatibility with modulesync.
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def initialize(template_file, data = {})
|
17
|
+
@template_file = template_file
|
18
|
+
|
19
|
+
if data.has_key?(:configs)
|
20
|
+
@configs = data[:configs]
|
21
|
+
end
|
22
|
+
|
23
|
+
super(data)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Renders the template by calling the appropriate engine based on the file
|
27
|
+
# extension.
|
28
|
+
#
|
29
|
+
# If the template has an `.erb` extension, the content of the template
|
30
|
+
# file will be treated as an ERB template. All other extensions are treated
|
31
|
+
# as plain text.
|
32
|
+
#
|
33
|
+
# @return [String] The rendered template
|
34
|
+
#
|
35
|
+
# @raise (see #template_content)
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def render
|
39
|
+
case File.extname(@template_file)
|
40
|
+
when ".erb"
|
41
|
+
render_erb
|
42
|
+
else
|
43
|
+
render_plain
|
44
|
+
end
|
45
|
+
end
|
46
|
+
private
|
47
|
+
# Reads the content of the template file into memory.
|
48
|
+
#
|
49
|
+
# @return [String] The content of the template file.
|
50
|
+
#
|
51
|
+
# @raise [ArgumentError] If the template file does not exist or can not be
|
52
|
+
# read.
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
def template_content
|
56
|
+
if File.file?(@template_file) && File.readable?(@template_file)
|
57
|
+
File.read(@template_file)
|
58
|
+
else
|
59
|
+
raise ArgumentError, _("'%{template}' is not a readable file") % {:template => @template_file}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Renders the content of the template file as an ERB template.
|
64
|
+
#
|
65
|
+
# @return [String] The rendered template.
|
66
|
+
#
|
67
|
+
# @raise (see #template_content)
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
def render_erb
|
71
|
+
renderer = ERB.new(template_content, nil, '-')
|
72
|
+
renderer.filename = @template_file
|
73
|
+
renderer.result(binding)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Renders the content of the template file as plain text.
|
77
|
+
#
|
78
|
+
# @return [String] The rendered template.
|
79
|
+
#
|
80
|
+
# @raise (see #template_content)
|
81
|
+
#
|
82
|
+
# @api private
|
83
|
+
def render_plain
|
84
|
+
template_content
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|