pdk 1.9.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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