owasp-pipeline 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Pipeline Logo](https://upload.wikimedia.org/wikipedia/commons/3/37/The_Great_Wave_of_Kanagava.jpg)
|
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
|