pdk 1.9.0 → 3.2.0

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.
Files changed (163) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +744 -711
  3. data/README.md +23 -21
  4. data/lib/pdk/answer_file.rb +3 -112
  5. data/lib/pdk/bolt.rb +20 -0
  6. data/lib/pdk/cli/build.rb +51 -54
  7. data/lib/pdk/cli/bundle.rb +33 -29
  8. data/lib/pdk/cli/console.rb +148 -0
  9. data/lib/pdk/cli/convert.rb +46 -37
  10. data/lib/pdk/cli/env.rb +51 -0
  11. data/lib/pdk/cli/errors.rb +4 -3
  12. data/lib/pdk/cli/exec/command.rb +285 -0
  13. data/lib/pdk/cli/exec/interactive_command.rb +109 -0
  14. data/lib/pdk/cli/exec.rb +32 -201
  15. data/lib/pdk/cli/exec_group.rb +79 -43
  16. data/lib/pdk/cli/get/config.rb +26 -0
  17. data/lib/pdk/cli/get.rb +22 -0
  18. data/lib/pdk/cli/new/class.rb +20 -22
  19. data/lib/pdk/cli/new/defined_type.rb +21 -21
  20. data/lib/pdk/cli/new/fact.rb +27 -0
  21. data/lib/pdk/cli/new/function.rb +27 -0
  22. data/lib/pdk/cli/new/module.rb +40 -29
  23. data/lib/pdk/cli/new/provider.rb +18 -18
  24. data/lib/pdk/cli/new/task.rb +23 -22
  25. data/lib/pdk/cli/new/test.rb +52 -0
  26. data/lib/pdk/cli/new/transport.rb +27 -0
  27. data/lib/pdk/cli/new.rb +15 -9
  28. data/lib/pdk/cli/release/prep.rb +39 -0
  29. data/lib/pdk/cli/release/publish.rb +46 -0
  30. data/lib/pdk/cli/release.rb +185 -0
  31. data/lib/pdk/cli/remove/config.rb +83 -0
  32. data/lib/pdk/cli/remove.rb +22 -0
  33. data/lib/pdk/cli/set/config.rb +121 -0
  34. data/lib/pdk/cli/set.rb +22 -0
  35. data/lib/pdk/cli/test/unit.rb +71 -69
  36. data/lib/pdk/cli/test.rb +9 -8
  37. data/lib/pdk/cli/update.rb +38 -21
  38. data/lib/pdk/cli/util/command_redirector.rb +13 -3
  39. data/lib/pdk/cli/util/interview.rb +25 -9
  40. data/lib/pdk/cli/util/option_normalizer.rb +6 -6
  41. data/lib/pdk/cli/util/option_validator.rb +19 -9
  42. data/lib/pdk/cli/util/spinner.rb +13 -0
  43. data/lib/pdk/cli/util/update_manager_printer.rb +82 -0
  44. data/lib/pdk/cli/util.rb +105 -48
  45. data/lib/pdk/cli/validate.rb +96 -111
  46. data/lib/pdk/cli.rb +134 -87
  47. data/lib/pdk/config/errors.rb +5 -0
  48. data/lib/pdk/config/ini_file.rb +184 -0
  49. data/lib/pdk/config/ini_file_setting.rb +35 -0
  50. data/lib/pdk/config/json.rb +35 -0
  51. data/lib/pdk/config/json_schema_namespace.rb +137 -0
  52. data/lib/pdk/config/json_schema_setting.rb +51 -0
  53. data/lib/pdk/config/json_with_schema.rb +47 -0
  54. data/lib/pdk/config/namespace.rb +362 -0
  55. data/lib/pdk/config/setting.rb +134 -0
  56. data/lib/pdk/config/task_schema.json +116 -0
  57. data/lib/pdk/config/validator.rb +31 -0
  58. data/lib/pdk/config/yaml.rb +41 -0
  59. data/lib/pdk/config/yaml_with_schema.rb +51 -0
  60. data/lib/pdk/config.rb +304 -0
  61. data/lib/pdk/context/control_repo.rb +61 -0
  62. data/lib/pdk/context/module.rb +28 -0
  63. data/lib/pdk/context/none.rb +22 -0
  64. data/lib/pdk/context.rb +98 -0
  65. data/lib/pdk/control_repo.rb +89 -0
  66. data/lib/pdk/generate/defined_type.rb +27 -33
  67. data/lib/pdk/generate/fact.rb +26 -0
  68. data/lib/pdk/generate/function.rb +49 -0
  69. data/lib/pdk/generate/module.rb +160 -153
  70. data/lib/pdk/generate/provider.rb +16 -69
  71. data/lib/pdk/generate/puppet_class.rb +27 -32
  72. data/lib/pdk/generate/puppet_object.rb +100 -159
  73. data/lib/pdk/generate/task.rb +34 -51
  74. data/lib/pdk/generate/transport.rb +34 -0
  75. data/lib/pdk/generate.rb +21 -8
  76. data/lib/pdk/logger.rb +24 -6
  77. data/lib/pdk/module/build.rb +125 -37
  78. data/lib/pdk/module/convert.rb +146 -65
  79. data/lib/pdk/module/metadata.rb +72 -71
  80. data/lib/pdk/module/release.rb +255 -0
  81. data/lib/pdk/module/update.rb +48 -37
  82. data/lib/pdk/module/update_manager.rb +75 -39
  83. data/lib/pdk/module.rb +10 -2
  84. data/lib/pdk/monkey_patches.rb +268 -0
  85. data/lib/pdk/report/event.rb +36 -48
  86. data/lib/pdk/report.rb +35 -22
  87. data/lib/pdk/template/fetcher/git.rb +84 -0
  88. data/lib/pdk/template/fetcher/local.rb +29 -0
  89. data/lib/pdk/template/fetcher.rb +100 -0
  90. data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +108 -0
  91. data/lib/pdk/template/renderer/v1/renderer.rb +131 -0
  92. data/lib/pdk/template/renderer/v1/template_file.rb +100 -0
  93. data/lib/pdk/template/renderer/v1.rb +25 -0
  94. data/lib/pdk/template/renderer.rb +97 -0
  95. data/lib/pdk/template/template_dir.rb +67 -0
  96. data/lib/pdk/template.rb +52 -0
  97. data/lib/pdk/tests/unit.rb +101 -51
  98. data/lib/pdk/util/bundler.rb +44 -42
  99. data/lib/pdk/util/changelog_generator.rb +138 -0
  100. data/lib/pdk/util/env.rb +48 -0
  101. data/lib/pdk/util/filesystem.rb +139 -2
  102. data/lib/pdk/util/git.rb +108 -8
  103. data/lib/pdk/util/json_finder.rb +86 -0
  104. data/lib/pdk/util/puppet_strings.rb +125 -0
  105. data/lib/pdk/util/puppet_version.rb +71 -87
  106. data/lib/pdk/util/ruby_version.rb +49 -25
  107. data/lib/pdk/util/template_uri.rb +283 -0
  108. data/lib/pdk/util/vendored_file.rb +34 -42
  109. data/lib/pdk/util/version.rb +11 -10
  110. data/lib/pdk/util/windows/api_types.rb +74 -44
  111. data/lib/pdk/util/windows/file.rb +31 -27
  112. data/lib/pdk/util/windows/process.rb +74 -0
  113. data/lib/pdk/util/windows/string.rb +19 -12
  114. data/lib/pdk/util/windows.rb +2 -0
  115. data/lib/pdk/util.rb +111 -124
  116. data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -0
  117. data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -0
  118. data/lib/pdk/validate/external_command_validator.rb +213 -0
  119. data/lib/pdk/validate/internal_ruby_validator.rb +101 -0
  120. data/lib/pdk/validate/invokable_validator.rb +238 -0
  121. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +84 -0
  122. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +76 -0
  123. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
  124. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +131 -0
  125. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
  126. data/lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb +38 -0
  127. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +135 -0
  128. data/lib/pdk/validate/puppet/puppet_validator_group.rb +22 -0
  129. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +79 -0
  130. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
  131. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +83 -0
  132. data/lib/pdk/validate/tasks/tasks_name_validator.rb +45 -0
  133. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
  134. data/lib/pdk/validate/validator.rb +120 -0
  135. data/lib/pdk/validate/validator_group.rb +107 -0
  136. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +86 -0
  137. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
  138. data/lib/pdk/validate.rb +86 -12
  139. data/lib/pdk/version.rb +2 -2
  140. data/lib/pdk.rb +60 -10
  141. metadata +138 -100
  142. data/lib/pdk/cli/module/build.rb +0 -14
  143. data/lib/pdk/cli/module/generate.rb +0 -45
  144. data/lib/pdk/cli/module.rb +0 -14
  145. data/lib/pdk/i18n.rb +0 -4
  146. data/lib/pdk/module/templatedir.rb +0 -321
  147. data/lib/pdk/template_file.rb +0 -95
  148. data/lib/pdk/validate/base_validator.rb +0 -215
  149. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -86
  150. data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -109
  151. data/lib/pdk/validate/metadata_validator.rb +0 -30
  152. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -67
  153. data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -112
  154. data/lib/pdk/validate/puppet_validator.rb +0 -30
  155. data/lib/pdk/validate/ruby/rubocop.rb +0 -77
  156. data/lib/pdk/validate/ruby_validator.rb +0 -29
  157. data/lib/pdk/validate/tasks/metadata_lint.rb +0 -126
  158. data/lib/pdk/validate/tasks/name.rb +0 -88
  159. data/lib/pdk/validate/tasks_validator.rb +0 -33
  160. data/lib/pdk/validate/yaml/syntax.rb +0 -109
  161. data/lib/pdk/validate/yaml_validator.rb +0 -31
  162. data/locales/config.yaml +0 -21
  163. data/locales/pdk.pot +0 -1291
@@ -0,0 +1,48 @@
1
+ require 'pdk'
2
+ require 'forwardable'
3
+
4
+ module PDK
5
+ module Util
6
+ class Env
7
+ class WindowsEnv
8
+ extend Forwardable
9
+
10
+ # Note, these delegators may not have case insensitive keys
11
+ def_delegators :env_hash, :fetch, :select, :reject
12
+
13
+ def []=(key, value)
14
+ PDK::Util::Windows::Process.set_environment_variable(key, value)
15
+ end
16
+
17
+ def key?(key)
18
+ !env_hash.keys.find { |item| key.casecmp(item).zero? }.nil?
19
+ end
20
+
21
+ def [](key)
22
+ env_hash.each do |item, value|
23
+ next unless key.casecmp(item).zero?
24
+
25
+ return value
26
+ end
27
+ nil
28
+ end
29
+
30
+ private
31
+
32
+ def env_hash
33
+ PDK::Util::Windows::Process.environment_hash
34
+ end
35
+ end
36
+
37
+ class << self
38
+ extend Forwardable
39
+
40
+ def_delegators :implementation, :key?, :[], :[]=, :fetch, :select, :reject
41
+
42
+ def implementation
43
+ @implementation ||= Gem.win_platform? ? WindowsEnv.new : ENV
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,12 +1,149 @@
1
+ require 'pdk'
2
+ autoload :FileUtils, 'fileutils'
3
+
1
4
  module PDK
2
5
  module Util
3
6
  module Filesystem
4
7
  def write_file(path, content)
5
- raise ArgumentError unless path.is_a?(String) || path.respond_to?(:to_path)
8
+ raise ArgumentError, 'content must be a String' unless content.is_a?(String)
9
+ raise ArgumentError, 'path must be a String or Pathname' unless path.is_a?(String) || path.respond_to?(:to_path)
10
+
11
+ # Harmonize newlines across platforms.
12
+ content = content.encode(universal_newline: true)
13
+
14
+ # Make sure all written files have a trailing newline.
15
+ content += "\n" unless content[-1] == "\n"
6
16
 
7
- File.open(path, 'wb') { |f| f.write(content.encode(universal_newline: true)) }
17
+ File.binwrite(path, content)
8
18
  end
9
19
  module_function :write_file
20
+
21
+ def read_file(file, nil_on_error: false, open_args: 'r')
22
+ File.read(file, open_args: Array(open_args))
23
+ rescue StandardError => e
24
+ raise e unless nil_on_error
25
+
26
+ nil
27
+ end
28
+ module_function :read_file
29
+
30
+ def make_executable(file)
31
+ FileUtils.chmod('a+x', file)
32
+ end
33
+ module_function :make_executable
34
+
35
+ # :nocov:
36
+ # These methods just wrap core Ruby functionality and
37
+ # can be ignored for code coverage
38
+ def directory?(*args)
39
+ File.directory?(*args)
40
+ end
41
+ module_function :directory?
42
+
43
+ def mkdir_p(*args, **kwargs)
44
+ FileUtils.mkdir_p(*args, **kwargs)
45
+ end
46
+ module_function :mkdir_p
47
+
48
+ def file?(*args)
49
+ File.file?(*args)
50
+ end
51
+ module_function :file?
52
+
53
+ def expand_path(*args)
54
+ File.expand_path(*args)
55
+ end
56
+ module_function :expand_path
57
+
58
+ def glob(*args)
59
+ Dir.glob(*args)
60
+ end
61
+ module_function :glob
62
+
63
+ def fnmatch(*args)
64
+ File.fnmatch(*args)
65
+ end
66
+ module_function :fnmatch
67
+
68
+ def fnmatch?(*args)
69
+ File.fnmatch?(*args)
70
+ end
71
+ module_function :fnmatch?
72
+
73
+ def readable?(*args)
74
+ File.readable?(*args)
75
+ end
76
+ module_function :readable?
77
+
78
+ def exist?(*args)
79
+ File.exist?(*args)
80
+ end
81
+ module_function :exist?
82
+
83
+ def rm(*args, **kwargs)
84
+ FileUtils.rm(*args, **kwargs)
85
+ end
86
+ module_function :rm
87
+
88
+ def rm_f(*args, **kwargs)
89
+ FileUtils.rm_f(*args, **kwargs)
90
+ end
91
+ module_function :rm_f
92
+
93
+ def rm_rf(*args, **kwargs)
94
+ FileUtils.rm_rf(*args, **kwargs)
95
+ end
96
+ module_function :rm_rf
97
+
98
+ def remove_entry_secure(*args)
99
+ FileUtils.remove_entry_secure(*args)
100
+ end
101
+ module_function :remove_entry_secure
102
+
103
+ def zero?(*args)
104
+ File.empty?(*args)
105
+ end
106
+ module_function :zero?
107
+
108
+ def stat(*args)
109
+ File.stat(*args)
110
+ end
111
+ module_function :stat
112
+
113
+ def symlink?(*args)
114
+ File.symlink?(*args)
115
+ end
116
+ module_function :symlink?
117
+
118
+ def cp(*args, **kwargs)
119
+ FileUtils.cp(*args, **kwargs)
120
+ end
121
+ module_function :cp
122
+
123
+ def mv(*args, **kwargs)
124
+ FileUtils.mv(*args, **kwargs)
125
+ rescue Errno::ENOENT
126
+ # PDK-1169 - FileUtils.mv raises Errno::ENOENT when moving files inside
127
+ # VMWare shared folders on Windows. So we need to catch this
128
+ # case, check if the file exists to see if the exception is
129
+ # legit and "move" the file with cp & rm.
130
+ src, dest, opts = args
131
+ raise unless File.exist?(src)
132
+
133
+ FileUtils.cp(src, dest, preserve: true)
134
+ if (opts ||= {})[:secure]
135
+ FileUtils.remove_entry_secure(src, opts[:force])
136
+ else
137
+ FileUtils.remove_entry(src, opts[:force])
138
+ end
139
+ end
140
+ module_function :mv
141
+
142
+ def executable?(*args)
143
+ File.executable?(*args)
144
+ end
145
+ module_function :executable?
146
+ # :nocov:
10
147
  end
11
148
  end
12
149
  end
data/lib/pdk/util/git.rb CHANGED
@@ -1,6 +1,23 @@
1
+ require 'pdk'
2
+
1
3
  module PDK
2
4
  module Util
5
+ class GitError < StandardError
6
+ attr_reader :stdout, :stderr, :exit_code, :args
7
+
8
+ def initialze(args, result)
9
+ @args = args
10
+ @stdout = result[:stdout]
11
+ @stderr = result[:stderr]
12
+ @exit_code = result[:exit_code]
13
+
14
+ super(format('Git command failed: git %{args}', args: args.join(' ')))
15
+ end
16
+ end
17
+
3
18
  module Git
19
+ GIT_QUERY_CACHE_TTL = 10
20
+
4
21
  def self.git_bindir
5
22
  @git_dir ||= File.join('private', 'git', Gem.win_platform? ? 'cmd' : 'bin')
6
23
  end
@@ -20,6 +37,8 @@ module PDK
20
37
  end
21
38
 
22
39
  def self.git_bin
40
+ require 'pdk/cli/exec'
41
+
23
42
  git_bin = Gem.win_platform? ? 'git.exe' : 'git'
24
43
  vendored_bin_path = File.join(git_bindir, git_bin)
25
44
 
@@ -27,21 +46,33 @@ module PDK
27
46
  end
28
47
 
29
48
  def self.git(*args)
49
+ require 'pdk/cli/exec'
50
+
30
51
  PDK::CLI::Exec.ensure_bin_present!(git_bin, 'git')
31
52
 
32
53
  PDK::CLI::Exec.execute(git_bin, *args)
33
54
  end
34
55
 
35
56
  def self.git_with_env(env, *args)
57
+ require 'pdk/cli/exec'
58
+
36
59
  PDK::CLI::Exec.ensure_bin_present!(git_bin, 'git')
37
60
 
38
61
  PDK::CLI::Exec.execute_with_env(env, git_bin, *args)
39
62
  end
40
63
 
41
64
  def self.repo?(maybe_repo)
42
- return bare_repo?(maybe_repo) if File.directory?(maybe_repo)
43
-
44
- remote_repo?(maybe_repo)
65
+ result = cached_git_query(maybe_repo, :repo?)
66
+ return result unless result.nil?
67
+
68
+ result = if PDK::Util::Filesystem.directory?(maybe_repo)
69
+ # Use boolean shortcircuiting here. The mostly likely type of git repo
70
+ # is a "normal" repo with a working tree. Bare repos do not have work tree
71
+ work_tree?(maybe_repo) || bare_repo?(maybe_repo)
72
+ else
73
+ remote_repo?(maybe_repo)
74
+ end
75
+ cache_query_result(maybe_repo, :repo?, result)
45
76
  end
46
77
 
47
78
  def self.bare_repo?(maybe_repo)
@@ -55,20 +86,89 @@ module PDK
55
86
  git('ls-remote', '--exit-code', maybe_repo)[:exit_code].zero?
56
87
  end
57
88
 
89
+ def self.work_tree?(path)
90
+ return false unless PDK::Util::Filesystem.directory?(path)
91
+
92
+ result = cached_git_query(path, :work_tree?)
93
+ return result unless result.nil?
94
+
95
+ Dir.chdir(path) do
96
+ rev_parse = git('rev-parse', '--is-inside-work-tree')
97
+ cache_query_result(path, :work_tree?, rev_parse[:exit_code].zero? && rev_parse[:stdout].strip == 'true')
98
+ end
99
+ end
100
+
101
+ def self.work_dir_clean?(repo)
102
+ raise PDK::CLI::ExitWithError, format('Unable to locate git work dir "%{workdir}"', workdir: repo) unless PDK::Util::Filesystem.directory?(repo)
103
+ raise PDK::CLI::ExitWithError, format('Unable to locate git dir "%{gitdir}"', gitdir: repo) unless PDK::Util::Filesystem.directory?(File.join(repo, '.git'))
104
+
105
+ git('--work-tree', repo, '--git-dir', File.join(repo, '.git'), 'status', '--untracked-files=no', '--porcelain', repo)[:stdout].empty?
106
+ end
107
+
58
108
  def self.ls_remote(repo, ref)
109
+ repo = "file://#{repo}" if PDK::Util::Filesystem.directory?(repo)
110
+
59
111
  output = git('ls-remote', '--refs', repo, ref)
60
112
 
61
113
  unless output[:exit_code].zero?
62
114
  PDK.logger.error output[:stdout]
63
115
  PDK.logger.error output[:stderr]
64
- raise PDK::CLI::ExitWithError, _('Unable to access the template repository "%{repository}"') % {
65
- repository: repo,
66
- }
116
+ raise PDK::CLI::ExitWithError, format('Unable to access the template repository "%{repository}"', repository: repo)
117
+ end
118
+
119
+ matching_refs = output[:stdout].split(/\r?\n/).map { |r| r.split("\t") }
120
+ matching_ref = matching_refs.find { |_sha, remote_ref| ["refs/tags/#{ref}", "refs/remotes/origin/#{ref}", "refs/heads/#{ref}"].include?(remote_ref) }
121
+ raise PDK::CLI::ExitWithError, format('Unable to find a branch or tag named "%{ref}" in %{repo}', ref: ref, repo: repo) if matching_ref.nil?
122
+
123
+ matching_ref.first
124
+ end
125
+
126
+ def self.describe(path, ref = nil)
127
+ args = ['--git-dir', path, 'describe', '--all', '--long', '--always', ref].compact
128
+ result = git(*args)
129
+ raise PDK::Util::GitError, args, result unless result[:exit_code].zero?
130
+
131
+ result[:stdout].strip
132
+ end
133
+
134
+ def self.tag?(git_remote, tag_name)
135
+ git('ls-remote', '--tags', '--exit-code', git_remote, tag_name)[:exit_code].zero?
136
+ end
137
+
138
+ # Clears any cached information for git queries
139
+ # Should only be used during testing
140
+ # @api private
141
+ def self.clear_cached_information
142
+ @git_repo_expire_cache = nil
143
+ @git_repo_cache = nil
144
+ end
145
+
146
+ def self.cached_git_query(repo, query)
147
+ # TODO: Not thread safe
148
+ if @git_repo_expire_cache.nil? || Time.now > @git_repo_expire_cache
149
+ @git_repo_expire_cache = Time.now + GIT_QUERY_CACHE_TTL # Expire the cache every GIT_QUERY_CACHE_TTL seconds
150
+ @git_repo_cache = {}
67
151
  end
152
+ return nil if @git_repo_cache[repo].nil?
68
153
 
69
- matching_refs = output[:stdout].split("\n").map { |r| r.split("\t") }
70
- matching_refs.find { |_sha, remote_ref| remote_ref == ref }.first
154
+ @git_repo_cache[repo][query]
155
+ end
156
+ private_class_method :cached_git_query
157
+
158
+ def self.cache_query_result(repo, query, result)
159
+ # TODO: Not thread safe?
160
+ if @git_repo_expire_cache.nil?
161
+ @git_repo_expire_cache = Time.now + GIT_QUERY_CACHE_TTL
162
+ @git_repo_cache = {}
163
+ end
164
+ if @git_repo_cache[repo].nil?
165
+ @git_repo_cache[repo] = { query => result }
166
+ else
167
+ @git_repo_cache[repo][query] = result
168
+ end
169
+ result
71
170
  end
171
+ private_class_method :cache_query_result
72
172
  end
73
173
  end
74
174
  end
@@ -0,0 +1,86 @@
1
+ module PDK
2
+ module Util
3
+ # Processes a given string, looking for JSON objects and parsing them.
4
+ #
5
+ # @example A string with a JSON object and some junk characters
6
+ # PDK::Util::JSONFinder.new('foo{"bar":1}').objects
7
+ # => [{ 'bar' => 1 }]
8
+ #
9
+ # @example A string with mulitple JSON objects
10
+ # PDK::Util::JSONFinder.new('foo{"bar":1}baz{"gronk":2}').objects
11
+ # => [{ 'bar' => 1 }, { 'gronk' => 2 }]
12
+ class JSONFinder
13
+ # Creates a new instance of PDK::Util::JSONFinder.
14
+ #
15
+ # @param string [String] the string to find JSON objects inside of.
16
+ #
17
+ # @return [PDK::Util::JSONFinder] a new PDK::Util::JSONFinder object.
18
+ def initialize(string)
19
+ require 'strscan'
20
+
21
+ @scanner = StringScanner.new(string)
22
+ end
23
+
24
+ # Returns the parsed JSON objects from the string.
25
+ #
26
+ # @return [Array[Hash]] the parsed JSON objects present in the string.
27
+ def objects
28
+ return @objects unless @objects.nil?
29
+
30
+ require 'json'
31
+
32
+ until @scanner.eos?
33
+ @scanner.getch until @scanner.peek(1) == '{' || @scanner.eos?
34
+
35
+ (@objects ||= []) << begin
36
+ JSON.parse(read_object(true) || '')
37
+ rescue JSON::ParserError
38
+ nil
39
+ end
40
+ end
41
+
42
+ return [] if @objects.nil?
43
+
44
+ @objects = @objects.compact
45
+ end
46
+
47
+ private
48
+
49
+ # Recursively process the string to extract a complete JSON object.
50
+ #
51
+ # @param new_object [Boolean] Set to true if processing a new object to
52
+ # capture the opening brace. Set to false if being called recursively
53
+ # where the opening brace has already been captured.
54
+ #
55
+ # @return [String] The matched substring containing a JSON object.
56
+ def read_object(new_object = false)
57
+ matched_text = new_object ? @scanner.getch : ''
58
+
59
+ until @scanner.eos?
60
+ text = @scanner.scan_until(/(?:(?<!\\)"|\{|\})/)
61
+ unless text
62
+ @scanner.terminate
63
+ return nil
64
+ end
65
+ matched_text += text
66
+
67
+ case @scanner.matched
68
+ when '}'
69
+ break
70
+ when '"'
71
+ text = @scanner.scan_until(/(?<!\\)"/)
72
+ unless text
73
+ @scanner.terminate
74
+ return nil
75
+ end
76
+ matched_text += text
77
+ else
78
+ matched_text += read_object
79
+ end
80
+ end
81
+
82
+ matched_text
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,125 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Util
5
+ module PuppetStrings
6
+ class NoObjectError < StandardError; end
7
+ class RunError < StandardError; end
8
+ class NoGeneratorError < StandardError; end
9
+
10
+ # Runs Puppet for the purposes of generating puppet-strings output.
11
+ #
12
+ # @param *args [String] additional parameters to pass to puppet.
13
+ #
14
+ # @return [Hash{Symbol=>Object}] the result of the command execution.
15
+ def self.puppet(*args)
16
+ require 'pdk/cli/exec/command'
17
+
18
+ PDK::Util::Bundler.ensure_binstubs!('puppet')
19
+
20
+ argv = [File.join(PDK::Util.module_root, 'bin', 'puppet')] + args
21
+ argv.unshift(File.join(PDK::Util::RubyVersion.bin_path, 'ruby.exe')) if Gem.win_platform?
22
+
23
+ command = PDK::CLI::Exec::Command.new(*argv).tap do |c|
24
+ c.context = :module
25
+ c.add_spinner('Examining module contents')
26
+ end
27
+
28
+ command.execute!
29
+ end
30
+
31
+ # Generates and parses the JSON output from puppet-strings.
32
+ #
33
+ # @returns [Hash{String=>Object}] the parsed puppet-strings output.
34
+ #
35
+ # @raises [PDK::Util::PuppetStrings::RunError] if the puppet-strings
36
+ # command exits non-zero.
37
+ # @raises [PDK::Util::PuppetStrings::RunError] if the puppet-strings
38
+ # command outputs invalid JSON.
39
+ def self.generate_hash
40
+ result = puppet('strings', 'generate', '--format', 'json')
41
+
42
+ raise RunError, result[:stderr] unless result[:exit_code].zero?
43
+
44
+ JSON.parse(result[:stdout])
45
+ rescue JSON::ParserError => e
46
+ PDK.logger.debug(e)
47
+ raise RunError, 'Unable to parse puppet-strings output'
48
+ end
49
+
50
+ # Searches the puppet-strings result to find the definition of the named
51
+ # object.
52
+ #
53
+ # @param name [String] the name of the object definition to search for.
54
+ # If the object name is not prepended with the module name,
55
+ # "#{module_name}::#{object_name}" will also be search for.
56
+ #
57
+ # @returns [Array[PDK::Generate::PuppetObject, Hash{String=>Object}]] the
58
+ # appropriate generator class for the object as the first element and
59
+ # the puppet-strings description hash for the definition.
60
+ #
61
+ # @raises [PDK::Util::PuppetStrings::NoObjectError] if the named object
62
+ # can not be found in the puppet-strings output.
63
+ # @raises [PDK::Util::PuppetStrings::NoGeneratorError] if the named
64
+ # object does not have a corresponding PDK generator class.
65
+ def self.find_object(name)
66
+ module_name = PDK::Util.module_metadata['name'].rpartition('-').last
67
+
68
+ object_names = [name]
69
+ object_names << "#{module_name}::#{name}" unless name.start_with?("#{module_name}::")
70
+
71
+ known_objects = generate_hash
72
+ object_type = known_objects.keys.find do |obj_type|
73
+ known_objects[obj_type].any? { |obj| object_names.include?(obj['name']) }
74
+ end
75
+
76
+ raise NoObjectError if object_type.nil?
77
+
78
+ generator = find_generator(object_type)
79
+
80
+ raise NoGeneratorError, object_type if generator.nil?
81
+
82
+ [generator, known_objects[object_type].find { |obj| object_names.include?(obj['name']) }]
83
+ end
84
+
85
+ # Generate a list of all objects that PDK has a generator for.
86
+ #
87
+ # @returns [Array[Array[PDK::Generate::PuppetObject,
88
+ # Array[Hash{String=>Object}]]]] an associative array where the first
89
+ # element of each pair is the generator class and the second element of
90
+ # each pair is an array of description hashes from puppet-strings.
91
+ def self.all_objects
92
+ require 'pdk/generate'
93
+
94
+ generators = PDK::Generate.generators.select do |gen|
95
+ gen.const_defined?(:PUPPET_STRINGS_TYPE) && !gen::PUPPET_STRINGS_TYPE.nil?
96
+ end
97
+
98
+ known_objects = generate_hash
99
+
100
+ generators.map { |gen| [gen, known_objects[gen::PUPPET_STRINGS_TYPE]] }.reject do |_, obj|
101
+ obj.nil? || obj.empty?
102
+ end
103
+ end
104
+
105
+ # Evaluates the mapping of puppet-strings object type to PDK generator
106
+ # class.
107
+ #
108
+ # @param type [String] the object type as returned from puppet-strings.
109
+ #
110
+ # @returns [PDK:Generate::PuppetObject,nil] a child of
111
+ # PDK::Generate::PuppetObject or nil a suitable generator is not found.
112
+ #
113
+ # @example
114
+ # PDK::Util::PuppetStrings.find_generator('puppet_classes')
115
+ # => PDK::Generate::PuppetClass
116
+ def self.find_generator(type)
117
+ require 'pdk/generate'
118
+
119
+ PDK::Generate.generators.find do |gen|
120
+ gen.const_defined?(:PUPPET_STRINGS_TYPE) && type == gen::PUPPET_STRINGS_TYPE
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end