octocatalog-diff 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a5c575bb439d7b065379f5c30bb650d74f3fa09b
4
- data.tar.gz: ba4dda3fd34774227a3babac32f981bdbb4fb03b
3
+ metadata.gz: 86467134c489b13251760bd9ffd23caced1c5222
4
+ data.tar.gz: 8260b43c4e844b2224dd491018feb8a1da65c4ed
5
5
  SHA512:
6
- metadata.gz: 3c577fe4b8add2eb722b83ac5b5c33efece70619d6ca7da88287d2a1d49ac24a7091615d33fd7fe80f413ecee268ecdc24af016e53df0d7dc3f57bc491174db9
7
- data.tar.gz: bbb4cdd2d011c87f0ae9f688af9e416bad7009f3391bbb01981a831d16ae83ffe46aadec12cbef579b6076e2c52b77e505c242e5196a381a9258dd679c3a1df2
6
+ metadata.gz: fedd421520a95d4147878d50510d7f2f149cfcba0933c75d7f88c5f3efbeb45f6105b9c5bab53cbc46cf7234db1987cba0b9bdc327cd0b418ce8f69365723282
7
+ data.tar.gz: 1bc344f71b92ef5154253e4b115f7916e59a9ee49c21c7344adb80ad7dd97d2f164fb951016e8a22d95ba564eab1dbc27a9c1b0cff8a014d327bfde881fb5d21
data/.version CHANGED
@@ -1 +1 @@
1
- 1.0.4
1
+ 1.1.0
data/doc/CHANGELOG.md CHANGED
@@ -8,6 +8,16 @@
8
8
  </tr>
9
9
  </thead><tbody>
10
10
  <tr valign=top>
11
+ <td>1.1.0</td>
12
+ <td>2017-05-08</td>
13
+ <td>
14
+ <li><a href="https://github.com/github/octocatalog-diff/pull/108">#108</a>: (Bug) Support hiera.yaml backend declared as a string instead of array</li>
15
+ <li><a href="https://github.com/github/octocatalog-diff/pull/105">#105</a>: (Bug) Remove legacy exclusion of tags</li>
16
+ <li><a href="https://github.com/github/octocatalog-diff/pull/103">#103</a>: (Enhancement) Identify where the broken reference was declared</li>
17
+ <li><a href="https://github.com/github/octocatalog-diff/pull/98">#98</a>: (Enhancement) Separate scripts and commands and make override-able</li>
18
+ </td>
19
+ </tr>
20
+ <tr valign=top>
11
21
  <td>1.0.4</td>
12
22
  <td>2017-03-17</td>
13
23
  <td>
@@ -2,6 +2,8 @@
2
2
 
3
3
  Using the `--ignore-tags` command line option, it is possible to ignore all resources with particular Puppet tags. This allows dynamic ignoring of wrappers or other resources that are not of interest.
4
4
 
5
+ NOTE: This option is separate and distinct from `--include-tags`, which controls whether differences in tags themselves will appear as a difference. For more on `--include-tags`, consult the [options reference](/doc/optionsref.md).
6
+
5
7
  ## Getting Started
6
8
 
7
9
  To use ignored tags, you first need to decide what the name of your tag will be. The standard is `ignored_octocatalog_diff`.
@@ -0,0 +1,51 @@
1
+ # Overriding external scripts
2
+
3
+ ## Background
4
+
5
+ During normal operation, `octocatalog-diff` runs certain scripts or commands from the underlying operating system. For example, it may run `git` to check out a certain code branch, and run `puppet` to build a catalog.
6
+
7
+ Each external script is found within the [`scripts`](/scripts) directory.
8
+
9
+ ## How to override scripts
10
+
11
+ ### Command line option
12
+
13
+ It is possible to override these scripts with customized versions. To do this, specify a directory that contains replacement scripts via the command line:
14
+
15
+ ```
16
+ octocatalog-diff [other options] --override-script-path /path/to/scripts ...
17
+ ```
18
+
19
+ ### Configuration file
20
+
21
+ You can also specify this option via a [configuration file](/doc/configuration.md) setting:
22
+
23
+ ```
24
+ settings[:override_script_path] = '/path/to/scripts'
25
+ ```
26
+
27
+ ### Writing replacement scripts
28
+
29
+ Within the override script path you've configured, place a file with the same name as the built-in script. For example, if you wish to override the `git-extract.sh` script with a custom version, also name your script `git-extract.sh`. (Do NOT create subdirectories within the override directory.)
30
+
31
+ If you specify an override script path but a particular script is not present there, octocatalog-diff will default to the built-in script. This means that you do not need to create unmodified copies of the built-in scripts. Only override the scripts you need to change.
32
+
33
+ ### Notes
34
+
35
+ Please note that these scripts are considered part of octocatalog-diff, and not part of your Puppet codebase. Therefore, the path to your scripts must be an absolute path, and we do not support (or intend to support) using multiple script directories during the same run of octocatalog-diff.
36
+
37
+ ## Explanation of scripts
38
+
39
+ This is an explanation of the [existing scripts supplied by octocatalog-diff](/scripts):
40
+
41
+ - [`env.sh`](/scripts/env)
42
+
43
+ Prints out the environment. This is currently only used for spec tests.
44
+
45
+ - [`git-extract.sh`](/scripts/git-extract)
46
+
47
+ Extracts a specified branch from the git repository into a specified target directory.
48
+
49
+ - [`puppet.sh`](/scripts/puppet)
50
+
51
+ Runs puppet (with additional command line arguments), generally used to compile a catalog or determine the Puppet version.
data/doc/advanced.md CHANGED
@@ -24,6 +24,7 @@ See also:
24
24
  - [Using `octocatalog-diff` without git](/doc/advanced-using-without-git.md)
25
25
  - [Catalog validation](/doc/advanced-catalog-validation.md)
26
26
  - [Environment setup](/doc/advanced-environments.md)
27
+ - [Overriding built-in octocatalog-diff scripts](/doc/advanced-script-override.md)
27
28
 
28
29
  ### Controlling output
29
30
 
data/doc/dev/releasing.md CHANGED
@@ -4,9 +4,11 @@ The project maintainers are responsible for bumping the version number, regenera
4
4
 
5
5
  ## Local testing
6
6
 
7
- To test the new version of `octocatalog-diff` in the Puppet repository:
7
+ *This procedure is performed by a GitHubber.*
8
8
 
9
- 0. In the Puppet checkout, start a new branch based off master.
9
+ To test the new version of `octocatalog-diff` in the GitHub Puppet repository:
10
+
11
+ 0. In a checkout of the GitHub Puppet repository, start a new branch based off master.
10
12
  0. In the `octocatalog-diff` checkout:
11
13
  - Ensure that the desired branch is checked out.
12
14
  - Choose a unique internal version number which has never been used in CI. A good guideline is that if you're planning to release a version `0.6.0` then for these tests, use `0.6.0a`, `0.6.0b`, ...
@@ -23,25 +25,62 @@ To test the new version of `octocatalog-diff` in the Puppet repository:
23
25
 
24
26
  0. Back in the Puppet checkout, ensure that the changes are as expected (updates to Gemfile / Gemfile.lock, addition of new gem). Push the change and build appropriate CI job(s) to validate the changes.
25
27
 
26
- ## Merging
28
+ ## Merging one PR
29
+
30
+ This section is useful when releasing a new version based on one PR submitted by a contributor. Following this workflow is important so that the contributor gets appropriate credit in the revision history for his or her work.
31
+
32
+ 0. In your local checkout, start a new branch based off master.
33
+ 0. Add the contributor's repository as a remote. For example:
34
+
35
+ ```
36
+ git remote add octocat https://github.com/octocat/octocatalog-diff.git
37
+ ```
38
+
39
+ 0. Merge in the contributor's branch into your own. For example:
40
+
41
+ ```
42
+ git merge octocat/some-branch
43
+ ```
44
+
45
+ 0. Update `.version` and `doc/CHANGELOG.md` appropriately. In CHANGELOG you should link to the PR submitted by the contributor (not the PR you're creating now).
46
+ 0. Commit your changes to `.version` and `doc/CHANGELOG.md`.
47
+ 0. If necessary, auto-generate the build documentation, and commit the changes to your branch.
48
+
49
+ ```
50
+ rake doc:build
51
+ ```
52
+
53
+ 0. Open a Pull Request based on your branch. Confirm that the history is correct, showing the contributor's commits first, and then your commit(s) updating the version file, change log, and/or auto-generated documentation.
54
+ 0. Ensure that CI tests are all passing.
55
+ 0. Ensure that you've performed "local testing" within GitHub (typically, ~1 day) to confirm the changes.
56
+ 0. Merge your PR and delete your branch.
57
+ 0. Confirm that the contributor's PR now appears as merged, and any associated issues have been closed.
58
+
59
+ ## Merging multiple PRs
60
+
61
+ If multiple PRs will constitute a release, it's generally easier to merge each such PR individually, and then create a separate PR afterwards to update the necessary files.
27
62
 
28
- 0. If necessary, complete a Pull Request to update the [version file](/.version).
29
- 0. If necessary, auto-generate the build documentation.
63
+ 0. Merge all constituent PRs and ensure that any associated issues have been closed.
64
+ 0. Create your own branch based off master.
65
+ 0. Update `.version` and `doc/CHANGELOG.md` appropriately. In CHANGELOG you should link to the PR submitted by the contributor (not the PR you're creating now).
66
+ 0. Commit your changes to `.version` and `doc/CHANGELOG.md`.
67
+ 0. If necessary, auto-generate the build documentation, and commit the changes to your branch.
30
68
 
31
69
  ```
32
70
  rake doc:build
33
71
  ```
34
72
 
73
+ 0. Open a Pull Request based on your branch.
35
74
  0. Ensure that CI tests are all passing.
36
- 0. Merge and delete the branch.
75
+ 0. Ensure that you've performed "local testing" within GitHub (typically, ~1 day) to confirm the changes.
76
+ 0. Merge your PR and delete your branch.
37
77
 
38
78
  ## Releasing
39
79
 
40
80
  Generally, a new release will correspond to a merge to master of one or more Pull Requests.
41
81
 
42
82
  0. Ensure that all changes associated with the release have been merged to master.
43
- - Merge all Pull Requests associated with release.
44
- - If necessary, complete a Pull Request to update the [change log](/doc/CHANGELOG.md).
83
+ - Merge all Pull Requests associated with release, including the version number bump, change log update, etc.
45
84
  - If necessary (for significant changes), complete a Pull Request to update the top-level README file.
46
85
  0. Ensure the the master branch is checked out on your system.
47
86
  0. Run the release procedure:
data/doc/optionsref.md CHANGED
@@ -139,6 +139,8 @@ Usage: octocatalog-diff [command line options]
139
139
  SSL client certificate to connect to PE ENC
140
140
  --pe-enc-ssl-client-key FILENAME
141
141
  SSL client key to connect to PE ENC
142
+ --override-script-path DIRNAME
143
+ Directory with scripts to override built-ins
142
144
  --no-ignore-tags Disable ignoring based on tags
143
145
  --ignore-tags STRING1[,STRING2[,...]]
144
146
  Specify tags to ignore
@@ -730,6 +732,19 @@ array of differences, where each difference is an array (the octocatalog-diff 0.
730
732
  </td>
731
733
  </tr>
732
734
 
735
+ <tr>
736
+ <td valign=top>
737
+ <pre><code>--override-script-path DIRNAME</code></pre>
738
+ </td>
739
+ <td valign=top>
740
+ Directory with scripts to override built-ins
741
+ </td>
742
+ <td valign=top>
743
+ Provide an optional directory to override default built-in scripts such as git checkout
744
+ and puppet version determination. (<a href="../lib/octocatalog-diff/cli/options/override_script_path.rb">override_script_path.rb</a>)
745
+ </td>
746
+ </tr>
747
+
733
748
  <tr>
734
749
  <td valign=top>
735
750
  <pre><code>--parallel
@@ -266,11 +266,8 @@ module OctocatalogDiff
266
266
  hsh[k] = cleansed_param unless cleansed_param.nil? || cleansed_param.empty?
267
267
  elsif k == 'tags'
268
268
  # The order of tags is unimportant. Sort this array to avoid false diffs if order changes.
269
- # Also if tags is empty, don't add. Most uses of catalog diff will want to ignore tags,
270
- # and if you're ignoring tags you won't get here anyway. Also, don't add empty array of tags.
271
- unless @opts[:ignore_tags]
272
- hsh[k] = v.sort if v.is_a?(Array) && v.any?
273
- end
269
+ # Also if tags is empty, don't add.
270
+ hsh[k] = v.sort if v.is_a?(Array) && v.any?
274
271
  elsif k == 'file' || k == 'line'
275
272
  # We don't care, for the purposes of catalog-diff, from which manifest and line this resource originated.
276
273
  # However, we may report this to the user, so we will keep it in here for now.
@@ -82,8 +82,8 @@ module OctocatalogDiff
82
82
 
83
83
  # Performs the actual bootstrap of a directory. Intended to be called by bootstrap_directory_parallelizer
84
84
  # above, or as part of the parallelized catalog build process from util/catalogs.
85
+ # @param options [Hash] Directory options: branch, path, tag
85
86
  # @param logger [Logger] Logger object
86
- # @param dir_opts [Hash] Directory options: branch, path, tag
87
87
  def self.bootstrap_directory(options, logger)
88
88
  raise ArgumentError, ':path must be supplied' unless options[:path]
89
89
  FileUtils.mkdir_p(options[:path]) unless Dir.exist?(options[:path])
@@ -96,11 +96,11 @@ module OctocatalogDiff
96
96
 
97
97
  # Perform git checkout
98
98
  # @param logger [Logger] Logger object
99
- # @param dir_opts [Hash] Directory options: branch, path, tag
100
- def self.git_checkout(logger, dir_opts)
101
- logger.debug("Begin git checkout #{dir_opts[:basedir]}:#{dir_opts[:branch]} -> #{dir_opts[:path]}")
102
- OctocatalogDiff::CatalogUtil::Git.check_out_git_archive(dir_opts.merge(logger: logger))
103
- logger.debug("Success git checkout #{dir_opts[:basedir]}:#{dir_opts[:branch]} -> #{dir_opts[:path]}")
99
+ # @param options [Hash] Options (need to contain: basedir, branch, path)
100
+ def self.git_checkout(logger, options)
101
+ logger.debug("Begin git checkout #{options[:basedir]}:#{options[:branch]} -> #{options[:path]}")
102
+ OctocatalogDiff::CatalogUtil::Git.check_out_git_archive(options.merge(logger: logger))
103
+ logger.debug("Success git checkout #{options[:basedir]}:#{options[:branch]} -> #{options[:path]}")
104
104
  rescue OctocatalogDiff::Errors::GitCheckoutError => exc
105
105
  logger.error("Git checkout error: #{exc}")
106
106
  raise OctocatalogDiff::Errors::BootstrapError, exc
@@ -234,7 +234,7 @@ module OctocatalogDiff
234
234
 
235
235
  # Munge datadir in hiera config file
236
236
  obj = YAML.load_file(file_src)
237
- (obj[:backends] || %w(yaml json)).each do |key|
237
+ ([obj[:backends]].flatten || %w(yaml json)).each do |key|
238
238
  next unless obj.key?(key.to_sym)
239
239
  if options[:hiera_path_strip].is_a?(String)
240
240
  next if obj[key.to_sym][:datadir].nil?
@@ -21,19 +21,41 @@ module OctocatalogDiff
21
21
 
22
22
  @node = options[:node]
23
23
  raise ArgumentError, 'Node must be specified to compile catalog' if @node.nil? || !@node.is_a?(String)
24
+
25
+ # To be initialized on-demand
26
+ @puppet_argv = nil
27
+ @puppet_binary = nil
28
+ end
29
+
30
+ # Retrieve puppet_command, puppet_binary, puppet_argv
31
+ def puppet_argv
32
+ setup
33
+ @puppet_argv
34
+ end
35
+
36
+ def puppet_binary
37
+ setup
38
+ @puppet_binary
24
39
  end
25
40
 
26
- # Build up the command line to run Puppet
27
41
  def puppet_command
28
- cmdline = []
42
+ setup
43
+ [@puppet_binary, @puppet_argv].flatten.join(' ')
44
+ end
45
+
46
+ private
47
+
48
+ # Build up the command line to run Puppet
49
+ def setup
50
+ return if @puppet_binary && @puppet_argv
29
51
 
30
52
  # Where is the puppet binary?
31
- puppet = @options[:puppet_binary]
32
- raise ArgumentError, 'Puppet binary was not supplied' if puppet.nil?
33
- raise Errno::ENOENT, "Puppet binary #{puppet} doesn't exist" unless File.file?(puppet)
34
- cmdline << puppet
53
+ @puppet_binary = @options[:puppet_binary]
54
+ raise ArgumentError, 'Puppet binary was not supplied' if @puppet_binary.nil?
55
+ raise Errno::ENOENT, "Puppet binary #{@puppet_binary} doesn't exist" unless File.file?(@puppet_binary)
35
56
 
36
57
  # Node to compile
58
+ cmdline = []
37
59
  cmdline.concat ['master', '--compile', Shellwords.escape(@node)]
38
60
 
39
61
  # storeconfigs?
@@ -99,11 +121,9 @@ module OctocatalogDiff
99
121
  override_and_append_commandline_with_user_supplied_arguments(cmdline)
100
122
 
101
123
  # Return full command
102
- cmdline.join(' ')
124
+ @puppet_argv = cmdline
103
125
  end
104
126
 
105
- private
106
-
107
127
  # Private: Mutate the command line with arguments that were passed directly from the
108
128
  # user. This appends new arguments and overwrites existing arguments.
109
129
  # @param cmdline [Array] Existing command line - mutated by this method
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fileutils'
4
- require 'open3'
5
3
  require 'rugged'
6
- require 'shellwords'
7
- require 'tempfile'
8
4
 
9
5
  require_relative '../errors'
6
+ require_relative '../util/scriptrunner'
10
7
 
11
8
  module OctocatalogDiff
12
9
  module CatalogUtil
@@ -24,6 +21,7 @@ module OctocatalogDiff
24
21
  path = options.fetch(:path)
25
22
  dir = options.fetch(:basedir)
26
23
  logger = options.fetch(:logger)
24
+ override_script_path = options.fetch(:override_script_path, nil)
27
25
 
28
26
  # Validate parameters
29
27
  if dir.nil? || !File.directory?(dir)
@@ -34,31 +32,26 @@ module OctocatalogDiff
34
32
  end
35
33
 
36
34
  # Create and execute checkout script
37
- script = create_git_checkout_script(branch, path)
38
- logger.debug("Begin git archive #{dir}:#{branch} -> #{path}")
39
- output, status = Open3.capture2e(script, chdir: dir)
40
- unless status.exitstatus.zero?
41
- raise OctocatalogDiff::Errors::GitCheckoutError, "Git archive #{branch}->#{path} failed: #{output}"
42
- end
43
- logger.debug("Success git archive #{dir}:#{branch}")
44
- end
35
+ sr_opts = {
36
+ logger: logger,
37
+ default_script: 'git-extract/git-extract.sh',
38
+ override_script_path: override_script_path
39
+ }
40
+ script = OctocatalogDiff::Util::ScriptRunner.new(sr_opts)
45
41
 
46
- # Create the temporary file used to interact with the git command line.
47
- # To get the options working correctly (-o pipefail in particular) this needs to run under
48
- # bash. It's just creating a script, rather than figuring out all the shell escapes...
49
- # @param branch [String] Branch name
50
- # @param path [String] Target directory
51
- # @return [String] Name of script
52
- def self.create_git_checkout_script(branch, path)
53
- tmp_script = Tempfile.new(['git-checkout', '.sh'])
54
- tmp_script.write "#!/bin/bash\n"
55
- tmp_script.write "set -euf -o pipefail\n"
56
- tmp_script.write "git archive --format=tar #{Shellwords.escape(branch)} | \\\n"
57
- tmp_script.write " ( cd #{Shellwords.escape(path)} && tar -xf - )\n"
58
- tmp_script.close
59
- FileUtils.chmod 0o755, tmp_script.path
60
- at_exit { FileUtils.rm_f tmp_script.path if File.exist?(tmp_script.path) }
61
- tmp_script.path
42
+ sr_run_opts = {
43
+ :working_dir => dir,
44
+ :pass_env_vars => options[:pass_env_vars],
45
+ 'OCD_GIT_EXTRACT_BRANCH' => branch,
46
+ 'OCD_GIT_EXTRACT_TARGET' => path
47
+ }
48
+
49
+ begin
50
+ script.run(sr_run_opts)
51
+ logger.debug("Success git archive #{dir}:#{branch}")
52
+ rescue OctocatalogDiff::Util::ScriptRunner::ScriptException
53
+ raise OctocatalogDiff::Errors::GitCheckoutError, "Git archive #{branch}->#{path} failed: #{script.output}"
54
+ end
62
55
  end
63
56
 
64
57
  # Determine the SHA of origin/master (or any other branch really) in the git repo
@@ -201,29 +201,56 @@ module OctocatalogDiff
201
201
  end
202
202
  return if missing.empty?
203
203
 
204
- # At this point there is at least one broken/missing reference. Format an error message and
205
- # raise. Error message will look like this:
206
- # ---
207
- # Catalog has broken references: exec[subscribe caller 1] -> subscribe[Exec[subscribe target]];
208
- # exec[subscribe caller 2] -> subscribe[Exec[subscribe target]]; exec[subscribe caller 2] ->
209
- # subscribe[Exec[subscribe target 2]]
210
- # ---
204
+ # At this point there is at least one broken/missing reference. Format an error message and raise.
205
+ errors = format_missing_references(missing)
206
+ plural = errors =~ /;/ ? 's' : ''
207
+ raise OctocatalogDiff::Errors::ReferenceValidationError, "Catalog has broken reference#{plural}: #{errors}"
208
+ end
209
+
210
+ private
211
+
212
+ # Private method: Format the name of the source file and line number, based on compilation directory and
213
+ # other settings. This is used by format_missing_references.
214
+ # @param source_file [String] Raw source file name from catalog
215
+ # @param line_number [Fixnum] Line number from catalog
216
+ # @return [String] Formatted source file
217
+ def format_source_file_line(source_file, line_number)
218
+ return '' if source_file.nil? || source_file.empty?
219
+ filename = if compilation_dir && source_file.start_with?(compilation_dir)
220
+ stripped_file = source_file[compilation_dir.length..-1]
221
+ stripped_file.start_with?('/') ? stripped_file[1..-1] : stripped_file
222
+ else
223
+ source_file
224
+ end
225
+ "(#{filename.sub(%r{^environments/production/}, '')}:#{line_number})"
226
+ end
227
+
228
+ # Private method: Format the missing references into human-readable text
229
+ # Error message will look like this:
230
+ # ---
231
+ # Catalog has broken references: exec[subscribe caller 1](file:line) -> subscribe[Exec[subscribe target]];
232
+ # exec[subscribe caller 2](file:line) -> subscribe[Exec[subscribe target]]; exec[subscribe caller 2](file:line) ->
233
+ # subscribe[Exec[subscribe target 2]]
234
+ # ---
235
+ # @param missing [Array] Array of missing references
236
+ # @return [String] Formatted references
237
+ def format_missing_references(missing)
238
+ unless missing.is_a?(Array) && missing.any?
239
+ raise ArgumentError, 'format_missing_references() requires a non-empty array as input'
240
+ end
241
+
211
242
  formatted_references = missing.map do |obj|
212
243
  # obj[:target_value] can be a string or an array. If it's an array, create a
213
244
  # separate error message per element of that array. This allows the total number
214
245
  # of errors to be correct.
215
- src = "#{obj[:source]['type'].downcase}[#{obj[:source]['title']}]"
246
+ src_ref = "#{obj[:source]['type'].downcase}[#{obj[:source]['title']}]"
247
+ src_file = format_source_file_line(obj[:source]['file'], obj[:source]['line'])
216
248
  target_val = obj[:target_value].is_a?(Array) ? obj[:target_value] : [obj[:target_value]]
217
- target_val.map { |tv| "#{src} -> #{obj[:target_type].downcase}[#{tv}]" }
218
- end
219
- formatted_references.flatten!
220
- plural = formatted_references.size == 1 ? '' : 's'
221
- errors = formatted_references.join('; ')
222
- raise OctocatalogDiff::Errors::ReferenceValidationError, "Catalog has broken reference#{plural}: #{errors}"
249
+ target_val.map { |tv| "#{src_ref}#{src_file} -> #{obj[:target_type].downcase}[#{tv}]" }
250
+ end.flatten
251
+ formatted_references.join('; ')
223
252
  end
224
253
 
225
- private
226
-
227
254
  # Private method: Given a list of resources to check, return the references from
228
255
  # that list that are missing from the catalog. (An empty array returned would indicate
229
256
  # all references are present in the catalog.)
@@ -2,14 +2,14 @@
2
2
 
3
3
  require 'fileutils'
4
4
  require 'json'
5
- require 'open3'
6
5
  require 'stringio'
7
6
 
8
7
  require_relative '../catalog-util/bootstrap'
9
8
  require_relative '../catalog-util/builddir'
10
9
  require_relative '../catalog-util/command'
11
- require_relative '../util/puppetversion'
12
10
  require_relative '../catalog-util/facts'
11
+ require_relative '../util/puppetversion'
12
+ require_relative '../util/scriptrunner'
13
13
 
14
14
  module OctocatalogDiff
15
15
  class Catalog
@@ -160,37 +160,60 @@ module OctocatalogDiff
160
160
 
161
161
  # Get the command to compile the catalog
162
162
  # @return [String] Puppet command line
163
- def puppet_command(options = @opts)
164
- return @puppet_command if @puppet_command
165
- raise ArgumentError, '"puppet_binary" was not passed to OctocatalogDiff::Catalog::Computed' unless @puppet_binary
166
- command_opts = options.merge(
167
- node: @node,
168
- compilation_dir: @builddir.tempdir,
169
- parser: options.fetch(:parser, :default),
170
- puppet_binary: @puppet_binary,
171
- fact_file: @builddir.fact_file,
172
- dir: @builddir.tempdir,
173
- enc: @builddir.enc
174
- )
175
- command = OctocatalogDiff::CatalogUtil::Command.new(command_opts)
176
- @puppet_command = command.puppet_command
163
+ def puppet_command
164
+ puppet_command_obj.puppet_command
165
+ end
166
+
167
+ def puppet_command_obj
168
+ @puppet_command_obj ||= begin
169
+ raise ArgumentError, '"puppet_binary" was not passed to OctocatalogDiff::Catalog::Computed' unless @puppet_binary
170
+
171
+ command_opts = @opts.merge(
172
+ node: @node,
173
+ compilation_dir: @builddir.tempdir,
174
+ parser: @opts.fetch(:parser, :default),
175
+ puppet_binary: @puppet_binary,
176
+ fact_file: @builddir.fact_file,
177
+ dir: @builddir.tempdir,
178
+ enc: @builddir.enc
179
+ )
180
+ OctocatalogDiff::CatalogUtil::Command.new(command_opts)
181
+ end
177
182
  end
178
183
 
179
184
  # Private method: Actually execute puppet
180
185
  # @return [Hash] { stdout, stderr, exitcode }
181
- def exec_puppet
186
+ def exec_puppet(logger)
182
187
  # This is the environment provided to the puppet command.
183
- env = {
184
- 'HOME' => ENV['HOME'],
185
- 'PATH' => ENV['PATH'],
186
- 'PWD' => @builddir.tempdir
187
- }
188
+ env = {}
188
189
  @pass_env_vars.each { |var| env[var] ||= ENV[var] }
189
- out, err, status = Open3.capture3(env, puppet_command, unsetenv_others: true, chdir: @builddir.tempdir)
190
+
191
+ # This is the Puppet command itself
192
+ env['OCD_PUPPET_BINARY'] = @puppet_command_obj.puppet_binary
193
+
194
+ # Additional passed-in options
195
+ sr_run_opts = env.merge(
196
+ logger: logger,
197
+ working_dir: @builddir.tempdir,
198
+ argv: @puppet_command_obj.puppet_argv
199
+ )
200
+
201
+ # Set up the ScriptRunner
202
+ scriptrunner = OctocatalogDiff::Util::ScriptRunner.new(
203
+ default_script: 'puppet/puppet.sh',
204
+ override_script_path: @opts[:override_script_path]
205
+ )
206
+
207
+ begin
208
+ scriptrunner.run(sr_run_opts)
209
+ rescue OctocatalogDiff::Util::ScriptRunner::ScriptException => exc
210
+ logger.warn "Puppet command failed: #{exc.message}" if logger
211
+ end
212
+
190
213
  {
191
- stdout: out,
192
- stderr: err,
193
- exitcode: status.exitstatus
214
+ stdout: scriptrunner.stdout,
215
+ stderr: scriptrunner.stderr,
216
+ exitcode: scriptrunner.exitcode
194
217
  }
195
218
  end
196
219
 
@@ -216,7 +239,7 @@ module OctocatalogDiff
216
239
  @retries = retry_num
217
240
  time_begin = Time.now
218
241
  logger.debug("(#{@tag}) Try #{1 + retry_num} executing Puppet #{puppet_version}: #{puppet_command}")
219
- result = exec_puppet
242
+ result = exec_puppet(logger)
220
243
 
221
244
  # Success
222
245
  if (result[:exitcode]).zero?
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Provide an optional directory to override default built-in scripts such as git checkout
4
+ # and puppet version determination.
5
+ # @param parser [OptionParser object] The OptionParser argument
6
+ # @param options [Hash] Options hash being constructed; this is modified in this method.
7
+ OctocatalogDiff::Cli::Options::Option.newoption(:override_script_path) do
8
+ has_weight 385
9
+
10
+ def parse(parser, options)
11
+ parser.on('--override-script-path DIRNAME', 'Directory with scripts to override built-ins') do |dir|
12
+ unless dir.start_with?('/')
13
+ raise ArgumentError, 'Absolute path is required for --override-script-path'
14
+ end
15
+
16
+ unless File.directory?(dir)
17
+ raise Errno::ENOENT, 'Invalid --override-script-path'
18
+ end
19
+
20
+ options[:override_script_path] = dir
21
+ end
22
+ end
23
+ end
@@ -2,9 +2,7 @@
2
2
 
3
3
  # Helper to determine the version of Puppet
4
4
 
5
- require 'fileutils'
6
- require 'open3'
7
- require 'shellwords'
5
+ require_relative 'scriptrunner'
8
6
 
9
7
  module OctocatalogDiff
10
8
  module Util
@@ -17,19 +15,27 @@ module OctocatalogDiff
17
15
  def self.puppet_version(puppet, options = {})
18
16
  raise ArgumentError, 'Puppet binary was not supplied' if puppet.nil?
19
17
  raise Errno::ENOENT, "Puppet binary #{puppet} doesn't exist" unless File.file?(puppet)
20
- cmdline = [Shellwords.escape(puppet), '--version'].join(' ')
21
18
 
22
- # This is the environment provided to the puppet command.
23
- env = {
24
- 'HOME' => ENV['HOME'],
25
- 'PATH' => ENV['PATH'],
26
- 'PWD' => File.dirname(puppet)
19
+ sr_opts = {
20
+ default_script: 'puppet/puppet.sh',
21
+ override_script_path: options[:override_script_path]
27
22
  }
28
- pass_env_vars = options.fetch(:pass_env_vars, [])
29
- pass_env_vars.each { |var| env[var] ||= ENV[var] }
30
- out, err, _status = Open3.capture3(env, cmdline, unsetenv_others: true, chdir: env['PWD'])
31
- return Regexp.last_match(1) if out =~ /^([\d\.]+)\s*$/
32
- raise "Unable to determine Puppet version: #{out} #{err}"
23
+
24
+ script = OctocatalogDiff::Util::ScriptRunner.new(sr_opts)
25
+
26
+ sr_run_opts = {
27
+ :logger => options[:logger],
28
+ :working_dir => File.dirname(puppet),
29
+ :pass_env_vars => options[:pass_env_vars],
30
+ :argv => '--version',
31
+ 'OCD_PUPPET_BINARY' => puppet
32
+ }
33
+
34
+ output = script.run(sr_run_opts)
35
+ return Regexp.last_match(1) if output =~ /^([\d\.]+)\s*$/
36
+ # :nocov:
37
+ raise "Unable to determine Puppet version: #{script.output}"
38
+ # :nocov:
33
39
  end
34
40
  end
35
41
  end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Execute a built-in script (which can also be overridden with a user-supplied script)
4
+
5
+ require 'fileutils'
6
+ require 'open3'
7
+ require 'shellwords'
8
+
9
+ module OctocatalogDiff
10
+ module Util
11
+ # This is a utility class to execute a built-in script.
12
+ class ScriptRunner
13
+ # For an exception running the script
14
+ class ScriptException < RuntimeError; end
15
+
16
+ attr_reader :script, :script_src, :logger, :stdout, :stderr, :exitcode
17
+
18
+ # Create the object - the object is a configured script, which can be executed multiple
19
+ # times with different environment varibles.
20
+ #
21
+ # @param opts [Hash] Options hash
22
+ # opts[:default_script] (Required) Path to script, relative to `scripts` directory
23
+ # opts[:logger] (Optional) Logger object
24
+ # opts[:override_script_path] (Optional) Directory where a similarly-named script MAY exist
25
+ def initialize(opts = {})
26
+ @logger = opts[:logger]
27
+ @script_src = find_script(opts.fetch(:default_script), opts[:override_script_path])
28
+ @script = temp_script(@script_src)
29
+ @stdout = nil
30
+ @stderr = nil
31
+ @exitcode = nil
32
+ end
33
+
34
+ # Execute the script from a given working directory, with additional environment variables
35
+ # specified in the options hash.
36
+ #
37
+ # @param opts [Hash] Options hash
38
+ # opts[:working_dir] (Required) Directory where script is to be executed
39
+ # opts[:argv] (Optional Array) Command line arguments
40
+ # opts[:pass_env_vars] (Optional Array) Environment variables to pass (default: HOME, PATH)
41
+ # opts[<STRING>] (Optional) Environment variable
42
+ def run(opts = {})
43
+ working_dir = opts.fetch(:working_dir)
44
+ assert_directory_exists(working_dir)
45
+
46
+ argv = opts.fetch(:argv, [])
47
+ logger = opts[:logger] || @logger
48
+
49
+ pass_env_vars = [opts[:pass_env_vars], 'HOME', 'PATH'].flatten.compact
50
+ env = opts.select { |k, _v| k.is_a?(String) }
51
+ pass_env_vars.each { |var| env[var] ||= ENV[var] }
52
+ env['PWD'] = working_dir
53
+
54
+ cmdline = [script, argv].flatten.compact.map { |x| Shellwords.escape(x) }.join(' ')
55
+ log(:debug, "Execute: #{cmdline}", opts[:logger])
56
+
57
+ @stdout, @stderr, status = Open3.capture3(env, cmdline, unsetenv_others: true, chdir: working_dir)
58
+ @exitcode = status.exitstatus
59
+
60
+ @stderr.split(/\n/).select { |line| line =~ /\S/ }.each { |line| log(:debug, "STDERR: #{line}", logger) }
61
+ log(:debug, "Exit status: #{@exitcode}", logger)
62
+ return @stdout if @exitcode.zero?
63
+ raise ScriptException, output
64
+ end
65
+
66
+ # All output from the latest execution of the command.
67
+ # @return [String] Combined output of STDOUT and STDERR
68
+ def output
69
+ return if @exitcode.nil?
70
+ [
71
+ 'STDOUT:',
72
+ @stdout.split(/\n/).map { |line| " #{line}" },
73
+ 'STDERR:',
74
+ @stderr.split(/\n/).map { |line| " #{line}" }
75
+ ].flatten.compact.join("\n")
76
+ end
77
+
78
+ private
79
+
80
+ # PRIVATE: Log a message, if logger is defined. Since this might be called under `parallel`
81
+ # it's possible that the logger isn't defined, and if so the logged message is skipped.
82
+ def log(priority, message, logger = @logger)
83
+ return unless logger
84
+ logger.send(priority, [message])
85
+ end
86
+
87
+ # PRIVATE: Create a temporary file with the contents of the script and mark the script executable.
88
+ # This is to avoid changing ownership or permissions on any user-supplied file.
89
+ #
90
+ # @param script [String] Path to script
91
+ # @return [String] Path to tempfile containing script
92
+ def temp_script(script)
93
+ raise Errno::ENOENT, "Script '#{script}' not found" unless File.file?(script)
94
+ temp_dir = Dir.mktmpdir
95
+ at_exit do
96
+ begin
97
+ FileUtils.remove_entry_secure temp_dir
98
+ rescue Errno::ENOENT # rubocop:disable Lint/HandleExceptions
99
+ # OK if the directory doesn't exist since we're trying to remove it anyway
100
+ end
101
+ end
102
+ temp_file = File.join(temp_dir, File.basename(script))
103
+ File.open(temp_file, 'w') { |f| f.write(File.read(script)) }
104
+ FileUtils.chmod 0o755, temp_file
105
+ temp_file
106
+ end
107
+
108
+ # PRIVATE: Determine the path to the script to execute, taking into account the default script
109
+ # location and the optional override script path.
110
+ #
111
+ # @param default_script [String] Path to script, relative to `scripts` directory
112
+ # @param override_script_path [String] Optional directory with override script
113
+ # @return [String] Full path to script
114
+ def find_script(default_script, override_script_path = nil)
115
+ script = find_script_from_override_path(default_script, override_script_path) ||
116
+ File.expand_path("../../../scripts/#{default_script}", File.dirname(__FILE__))
117
+ raise Errno::ENOENT, "Unable to locate script '#{script}'" unless File.file?(script)
118
+ script
119
+ end
120
+
121
+ # PRIVATE: Find script from override path.
122
+ #
123
+ # @param default_script [String] Path to script, relative to `scripts` directory
124
+ # @param override_script_path [String] Optional directory
125
+ # @return [String] Override script if found, else nil
126
+ def find_script_from_override_path(default_script, override_script_path = nil)
127
+ return unless override_script_path
128
+ script_test = File.join(override_script_path, File.basename(default_script))
129
+ if File.file?(script_test)
130
+ log(:debug, "Selecting #{script_test} from override script path")
131
+ script_test
132
+ else
133
+ log(:debug, "Did not find #{script_test} in override script path")
134
+ nil
135
+ end
136
+ end
137
+
138
+ # PRIVATE: Assert that a directory exists (and is a directory). Raise error if not.
139
+ #
140
+ # @param dir [String] Directory to test
141
+ def assert_directory_exists(dir)
142
+ return if File.directory?(dir)
143
+ raise Errno::ENOENT, "Invalid directory '#{dir}'"
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ # This script echoes back the environment. This is used for spec testing
4
+ # and possible debugging.
5
+
6
+ env
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+
3
+ # This script is called from lib/octocatalog-diff/catalog-util/git.rb and is used to
4
+ # archive and extract a certain branch of a git repository into a target directory.
5
+
6
+ if [ -z "$OCD_GIT_EXTRACT_BRANCH" ]; then
7
+ echo "Error: Must declare OCD_GIT_EXTRACT_BRANCH"
8
+ exit 255
9
+ fi
10
+
11
+ if [ -z "$OCD_GIT_EXTRACT_TARGET" ]; then
12
+ echo "Error: Must declare OCD_GIT_EXTRACT_TARGET"
13
+ exit 255
14
+ fi
15
+
16
+ set -euf -o pipefail
17
+ git archive --format=tar "$OCD_GIT_EXTRACT_BRANCH" | ( cd "$OCD_GIT_EXTRACT_TARGET" && tar -xf - )
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+
3
+ # Script to run Puppet. The default implementation here is simply to pass
4
+ # through the command line arguments (which are likely to be numerous when
5
+ # compiling a catalog).
6
+
7
+ if [ -z "$OCD_PUPPET_BINARY" ]; then
8
+ echo "Error: PUPPET_BINARY must be set"
9
+ exit 255
10
+ fi
11
+
12
+ "$OCD_PUPPET_BINARY" "$@"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: octocatalog-diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub, Inc.
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-03-17 00:00:00.000000000 Z
12
+ date: 2017-05-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: diffy
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 1.9.0
62
+ version: 1.11.1
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: 1.9.0
69
+ version: 1.11.1
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rugged
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -141,16 +141,16 @@ dependencies:
141
141
  name: rubocop
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
- - - "~>"
144
+ - - '='
145
145
  - !ruby/object:Gem::Version
146
- version: '0.35'
146
+ version: 0.48.1
147
147
  type: :development
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
- - - "~>"
151
+ - - '='
152
152
  - !ruby/object:Gem::Version
153
- version: '0.35'
153
+ version: 0.48.1
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: puppetdb-terminus
156
156
  requirement: !ruby/object:Gem::Requirement
@@ -171,14 +171,14 @@ dependencies:
171
171
  requirements:
172
172
  - - ">="
173
173
  - !ruby/object:Gem::Version
174
- version: '0'
174
+ version: 0.14.1
175
175
  type: :development
176
176
  prerelease: false
177
177
  version_requirements: !ruby/object:Gem::Requirement
178
178
  requirements:
179
179
  - - ">="
180
180
  - !ruby/object:Gem::Version
181
- version: '0'
181
+ version: 0.14.1
182
182
  - !ruby/object:Gem::Dependency
183
183
  name: simplecov-json
184
184
  requirement: !ruby/object:Gem::Requirement
@@ -199,14 +199,14 @@ dependencies:
199
199
  requirements:
200
200
  - - "~>"
201
201
  - !ruby/object:Gem::Version
202
- version: 4.5.3
202
+ version: 4.10.0
203
203
  type: :development
204
204
  prerelease: false
205
205
  version_requirements: !ruby/object:Gem::Requirement
206
206
  requirements:
207
207
  - - "~>"
208
208
  - !ruby/object:Gem::Version
209
- version: 4.5.3
209
+ version: 4.10.0
210
210
  description: |
211
211
  Octocatalog-Diff assists with Puppet development and testing by enabling the user to
212
212
  compile 2 Puppet catalogs and compare them. It is possible to compare different
@@ -241,6 +241,7 @@ files:
241
241
  - doc/advanced-pe-enc.md
242
242
  - doc/advanced-puppet-master.md
243
243
  - doc/advanced-puppet-versions.md
244
+ - doc/advanced-script-override.md
244
245
  - doc/advanced-storeconfigs.md
245
246
  - doc/advanced-using-without-git.md
246
247
  - doc/advanced.md
@@ -351,6 +352,7 @@ files:
351
352
  - lib/octocatalog-diff/cli/options/master_cache_branch.rb
352
353
  - lib/octocatalog-diff/cli/options/output_file.rb
353
354
  - lib/octocatalog-diff/cli/options/output_format.rb
355
+ - lib/octocatalog-diff/cli/options/override_script_path.rb
354
356
  - lib/octocatalog-diff/cli/options/parallel.rb
355
357
  - lib/octocatalog-diff/cli/options/parser.rb
356
358
  - lib/octocatalog-diff/cli/options/pass_env_vars.rb
@@ -402,7 +404,11 @@ files:
402
404
  - lib/octocatalog-diff/util/httparty.rb
403
405
  - lib/octocatalog-diff/util/parallel.rb
404
406
  - lib/octocatalog-diff/util/puppetversion.rb
407
+ - lib/octocatalog-diff/util/scriptrunner.rb
405
408
  - lib/octocatalog-diff/version.rb
409
+ - scripts/env/env.sh
410
+ - scripts/git-extract/git-extract.sh
411
+ - scripts/puppet/puppet.sh
406
412
  homepage: https://github.com/github/octocatalog-diff
407
413
  licenses:
408
414
  - MIT