pdi 2.0.0.pre.alpha
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.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +25 -0
- data/.ruby-version +1 -0
- data/.travis.yml +25 -0
- data/CHANGELOG.md +19 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/Guardfile +16 -0
- data/LICENSE +7 -0
- data/README.md +137 -0
- data/Rakefile +17 -0
- data/bin/console +18 -0
- data/lib/pdi.rb +17 -0
- data/lib/pdi/executor.rb +54 -0
- data/lib/pdi/executor/result.rb +31 -0
- data/lib/pdi/executor/status.rb +27 -0
- data/lib/pdi/spoon.rb +106 -0
- data/lib/pdi/spoon/kitchen_error.rb +36 -0
- data/lib/pdi/spoon/options.rb +82 -0
- data/lib/pdi/spoon/options/arg.rb +68 -0
- data/lib/pdi/spoon/options/level.rb +23 -0
- data/lib/pdi/spoon/options/param.rb +29 -0
- data/lib/pdi/spoon/pan_error.rb +37 -0
- data/lib/pdi/spoon/parser.rb +25 -0
- data/lib/pdi/spoon/result.rb +25 -0
- data/lib/pdi/version.rb +12 -0
- data/pdi.gemspec +33 -0
- data/spec/mocks/spoon/return_code.sh +14 -0
- data/spec/mocks/spoon/sleep.sh +10 -0
- data/spec/mocks/spoon/version.sh +10 -0
- data/spec/pdi/executor_spec.rb +52 -0
- data/spec/pdi/spoon/kitchen_error_spec.rb +42 -0
- data/spec/pdi/spoon/options/arg_spec.rb +73 -0
- data/spec/pdi/spoon/options/param_spec.rb +39 -0
- data/spec/pdi/spoon/options_spec.rb +102 -0
- data/spec/pdi/spoon/pan_error_spec.rb +42 -0
- data/spec/pdi/spoon_spec.rb +175 -0
- data/spec/spec_helper.rb +22 -0
- metadata +207 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'status'
|
11
|
+
|
12
|
+
module Pdi
|
13
|
+
class Executor
|
14
|
+
# General return object for an execution call result.
|
15
|
+
class Result
|
16
|
+
extend Forwardable
|
17
|
+
acts_as_hashable
|
18
|
+
|
19
|
+
attr_reader :args, :status
|
20
|
+
|
21
|
+
def_delegators :status, :code, :out, :pid
|
22
|
+
|
23
|
+
def initialize(args:, status: {})
|
24
|
+
@args = args
|
25
|
+
@status = Status.make(status)
|
26
|
+
|
27
|
+
freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Pdi
|
11
|
+
class Executor
|
12
|
+
# General return object for describing the operating system return data.
|
13
|
+
class Status
|
14
|
+
acts_as_hashable
|
15
|
+
|
16
|
+
attr_reader :code, :out, :pid
|
17
|
+
|
18
|
+
def initialize(code:, out: '', pid:)
|
19
|
+
@code = code
|
20
|
+
@out = out
|
21
|
+
@pid = pid
|
22
|
+
|
23
|
+
freeze
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/pdi/spoon.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'spoon/kitchen_error'
|
11
|
+
require_relative 'spoon/options'
|
12
|
+
require_relative 'spoon/pan_error'
|
13
|
+
require_relative 'spoon/parser'
|
14
|
+
require_relative 'spoon/result'
|
15
|
+
|
16
|
+
module Pdi
|
17
|
+
# This class is the main wrapper for PDI's pan and kitchen scripts.
|
18
|
+
class Spoon
|
19
|
+
DEFAULT_KITCHEN = 'kitchen.sh'
|
20
|
+
DEFAULT_PAN = 'pan.sh'
|
21
|
+
|
22
|
+
TYPES_TO_ERRORS = {
|
23
|
+
Options::Type::JOB => KitchenError,
|
24
|
+
Options::Type::TRANSFORMATION => PanError
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
attr_reader :args, :dir, :kitchen, :pan
|
28
|
+
|
29
|
+
def initialize(
|
30
|
+
args: [],
|
31
|
+
dir:,
|
32
|
+
kitchen: DEFAULT_KITCHEN,
|
33
|
+
pan: DEFAULT_PAN,
|
34
|
+
timeout_in_seconds: nil
|
35
|
+
)
|
36
|
+
assert_required(:dir, dir)
|
37
|
+
assert_required(:kitchen, kitchen)
|
38
|
+
assert_required(:pan, pan)
|
39
|
+
|
40
|
+
@args = Array(args)
|
41
|
+
@dir = File.expand_path(dir.to_s)
|
42
|
+
@kitchen = kitchen.to_s
|
43
|
+
@pan = pan.to_s
|
44
|
+
@executor = Executor.new(timeout_in_seconds: timeout_in_seconds)
|
45
|
+
@parser = Parser.new
|
46
|
+
|
47
|
+
freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a Spoon::Result instance when PDI returns error code 0 or else raises
|
51
|
+
# a KitchenError since Kitchen was used to run the version command.
|
52
|
+
def version
|
53
|
+
final_args = [kitchen_path] + args + [Options::Arg.new(Options::Arg::Key::VERSION)]
|
54
|
+
|
55
|
+
result = executor.run(final_args)
|
56
|
+
version_line = parser.version(result.out)
|
57
|
+
|
58
|
+
raise(KitchenError, result) if result.code != 0
|
59
|
+
|
60
|
+
Result.new(result, version_line)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns an Executor::Result instance when PDI returns error code 0 or else raises
|
64
|
+
# a PanError (transformation) or KitchenError (job).
|
65
|
+
def run(options)
|
66
|
+
options = Options.make(options)
|
67
|
+
all_args = run_args(options)
|
68
|
+
|
69
|
+
executor.run(all_args).tap do |result|
|
70
|
+
raise(error_constant(options), result) if result.code != 0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
attr_reader :executor, :parser
|
77
|
+
|
78
|
+
def error_constant(options)
|
79
|
+
TYPES_TO_ERRORS.fetch(options.type)
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_args(options)
|
83
|
+
[script_path(options.type)] + args + options.to_args
|
84
|
+
end
|
85
|
+
|
86
|
+
def script_path(options_type)
|
87
|
+
if options_type == Options::Type::JOB
|
88
|
+
kitchen_path
|
89
|
+
elsif options_type == Options::Type::TRANSFORMATION
|
90
|
+
pan_path
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def kitchen_path
|
95
|
+
File.join(dir, kitchen)
|
96
|
+
end
|
97
|
+
|
98
|
+
def pan_path
|
99
|
+
File.join(dir, pan)
|
100
|
+
end
|
101
|
+
|
102
|
+
def assert_required(name, value)
|
103
|
+
raise ArgumentError, "#{name} is required" if value.to_s.empty?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Pdi
|
11
|
+
class Spoon
|
12
|
+
# This class subclasses Ruby's StandardError and provides a mapping to custom Kitchen
|
13
|
+
# specific error codes to messages.
|
14
|
+
# You can find the list of errors in Pentaho's documentation site, for example:
|
15
|
+
# https://help.pentaho.com/Documentation/8.0/Products/Data_Integration/Command_Line_Tools
|
16
|
+
class KitchenError < StandardError
|
17
|
+
MESSAGES = {
|
18
|
+
'1' => 'Errors occurred during processing',
|
19
|
+
'2' => 'An unexpected error occurred during loading or running of the job',
|
20
|
+
'7' => "The job couldn't be loaded from XML or the Repository",
|
21
|
+
'8' => 'Error loading steps or plugins (error in loading one of the plugins mostly)',
|
22
|
+
'9' => 'Command line usage printing'
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
attr_reader :execution
|
26
|
+
|
27
|
+
def initialize(execution)
|
28
|
+
@execution = execution
|
29
|
+
|
30
|
+
message = MESSAGES[execution.code.to_s] || 'Unknown'
|
31
|
+
|
32
|
+
super(message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'options/level'
|
11
|
+
require_relative 'options/param'
|
12
|
+
|
13
|
+
module Pdi
|
14
|
+
class Spoon
|
15
|
+
# This class serves as the input for executing a transformation or job through Pan or Kitchen.
|
16
|
+
class Options
|
17
|
+
acts_as_hashable
|
18
|
+
|
19
|
+
module Type
|
20
|
+
JOB = :job
|
21
|
+
TRANSFORMATION = :transformation
|
22
|
+
end
|
23
|
+
|
24
|
+
TYPES_TO_KEYS = {
|
25
|
+
Type::JOB => Arg::Key::JOB,
|
26
|
+
Type::TRANSFORMATION => Arg::Key::TRANS
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
attr_reader :level,
|
30
|
+
:name,
|
31
|
+
:params,
|
32
|
+
:repository,
|
33
|
+
:type
|
34
|
+
|
35
|
+
def initialize(
|
36
|
+
level: Level::BASIC,
|
37
|
+
name:,
|
38
|
+
params: {},
|
39
|
+
repository:,
|
40
|
+
type:
|
41
|
+
)
|
42
|
+
raise ArgumentError, 'name is required' if name.to_s.empty?
|
43
|
+
raise ArgumentError, 'repository is required' if repository.to_s.empty?
|
44
|
+
raise ArgumentError, 'type is required' if type.to_s.empty?
|
45
|
+
|
46
|
+
@level = constant(Level, level)
|
47
|
+
@name = name.to_s
|
48
|
+
@params = params || {}
|
49
|
+
@repository = repository.to_s
|
50
|
+
@type = constant(Type, type)
|
51
|
+
|
52
|
+
freeze
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_args
|
56
|
+
base_args + param_args
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def key
|
62
|
+
TYPES_TO_KEYS.fetch(type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def base_args
|
66
|
+
[
|
67
|
+
Arg.new(Arg::Key::REP, repository),
|
68
|
+
Arg.new(key, name),
|
69
|
+
Arg.new(Arg::Key::LEVEL, level)
|
70
|
+
]
|
71
|
+
end
|
72
|
+
|
73
|
+
def param_args
|
74
|
+
params.map { |key, value| Param.new(key, value) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def constant(constant, value)
|
78
|
+
constant.const_get(value.to_s.upcase.to_sym)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Pdi
|
11
|
+
class Spoon
|
12
|
+
class Options
|
13
|
+
# This class can form Pentaho-specific command-line arguments.
|
14
|
+
class Arg
|
15
|
+
COLON = ':'
|
16
|
+
DOUBLE_QUOTE = '"'
|
17
|
+
EMPTY = ''
|
18
|
+
HYPHEN = '-'
|
19
|
+
SPACE = ' '
|
20
|
+
|
21
|
+
module Key
|
22
|
+
JOB = :job
|
23
|
+
LEVEL = :level
|
24
|
+
PARAM = :param
|
25
|
+
REP = :rep
|
26
|
+
TRANS = :trans
|
27
|
+
VERSION = :version
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :key, :value
|
31
|
+
|
32
|
+
def initialize(key, value = '')
|
33
|
+
raise ArgumentError, 'key is required' if key.to_s.empty?
|
34
|
+
|
35
|
+
@key = Key.const_get(key.to_s.upcase.to_sym)
|
36
|
+
@value = value.to_s
|
37
|
+
|
38
|
+
freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
separator = value.to_s.empty? ? EMPTY : COLON
|
43
|
+
wrapper = wrap?(key, value) ? DOUBLE_QUOTE : EMPTY
|
44
|
+
prefix = HYPHEN
|
45
|
+
|
46
|
+
"#{wrapper}#{prefix}#{key}#{separator}#{value}#{wrapper}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def hash
|
50
|
+
[key, value].hash
|
51
|
+
end
|
52
|
+
|
53
|
+
def ==(other)
|
54
|
+
other.instance_of?(self.class) &&
|
55
|
+
key == other.key &&
|
56
|
+
value == other.value
|
57
|
+
end
|
58
|
+
alias eql? ==
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def wrap?(key, value)
|
63
|
+
key.to_s.include?(SPACE) || value.to_s.include?(SPACE)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Pdi
|
11
|
+
class Spoon
|
12
|
+
class Options
|
13
|
+
module Level
|
14
|
+
BASIC = 'Basic'
|
15
|
+
DETAILED = 'Detailed'
|
16
|
+
DEBUG = 'Debug'
|
17
|
+
ERROR = 'Error'
|
18
|
+
NOTHING = 'Nothing'
|
19
|
+
ROW_LEVEL = 'Rowlevel'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'arg'
|
11
|
+
|
12
|
+
module Pdi
|
13
|
+
class Spoon
|
14
|
+
class Options
|
15
|
+
# This class sub-classes Arg and knows how to form param key-value pair arguments.
|
16
|
+
class Param < Arg
|
17
|
+
EQUALS = '='
|
18
|
+
|
19
|
+
attr_reader :key, :value
|
20
|
+
|
21
|
+
def initialize(key, value = '')
|
22
|
+
super(Key::PARAM, "#{key}#{EQUALS}#{value}")
|
23
|
+
|
24
|
+
freeze
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Pdi
|
11
|
+
class Spoon
|
12
|
+
# This class subclasses Ruby's StandardError and provides a mapping to custom Pan
|
13
|
+
# specific error codes to messages.
|
14
|
+
# You can find the list of errors in Pentaho's documentation site, for example:
|
15
|
+
# https://help.pentaho.com/Documentation/8.0/Products/Data_Integration/Command_Line_Tools
|
16
|
+
class PanError < StandardError
|
17
|
+
MESSAGES = {
|
18
|
+
'1' => 'Errors occurred during processing',
|
19
|
+
'2' => 'An unexpected error occurred during loading / running of the transformation',
|
20
|
+
'3' => 'Unable to prepare and initialize this transformation',
|
21
|
+
'7' => "The transformation couldn't be loaded from XML or the Repository",
|
22
|
+
'8' => 'Error loading steps or plugins (error in loading one of the plugins mostly)',
|
23
|
+
'9' => 'Command line usage printing'
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
attr_reader :execution
|
27
|
+
|
28
|
+
def initialize(execution)
|
29
|
+
@execution = execution
|
30
|
+
|
31
|
+
message = MESSAGES[execution.code.to_s] || 'Unknown'
|
32
|
+
|
33
|
+
super(message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|