Skip to content

Commit af38f33

Browse files
authored
Merge pull request #98 from github/kpaulisse-git-checkout-script
Separate scripts and commands and make override-able
2 parents 8805b61 + 25bf5f9 commit af38f33

File tree

20 files changed

+641
-169
lines changed

20 files changed

+641
-169
lines changed

doc/advanced-script-override.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

doc/advanced.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ See also:
2424
- [Using `octocatalog-diff` without git](/doc/advanced-using-without-git.md)
2525
- [Catalog validation](/doc/advanced-catalog-validation.md)
2626
- [Environment setup](/doc/advanced-environments.md)
27+
- [Overriding built-in octocatalog-diff scripts](/doc/advanced-script-override.md)
2728

2829
### Controlling output
2930

doc/optionsref.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ Usage: octocatalog-diff [command line options]
139139
SSL client certificate to connect to PE ENC
140140
--pe-enc-ssl-client-key FILENAME
141141
SSL client key to connect to PE ENC
142+
--override-script-path DIRNAME
143+
Directory with scripts to override built-ins
142144
--no-ignore-tags Disable ignoring based on tags
143145
--ignore-tags STRING1[,STRING2[,...]]
144146
Specify tags to ignore
@@ -730,6 +732,19 @@ array of differences, where each difference is an array (the octocatalog-diff 0.
730732
</td>
731733
</tr>
732734

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+
733748
<tr>
734749
<td valign=top>
735750
<pre><code>--parallel

lib/octocatalog-diff/catalog-util/bootstrap.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ def self.bootstrap_directory_parallelizer(options, logger)
8282

8383
# Performs the actual bootstrap of a directory. Intended to be called by bootstrap_directory_parallelizer
8484
# above, or as part of the parallelized catalog build process from util/catalogs.
85+
# @param options [Hash] Directory options: branch, path, tag
8586
# @param logger [Logger] Logger object
86-
# @param dir_opts [Hash] Directory options: branch, path, tag
8787
def self.bootstrap_directory(options, logger)
8888
raise ArgumentError, ':path must be supplied' unless options[:path]
8989
FileUtils.mkdir_p(options[:path]) unless Dir.exist?(options[:path])
@@ -96,11 +96,11 @@ def self.bootstrap_directory(options, logger)
9696

9797
# Perform git checkout
9898
# @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]}")
104104
rescue OctocatalogDiff::Errors::GitCheckoutError => exc
105105
logger.error("Git checkout error: #{exc}")
106106
raise OctocatalogDiff::Errors::BootstrapError, exc

lib/octocatalog-diff/catalog-util/command.rb

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,41 @@ def initialize(options = {}, logger = nil)
2121

2222
@node = options[:node]
2323
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
2439
end
2540

26-
# Build up the command line to run Puppet
2741
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
2951

3052
# 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)
3556

3657
# Node to compile
58+
cmdline = []
3759
cmdline.concat ['master', '--compile', Shellwords.escape(@node)]
3860

3961
# storeconfigs?
@@ -99,11 +121,9 @@ def puppet_command
99121
override_and_append_commandline_with_user_supplied_arguments(cmdline)
100122

101123
# Return full command
102-
cmdline.join(' ')
124+
@puppet_argv = cmdline
103125
end
104126

105-
private
106-
107127
# Private: Mutate the command line with arguments that were passed directly from the
108128
# user. This appends new arguments and overwrites existing arguments.
109129
# @param cmdline [Array] Existing command line - mutated by this method

lib/octocatalog-diff/catalog-util/git.rb

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
# frozen_string_literal: true
22

3-
require 'fileutils'
4-
require 'open3'
53
require 'rugged'
6-
require 'shellwords'
7-
require 'tempfile'
84

95
require_relative '../errors'
6+
require_relative '../util/scriptrunner'
107

118
module OctocatalogDiff
129
module CatalogUtil
@@ -24,6 +21,7 @@ def self.check_out_git_archive(options = {})
2421
path = options.fetch(:path)
2522
dir = options.fetch(:basedir)
2623
logger = options.fetch(:logger)
24+
override_script_path = options.fetch(:override_script_path, nil)
2725

2826
# Validate parameters
2927
if dir.nil? || !File.directory?(dir)
@@ -34,31 +32,26 @@ def self.check_out_git_archive(options = {})
3432
end
3533

3634
# 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)
4541

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
6255
end
6356

6457
# Determine the SHA of origin/master (or any other branch really) in the git repo

lib/octocatalog-diff/catalog/computed.rb

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
require 'fileutils'
44
require 'json'
5-
require 'open3'
65
require 'stringio'
76

87
require_relative '../catalog-util/bootstrap'
98
require_relative '../catalog-util/builddir'
109
require_relative '../catalog-util/command'
11-
require_relative '../util/puppetversion'
1210
require_relative '../catalog-util/facts'
11+
require_relative '../util/puppetversion'
12+
require_relative '../util/scriptrunner'
1313

1414
module OctocatalogDiff
1515
class Catalog
@@ -160,37 +160,60 @@ def build_catalog(logger = nil)
160160

161161
# Get the command to compile the catalog
162162
# @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
177182
end
178183

179184
# Private method: Actually execute puppet
180185
# @return [Hash] { stdout, stderr, exitcode }
181-
def exec_puppet
186+
def exec_puppet(logger)
182187
# 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 = {}
188189
@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+
190213
{
191-
stdout: out,
192-
stderr: err,
193-
exitcode: status.exitstatus
214+
stdout: scriptrunner.stdout,
215+
stderr: scriptrunner.stderr,
216+
exitcode: scriptrunner.exitcode
194217
}
195218
end
196219

@@ -216,7 +239,7 @@ def run_puppet(logger)
216239
@retries = retry_num
217240
time_begin = Time.now
218241
logger.debug("(#{@tag}) Try #{1 + retry_num} executing Puppet #{puppet_version}: #{puppet_command}")
219-
result = exec_puppet
242+
result = exec_puppet(logger)
220243

221244
# Success
222245
if (result[:exitcode]).zero?
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)