owasp-pipeline 0.8.3
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/CHANGES +23 -0
- data/FEATURES +19 -0
- data/README.md +101 -0
- data/bin/pipeline +67 -0
- data/lib/pipeline.rb +301 -0
- data/lib/pipeline/event.rb +14 -0
- data/lib/pipeline/filters.rb +41 -0
- data/lib/pipeline/filters/base_filter.rb +19 -0
- data/lib/pipeline/filters/jira_one_time_filter.rb +57 -0
- data/lib/pipeline/filters/remove_all_filter.rb +16 -0
- data/lib/pipeline/finding.rb +52 -0
- data/lib/pipeline/mounters.rb +55 -0
- data/lib/pipeline/mounters/base_mounter.rb +31 -0
- data/lib/pipeline/mounters/docker_mounter.rb +44 -0
- data/lib/pipeline/mounters/filesystem_mounter.rb +25 -0
- data/lib/pipeline/mounters/git_mounter.rb +52 -0
- data/lib/pipeline/mounters/iso_mounter.rb +42 -0
- data/lib/pipeline/mounters/url_mounter.rb +28 -0
- data/lib/pipeline/options.rb +240 -0
- data/lib/pipeline/reporters.rb +50 -0
- data/lib/pipeline/reporters/base_reporter.rb +21 -0
- data/lib/pipeline/reporters/csv_reporter.rb +19 -0
- data/lib/pipeline/reporters/jira_reporter.rb +61 -0
- data/lib/pipeline/reporters/json_reporter.rb +20 -0
- data/lib/pipeline/reporters/text_reporter.rb +19 -0
- data/lib/pipeline/scanner.rb +28 -0
- data/lib/pipeline/tasks.rb +124 -0
- data/lib/pipeline/tasks/av.rb +43 -0
- data/lib/pipeline/tasks/base_task.rb +64 -0
- data/lib/pipeline/tasks/brakeman.rb +60 -0
- data/lib/pipeline/tasks/bundle-audit.rb +93 -0
- data/lib/pipeline/tasks/checkmarx.rb +62 -0
- data/lib/pipeline/tasks/eslint.rb +71 -0
- data/lib/pipeline/tasks/fim.rb +61 -0
- data/lib/pipeline/tasks/nsp.rb +59 -0
- data/lib/pipeline/tasks/owasp-dep-check.rb +120 -0
- data/lib/pipeline/tasks/patterns.json +394 -0
- data/lib/pipeline/tasks/retirejs.rb +106 -0
- data/lib/pipeline/tasks/scanjs-eslintrc +106 -0
- data/lib/pipeline/tasks/scanjs.rb +32 -0
- data/lib/pipeline/tasks/sfl.rb +67 -0
- data/lib/pipeline/tasks/test.rb +47 -0
- data/lib/pipeline/tasks/zap.rb +84 -0
- data/lib/pipeline/tracker.rb +47 -0
- data/lib/pipeline/util.rb +39 -0
- data/lib/pipeline/version.rb +3 -0
- data/lib/zapjson.json +0 -0
- metadata +205 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c19bcf63e27cbc2579358e2f581943797d31b056
|
4
|
+
data.tar.gz: 4b1daacd45d7dcb1d5bad0643da0b9c9cf8e93ad
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cf4edf3934d7f2b596bc6baf3c1e85449100026b86aec1fcb690723a8f4cb2f48c2e3a974c7b79ec478384970501c1f8413ed7a4743f1e5087f922cb8571a06e
|
7
|
+
data.tar.gz: c7cdbd7eaf00185447d5fbb2ebf042f4c68cdfa15ef10547bd923b98d8373a736b69b3c48cc030728f05164502852e07a7eaad6a38f7660bd17e7db77c91b1f5
|
data/CHANGES
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
## 0.6.0
|
2
|
+
* Docker image.
|
3
|
+
* JavaScript tools (retire.js, nodesecurity, eslint)
|
4
|
+
* Java tools (SonarCube, Findbugs)
|
5
|
+
## 0.5.3
|
6
|
+
* Checkmarx
|
7
|
+
* JIRA Integration
|
8
|
+
* Search for secrets (a la gitrob)
|
9
|
+
* OWASP dependency-check
|
10
|
+
## 0.1.0
|
11
|
+
* Structure for pipeline and initial tool series.
|
12
|
+
* gem packaging
|
13
|
+
* Finding / reporting
|
14
|
+
* docker mounting
|
15
|
+
* Support for task labels. Run with 'pipeline -l label' and only tasks that identify with that label will run.
|
16
|
+
* Move to only keep options in tracker.
|
17
|
+
|
18
|
+
## FUTURE
|
19
|
+
* TODO: add scap
|
20
|
+
* TODO: Devtools: pmd, brakeman, dependency checker, codescan, scanjs, bandit, etc.
|
21
|
+
* TODO: Misc OS checks
|
22
|
+
* TODO: Active: ZAP, gauntlt
|
23
|
+
* TODO: .NET
|
data/FEATURES
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Capabilities
|
2
|
+
* AV
|
3
|
+
* FIM
|
4
|
+
|
5
|
+
FUTURE:
|
6
|
+
* oscap - https://www.redhat.com/archives/open-scap-list/2013-May/msg00007.html
|
7
|
+
* pmd
|
8
|
+
* brakeman
|
9
|
+
* dependency checker
|
10
|
+
* codescan
|
11
|
+
|
12
|
+
# Images
|
13
|
+
* Raw directory on file system.
|
14
|
+
* git
|
15
|
+
* docker
|
16
|
+
|
17
|
+
FUTURE:
|
18
|
+
* iso
|
19
|
+
* vmdk, vdi, ami
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
# Pipeline
|
4
|
+
|
5
|
+
Pipeline is a framework for running a series of tools. Generally, it is intended as a backbone
|
6
|
+
for automating a security analysis pipeline of tools.
|
7
|
+
|
8
|
+
# Recommended Usage
|
9
|
+
|
10
|
+
For those wishing to run pipeline, we recommend using the docker image.
|
11
|
+
See the documentation for more info. [Pipeline Docker Documentation](./DOCKER.md)
|
12
|
+
|
13
|
+
For those interested in how to use Pipeline in a DevOps context, see
|
14
|
+
[Pipeline DevOps Integration Options](./DEVOPS.md)
|
15
|
+
|
16
|
+
# Installation
|
17
|
+
|
18
|
+
gem install pipeline
|
19
|
+
|
20
|
+
# Extending Pipeline
|
21
|
+
|
22
|
+
Pipeline is intended to be extended through added "tasks". To add a new tool,
|
23
|
+
copy an existing task and tweak to make it work for the tool in question.
|
24
|
+
|
25
|
+
# Usage
|
26
|
+
|
27
|
+
pipeline <options> <target>
|
28
|
+
|
29
|
+
## Options
|
30
|
+
|
31
|
+
Common options include:
|
32
|
+
-d for debug
|
33
|
+
-f for format (takes "json", "csv", "jira")
|
34
|
+
|
35
|
+
For a full list of options, use `pipeline --help` or see the [OPTIONS.md](./OPTIONS.md) file.
|
36
|
+
|
37
|
+
## Target
|
38
|
+
|
39
|
+
The target can be:
|
40
|
+
* Filesystem (which is analyzed in place)
|
41
|
+
* Git repo (which is cloned for analysis)
|
42
|
+
* Other types of images (.iso, docker, etc. are experimental)
|
43
|
+
|
44
|
+
|
45
|
+
# Dependencies
|
46
|
+
|
47
|
+
* clamav
|
48
|
+
* hashdeep
|
49
|
+
* rm (*nix)
|
50
|
+
* git
|
51
|
+
* mount (*nix)
|
52
|
+
* docker
|
53
|
+
|
54
|
+
# Development
|
55
|
+
|
56
|
+
To run the code, run the following from the root directory:
|
57
|
+
>ruby bin/pipeline <options> target
|
58
|
+
|
59
|
+
To build a gem, just run:
|
60
|
+
gem build pipeline.gemspec
|
61
|
+
|
62
|
+
|
63
|
+
# Integration
|
64
|
+
|
65
|
+
## Git Hooks
|
66
|
+
|
67
|
+
First, grab the hook from the code.
|
68
|
+
```
|
69
|
+
meditation:hooks mk$ cp /area53/owasp/pipeline/hooks/pre-commit .
|
70
|
+
```
|
71
|
+
|
72
|
+
Then make it executable.
|
73
|
+
```
|
74
|
+
meditation:hooks mk$ chmod +x pre-commit
|
75
|
+
```
|
76
|
+
|
77
|
+
Make sure the shell you are committing in can see docker.
|
78
|
+
```
|
79
|
+
meditation:hooks mk$ eval "$(docker-machine env default)"
|
80
|
+
```
|
81
|
+
|
82
|
+
Now go test and make a change and commit a file.
|
83
|
+
The result should be that pipeline runs against your
|
84
|
+
code and will not allow commits unless the results
|
85
|
+
are clean. (Which is not necessarily a reasonable
|
86
|
+
expectation)
|
87
|
+
|
88
|
+
|
89
|
+
# Configuration files
|
90
|
+
|
91
|
+
For advanced usage scenarios, you can save your configuration and use it at runtime.
|
92
|
+
|
93
|
+
# Authors
|
94
|
+
|
95
|
+
Matt Konda
|
96
|
+
Alex Lock
|
97
|
+
Rafa Perez
|
98
|
+
|
99
|
+
# License
|
100
|
+
|
101
|
+
Apache 2: http://www.apache.org/licenses/LICENSE-2.0
|
data/bin/pipeline
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#Adjust path in case called directly and not through gem
|
3
|
+
$:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
|
4
|
+
|
5
|
+
require 'pipeline'
|
6
|
+
require 'pipeline/options'
|
7
|
+
require 'pipeline/version'
|
8
|
+
|
9
|
+
#Parse options
|
10
|
+
begin
|
11
|
+
options, parser = Pipeline::Options.parse! ARGV
|
12
|
+
rescue OptionParser::ParseError => e
|
13
|
+
$stderr.puts e.message.capitalize
|
14
|
+
$stderr.puts "Please see `pipeline --help` for valid options"
|
15
|
+
exit -1
|
16
|
+
end
|
17
|
+
|
18
|
+
#Exit early for these options
|
19
|
+
if options[:list_checks] or options[:list_optional_checks]
|
20
|
+
Pipeline.list_checks options
|
21
|
+
exit
|
22
|
+
elsif options[:create_config]
|
23
|
+
Pipeline.dump_config options
|
24
|
+
exit
|
25
|
+
elsif options[:show_help]
|
26
|
+
puts parser
|
27
|
+
exit
|
28
|
+
elsif options[:show_version]
|
29
|
+
puts "Pipeline #{Pipeline::Version}"
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
#Set application path according to the commandline arguments
|
34
|
+
unless options[:target]
|
35
|
+
if ARGV[-1].nil?
|
36
|
+
options[:target] = "."
|
37
|
+
else
|
38
|
+
options[:target] = ARGV[-1]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
trap("INT") do
|
43
|
+
$stderr.puts "\nInterrupted - exiting."
|
44
|
+
|
45
|
+
if options[:debug]
|
46
|
+
$stderr.puts caller
|
47
|
+
end
|
48
|
+
|
49
|
+
exit!
|
50
|
+
end
|
51
|
+
|
52
|
+
if options[:quiet].nil?
|
53
|
+
options[:quiet] = :command_line
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
#Run scan and output a report
|
58
|
+
tracker = Pipeline.run options.merge(:print_report => true, :quiet => options[:quiet])
|
59
|
+
|
60
|
+
#Return error code if --exit-on-warn is used and warnings were found
|
61
|
+
if options[:exit_on_warn] and not tracker.findings.empty?
|
62
|
+
exit Pipeline::Warnings_Found_Exit_Code
|
63
|
+
end
|
64
|
+
rescue Pipeline::NoTargetError => e
|
65
|
+
$stderr.puts e.message
|
66
|
+
exit 1
|
67
|
+
end
|
data/lib/pipeline.rb
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
3
|
+
require 'set'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module Pipeline
|
7
|
+
|
8
|
+
#This exit code is used when warnings are found and the --exit-on-warn
|
9
|
+
#option is set
|
10
|
+
Warnings_Found_Exit_Code = 3
|
11
|
+
|
12
|
+
@debug = false
|
13
|
+
@quiet = false
|
14
|
+
@loaded_dependencies = []
|
15
|
+
|
16
|
+
#Run Pipeline.
|
17
|
+
#
|
18
|
+
#Options:
|
19
|
+
#
|
20
|
+
# * :config_file - configuration file
|
21
|
+
# * :exit_on_warn - return false if warnings found, true otherwise. Not recommended for library use (default: false)
|
22
|
+
# * :output_files - files for output
|
23
|
+
# * :output_formats - formats for output (:to_s, :to_tabs, :to_csv, :to_html)
|
24
|
+
# * :parallel_checks - run checks in parallel (default: true)
|
25
|
+
# * :print_report - if no output file specified, print to stdout (default: false)
|
26
|
+
# * :quiet - suppress most messages (default: true)
|
27
|
+
def self.run options
|
28
|
+
options = set_options options
|
29
|
+
|
30
|
+
@quiet = !!options[:quiet]
|
31
|
+
@debug = !!options[:debug]
|
32
|
+
|
33
|
+
if @quiet
|
34
|
+
options[:report_progress] = false
|
35
|
+
end
|
36
|
+
|
37
|
+
scan options
|
38
|
+
end
|
39
|
+
|
40
|
+
#Sets up options for run, checks given application path
|
41
|
+
def self.set_options options
|
42
|
+
if options.is_a? String
|
43
|
+
options = { :target => options }
|
44
|
+
end
|
45
|
+
|
46
|
+
if options[:quiet] == :command_line
|
47
|
+
command_line = true
|
48
|
+
options.delete :quiet
|
49
|
+
end
|
50
|
+
|
51
|
+
options = default_options.merge(load_options(options[:config_file], options[:quiet])).merge(options)
|
52
|
+
|
53
|
+
if options[:quiet].nil? and not command_line
|
54
|
+
options[:quiet] = true
|
55
|
+
end
|
56
|
+
|
57
|
+
options[:output_format] = get_output_format options
|
58
|
+
|
59
|
+
if options[:appname].nil?
|
60
|
+
path = options[:target]
|
61
|
+
options[:appname] = File.split(path).last
|
62
|
+
end
|
63
|
+
options
|
64
|
+
end
|
65
|
+
|
66
|
+
CONFIG_FILES = [
|
67
|
+
File.expand_path("./config/pipeline.yml"),
|
68
|
+
File.expand_path("~/.pipeline/config.yml"),
|
69
|
+
File.expand_path("/etc/pipeline/config.yml")
|
70
|
+
]
|
71
|
+
|
72
|
+
#Load options from YAML file
|
73
|
+
def self.load_options custom_location, quiet
|
74
|
+
#Load configuration file
|
75
|
+
if config = config_file(custom_location)
|
76
|
+
options = YAML.load_file config
|
77
|
+
|
78
|
+
if options
|
79
|
+
options.each { |k, v| options[k] = Set.new v if v.is_a? Array }
|
80
|
+
|
81
|
+
# notify if options[:quiet] and quiet is nil||false
|
82
|
+
notify "[Notice] Using configuration in #{config}" unless (options[:quiet] || quiet)
|
83
|
+
options
|
84
|
+
else
|
85
|
+
notify "[Notice] Empty configuration file: #{config}" unless quiet
|
86
|
+
{}
|
87
|
+
end
|
88
|
+
else
|
89
|
+
{}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.config_file custom_location = nil
|
94
|
+
supported_locations = [File.expand_path(custom_location || "")] + CONFIG_FILES
|
95
|
+
supported_locations.detect {|f| File.file?(f) }
|
96
|
+
end
|
97
|
+
|
98
|
+
#Default set of options
|
99
|
+
def self.default_options
|
100
|
+
{
|
101
|
+
:parallel_tasks => true,
|
102
|
+
:skip_tasks => Set.new(),
|
103
|
+
:exit_on_warn => true,
|
104
|
+
:output_format => :text,
|
105
|
+
:working_dir => "~/line/tmp/",
|
106
|
+
:zap_host => "http://localhost",
|
107
|
+
:zap_port => "9999",
|
108
|
+
:labels => Set.new() << "filesystem" << "code" # Defaults to run.
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
#Determine output formats based on options[:output_formats]
|
113
|
+
#or options[:output_files]
|
114
|
+
def self.get_output_format options
|
115
|
+
if options[:output_file]
|
116
|
+
get_format_from_output_file options[:output_file]
|
117
|
+
elsif options[:output_format]
|
118
|
+
get_format_from_output_format options[:output_format]
|
119
|
+
else
|
120
|
+
begin
|
121
|
+
require 'terminal-table'
|
122
|
+
return [:to_s]
|
123
|
+
rescue LoadError
|
124
|
+
return [:to_json]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.get_format_from_output_format output_format
|
130
|
+
case output_format
|
131
|
+
when :html, :to_html
|
132
|
+
[:to_html]
|
133
|
+
when :csv, :to_csv
|
134
|
+
[:to_csv]
|
135
|
+
when :pdf, :to_pdf
|
136
|
+
[:to_pdf]
|
137
|
+
when :tabs, :to_tabs
|
138
|
+
[:to_tabs]
|
139
|
+
when :json, :to_json
|
140
|
+
[:to_json]
|
141
|
+
when :jira, :to_jira
|
142
|
+
[:to_jira]
|
143
|
+
when :markdown, :to_markdown
|
144
|
+
[:to_markdown]
|
145
|
+
else
|
146
|
+
[:to_s]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
private_class_method :get_format_from_output_format
|
150
|
+
|
151
|
+
def self.get_format_from_output_file output_file
|
152
|
+
case output_file
|
153
|
+
when /\.html$/i
|
154
|
+
:to_html
|
155
|
+
when /\.csv$/i
|
156
|
+
:to_csv
|
157
|
+
when /\.pdf$/i
|
158
|
+
:to_pdf
|
159
|
+
when /\.tabs$/i
|
160
|
+
:to_tabs
|
161
|
+
when /\.json$/i
|
162
|
+
:to_json
|
163
|
+
when /\.md$/i
|
164
|
+
:to_markdown
|
165
|
+
else
|
166
|
+
:to_s
|
167
|
+
end
|
168
|
+
end
|
169
|
+
private_class_method :get_format_from_output_file
|
170
|
+
|
171
|
+
#Output list of tasks (for `-k` option)
|
172
|
+
def self.list_checks options
|
173
|
+
require 'pipeline/scanner'
|
174
|
+
|
175
|
+
add_external_tasks options
|
176
|
+
|
177
|
+
if options[:list_optional_tasks]
|
178
|
+
$stderr.puts "Optional Tasks:"
|
179
|
+
tasks = Tasks.optional_tasks
|
180
|
+
else
|
181
|
+
$stderr.puts "Available tasks:"
|
182
|
+
tasks = Tasks.tasks
|
183
|
+
end
|
184
|
+
|
185
|
+
format_length = 30
|
186
|
+
|
187
|
+
$stderr.puts "-" * format_length
|
188
|
+
tasks.each do |task|
|
189
|
+
$stderr.printf("%-#{format_length}s\n", task.name)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
#Output configuration to YAML
|
194
|
+
def self.dump_config options
|
195
|
+
if options[:create_config].is_a? String
|
196
|
+
file = options[:create_config]
|
197
|
+
else
|
198
|
+
file = nil
|
199
|
+
end
|
200
|
+
|
201
|
+
options.delete :create_config
|
202
|
+
|
203
|
+
options.each do |k,v|
|
204
|
+
if v.is_a? Set
|
205
|
+
options[k] = v.to_a
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
if file
|
210
|
+
File.open file, "w" do |f|
|
211
|
+
YAML.dump options, f
|
212
|
+
end
|
213
|
+
puts "Output configuration to #{file}"
|
214
|
+
else
|
215
|
+
puts YAML.dump(options)
|
216
|
+
end
|
217
|
+
exit
|
218
|
+
end
|
219
|
+
|
220
|
+
#Run a scan. Generally called from Pipeline.run instead of directly.
|
221
|
+
def self.scan options
|
222
|
+
#Load scanner
|
223
|
+
notify "Loading scanner..."
|
224
|
+
|
225
|
+
begin
|
226
|
+
require 'pipeline/scanner'
|
227
|
+
require 'pipeline/tracker'
|
228
|
+
require 'pipeline/mounters'
|
229
|
+
require 'pipeline/filters'
|
230
|
+
require 'pipeline/reporters'
|
231
|
+
|
232
|
+
rescue LoadError => e
|
233
|
+
$stderr.puts e.message
|
234
|
+
raise NoPipelineError, "Cannot find lib/ directory or load the key pipeline."
|
235
|
+
end
|
236
|
+
|
237
|
+
# debug "API: #{options[:jira_api_url.to_s]}"
|
238
|
+
# debug "Project: #{options[:jira_project.to_s]}"
|
239
|
+
# debug "Cookie: #{options[:jira_cookie.to_s]}"
|
240
|
+
|
241
|
+
add_external_tasks options
|
242
|
+
|
243
|
+
tracker = Tracker.new options
|
244
|
+
debug "Mounting ... #{options[:target]}"
|
245
|
+
# Make the target accessible.
|
246
|
+
target = Pipeline::Mounters.mount tracker
|
247
|
+
|
248
|
+
#Start scanning
|
249
|
+
scanner = Scanner.new
|
250
|
+
notify "Processing target...#{options[:target]}"
|
251
|
+
scanner.process target, tracker
|
252
|
+
|
253
|
+
# Filter the results (Don't report anything that has been reported before)
|
254
|
+
Pipeline::Filters.filter tracker
|
255
|
+
|
256
|
+
# Generate Report
|
257
|
+
notify "Generating report...#{options[:output_format]}"
|
258
|
+
Pipeline::Reporters.run_report tracker
|
259
|
+
|
260
|
+
tracker
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.error message
|
264
|
+
$stderr.puts message
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.warn message
|
268
|
+
$stderr.puts message unless @quiet
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.notify message
|
272
|
+
$stderr.puts message #unless @debug
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.debug message
|
276
|
+
$stderr.puts message if @debug
|
277
|
+
end
|
278
|
+
|
279
|
+
def self.load_pipeline_dependency name
|
280
|
+
return if @loaded_dependencies.include? name
|
281
|
+
|
282
|
+
begin
|
283
|
+
require name
|
284
|
+
rescue LoadError => e
|
285
|
+
$stderr.puts e.message
|
286
|
+
$stderr.puts "Please install the appropriate dependency."
|
287
|
+
exit! -1
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def self.add_external_tasks options
|
292
|
+
options[:additional_tasks_path].each do |path|
|
293
|
+
Pipeline::Tasks.initialize_tasks path
|
294
|
+
end if options[:additional_tasks_path]
|
295
|
+
end
|
296
|
+
|
297
|
+
class DependencyError < RuntimeError; end
|
298
|
+
class NoPipelineError < RuntimeError; end
|
299
|
+
class NoTargetError < RuntimeError; end
|
300
|
+
class JiraConfigError < RuntimeError; end
|
301
|
+
end
|