jenkins_pipeline_builder 0.4.2 → 0.5.0
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 +4 -4
- data/.rubocop.yml +65 -0
- data/LICENSE +1 -1
- data/README.md +64 -2
- data/Rakefile +13 -11
- data/bin/generate +2 -4
- data/jenkins_pipeline_builder.gemspec +14 -11
- data/lib/jenkins_pipeline_builder.rb +2 -1
- data/lib/jenkins_pipeline_builder/builders.rb +89 -33
- data/lib/jenkins_pipeline_builder/cli/base.rb +21 -29
- data/lib/jenkins_pipeline_builder/cli/helper.rb +12 -15
- data/lib/jenkins_pipeline_builder/cli/pipeline.rb +6 -1
- data/lib/jenkins_pipeline_builder/cli/view.rb +2 -2
- data/lib/jenkins_pipeline_builder/compiler.rb +58 -56
- data/lib/jenkins_pipeline_builder/generator.rb +362 -172
- data/lib/jenkins_pipeline_builder/job_builder.rb +48 -45
- data/lib/jenkins_pipeline_builder/module_registry.rb +4 -6
- data/lib/jenkins_pipeline_builder/publishers.rb +53 -38
- data/lib/jenkins_pipeline_builder/pull_request.rb +156 -0
- data/lib/jenkins_pipeline_builder/triggers.rb +24 -25
- data/lib/jenkins_pipeline_builder/utils.rb +13 -7
- data/lib/jenkins_pipeline_builder/version.rb +2 -2
- data/lib/jenkins_pipeline_builder/view.rb +120 -98
- data/lib/jenkins_pipeline_builder/wrappers.rb +44 -44
- data/lib/jenkins_pipeline_builder/xml_helper.rb +4 -4
- data/spec/func_tests/spec_helper.rb +2 -2
- data/spec/func_tests/view_spec.rb +6 -6
- data/spec/unit_tests/compiler_spec.rb +7 -7
- data/spec/unit_tests/fixtures/files/Job-Gem-Build.xml +2 -2
- data/spec/unit_tests/fixtures/files/Job-Gem-Build.yaml +1 -0
- data/spec/unit_tests/fixtures/files/concurrent_build.xml +17 -0
- data/spec/unit_tests/fixtures/files/concurrent_build.yaml +4 -0
- data/spec/unit_tests/fixtures/files/downstream_blocking.xml +19 -0
- data/spec/unit_tests/fixtures/files/downstream_blocking.yaml +15 -0
- data/spec/unit_tests/fixtures/files/groovy_postbuild.xml +29 -0
- data/spec/unit_tests/fixtures/files/groovy_postbuild.yaml +9 -0
- data/spec/unit_tests/generator_spec.rb +30 -25
- data/spec/unit_tests/module_registry_spec.rb +9 -9
- data/spec/unit_tests/resolve_dependencies_spec.rb +108 -89
- data/spec/unit_tests/spec_helper.rb +1 -1
- metadata +62 -4
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2014
|
2
|
+
# Copyright (c) 2014 Constant Contact
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -21,49 +21,41 @@
|
|
21
21
|
#
|
22
22
|
|
23
23
|
require 'thor'
|
24
|
-
#require "#{File.dirname(__FILE__)}/pipeline.rb"
|
25
24
|
|
26
25
|
module JenkinsPipelineBuilder
|
27
26
|
module CLI
|
28
27
|
class Base < Thor
|
28
|
+
class_option :username, aliases: '-u', desc: 'Name of Jenkins user'
|
29
|
+
class_option :password, aliases: '-p', desc: 'Password of Jenkins user'
|
30
|
+
class_option :password_base64, aliases: '-b', desc: 'Base 64 encoded password of Jenkins user'
|
31
|
+
class_option :server_ip, aliases: '-s', desc: 'Jenkins server IP address'
|
32
|
+
class_option :server_port, aliases: '-o', desc: 'Jenkins port'
|
33
|
+
class_option :creds_file, aliases: '-c', desc: 'Credentials file for communicating with Jenkins server'
|
34
|
+
class_option :debug, type: :boolean, aliases: '-d', desc: 'Run in debug mode (no Jenkins changes)', default: false
|
29
35
|
|
30
|
-
|
31
|
-
class_option :password, :aliases => "-p",
|
32
|
-
:desc => "Password of Jenkins user"
|
33
|
-
class_option :password_base64, :aliases => "-b",
|
34
|
-
:desc => "Base 64 encoded password of Jenkins user"
|
35
|
-
class_option :server_ip, :aliases => "-s",
|
36
|
-
:desc => "Jenkins server IP address"
|
37
|
-
class_option :server_port, :aliases => "-o", :desc => "Jenkins port"
|
38
|
-
class_option :creds_file, :aliases => "-c",
|
39
|
-
:desc => "Credentials file for communicating with Jenkins server"
|
40
|
-
class_option :debug, :type => :boolean, :aliases => "-d", :desc => "Run in debug mode (no Jenkins changes)",
|
41
|
-
:default => false
|
36
|
+
map '-v' => :version
|
42
37
|
|
43
|
-
|
44
|
-
map "-v" => :version
|
45
|
-
|
46
|
-
desc "version", "Shows current version"
|
38
|
+
desc 'version', 'Shows current version'
|
47
39
|
# CLI command that returns the version of Jenkins API Client
|
48
40
|
def version
|
49
41
|
puts JenkinsPipelineBuilder::VERSION
|
50
42
|
end
|
51
43
|
|
52
|
-
# Register the CLI::Pipeline class as
|
44
|
+
# Register the CLI::Pipeline class as 'pipeline' subcommand to CLI
|
53
45
|
register(
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
46
|
+
CLI::Pipeline,
|
47
|
+
'pipeline',
|
48
|
+
'pipeline [subcommand]',
|
49
|
+
'Provides functions to access pipeline functions of the Jenkins CI server'
|
58
50
|
)
|
59
51
|
|
60
|
-
# Register the CLI::Job class as
|
52
|
+
# Register the CLI::Job class as 'view' subcommand to CLI
|
61
53
|
register(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
54
|
+
CLI::View,
|
55
|
+
'view',
|
56
|
+
'view [subcommand]',
|
57
|
+
'Provides functions to access view interface of Jenkins CI server'
|
66
58
|
)
|
67
59
|
end
|
68
60
|
end
|
69
|
-
end
|
61
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2014
|
2
|
+
# Copyright (c) 2014 Constant Contact
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -22,7 +22,11 @@
|
|
22
22
|
|
23
23
|
require 'fileutils'
|
24
24
|
require 'yaml'
|
25
|
+
require 'json'
|
25
26
|
require 'jenkins_api_client'
|
27
|
+
require 'open-uri'
|
28
|
+
require 'zlib'
|
29
|
+
require 'archive/tar/minitar'
|
26
30
|
|
27
31
|
module JenkinsPipelineBuilder
|
28
32
|
module CLI
|
@@ -40,29 +44,22 @@ module JenkinsPipelineBuilder
|
|
40
44
|
(options[:password] || options[:password_base64])
|
41
45
|
creds = options
|
42
46
|
elsif options[:creds_file]
|
43
|
-
creds = YAML.load_file(
|
44
|
-
File.expand_path(options[:creds_file])
|
45
|
-
#File.expand_path(options[:creds_file], __FILE__)
|
46
|
-
#options[:creds_file]
|
47
|
-
)
|
47
|
+
creds = YAML.load_file(File.expand_path(options[:creds_file]))
|
48
48
|
elsif File.exist?("#{ENV['HOME']}/.jenkins_api_client/login.yml")
|
49
49
|
creds = YAML.load_file(
|
50
|
-
|
51
|
-
"#{ENV['HOME']}/.jenkins_api_client/login.yml", __FILE__
|
52
|
-
)
|
50
|
+
File.expand_path("#{ENV['HOME']}/.jenkins_api_client/login.yml", __FILE__)
|
53
51
|
)
|
54
52
|
else
|
55
|
-
msg =
|
56
|
-
msg <<
|
53
|
+
msg = 'Credentials are not set. Please pass them as parameters or'
|
54
|
+
msg << ' set them in the default credentials file'
|
57
55
|
puts msg
|
58
56
|
exit 1
|
59
57
|
end
|
60
58
|
|
61
|
-
#creds[:log_level] = Logger::DEBUG
|
62
59
|
client = JenkinsApi::Client.new(creds)
|
63
|
-
generator = JenkinsPipelineBuilder::Generator.new(
|
64
|
-
generator.debug = options[:debug]
|
65
|
-
|
60
|
+
generator = JenkinsPipelineBuilder::Generator.new(client)
|
61
|
+
generator.debug = options[:debug]
|
62
|
+
generator
|
66
63
|
end
|
67
64
|
end
|
68
65
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2014
|
2
|
+
# Copyright (c) 2014 Constant Contact
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -35,6 +35,11 @@ module JenkinsPipelineBuilder
|
|
35
35
|
def bootstrap(path, project_name = nil)
|
36
36
|
Helper.setup(parent_options).bootstrap(path, project_name)
|
37
37
|
end
|
38
|
+
|
39
|
+
desc 'pull_request Path', 'Generates jenkins jobs based on a git pull request.'
|
40
|
+
def pull_request(path, project_name = nil)
|
41
|
+
Helper.setup(parent_options).pull_request(path, project_name)
|
42
|
+
end
|
38
43
|
end
|
39
44
|
end
|
40
45
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2014
|
2
|
+
# Copyright (c) 2014 Constant Contact
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -37,4 +37,4 @@ module JenkinsPipelineBuilder
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
40
|
-
end
|
40
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2014
|
2
|
+
# Copyright (c) 2014 Constant Contact
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -22,17 +22,27 @@
|
|
22
22
|
|
23
23
|
module JenkinsPipelineBuilder
|
24
24
|
class Compiler
|
25
|
-
def self.resolve_value(value, settings)
|
25
|
+
def self.resolve_value(value, settings, job_collection)
|
26
26
|
settings = settings.with_indifferent_access
|
27
27
|
value_s = value.to_s.clone
|
28
|
-
|
28
|
+
# First we try to do job name correction
|
29
|
+
vars = value_s.scan(/{{job@(.*)}}/).flatten
|
30
|
+
if vars.count > 0
|
31
|
+
vars.select! do |var|
|
32
|
+
var_val = job_collection[var.to_s]
|
33
|
+
value_s.gsub!("{{job@#{var}}}", var_val[:value][:name]) unless var_val.nil?
|
34
|
+
var_val.nil?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
# Then we look for normal values to replace
|
38
|
+
vars = value_s.scan(/{{([^{}@]+)}}/).flatten
|
29
39
|
vars.select! do |var|
|
30
40
|
var_val = settings[var]
|
31
|
-
value_s.gsub!("{{#{var
|
41
|
+
value_s.gsub!("{{#{var}}}", var_val) unless var_val.nil?
|
32
42
|
var_val.nil?
|
33
43
|
end
|
34
44
|
return nil if vars.count != 0
|
35
|
-
|
45
|
+
value_s
|
36
46
|
end
|
37
47
|
|
38
48
|
def self.get_settings_bag(item_bag, settings_bag = {})
|
@@ -42,73 +52,65 @@ module JenkinsPipelineBuilder
|
|
42
52
|
item.keys.each do |k|
|
43
53
|
val = item[k]
|
44
54
|
if val.kind_of? String
|
45
|
-
new_value = resolve_value(val, settings_bag)
|
55
|
+
new_value = resolve_value(val, settings_bag, {})
|
46
56
|
return nil if new_value.nil?
|
47
57
|
bag[k] = new_value
|
48
58
|
end
|
49
59
|
end
|
50
60
|
my_settings_bag = settings_bag.clone
|
51
|
-
|
61
|
+
my_settings_bag.merge(bag)
|
52
62
|
end
|
53
63
|
|
54
|
-
def self.compile(item, settings = {})
|
64
|
+
def self.compile(item, settings = {}, job_collection = {})
|
55
65
|
errors = {}
|
56
66
|
case item
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
67
|
+
when String
|
68
|
+
new_value = resolve_value(item, settings, job_collection)
|
69
|
+
errors[item] = "Failed to resolve #{item}" if new_value.nil?
|
70
|
+
return false, errors unless errors.empty?
|
71
|
+
return true, new_value
|
72
|
+
when Hash
|
73
|
+
result = {}
|
74
|
+
item.each do |key, value|
|
75
|
+
if value.nil?
|
76
|
+
errors[key] = "key: #{key} has a nil value, this is likely a yaml syntax error. Skipping children and siblings"
|
77
|
+
break
|
61
78
|
end
|
62
|
-
|
63
|
-
|
79
|
+
success, payload = compile(value, settings, job_collection)
|
80
|
+
unless success
|
81
|
+
errors.merge!(payload)
|
82
|
+
next
|
64
83
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
item.each do |key, value|
|
69
|
-
if value.nil?
|
70
|
-
errors[key] = "key: #{key} has a nil value, this is likely a yaml syntax error. Skipping children and siblings"
|
71
|
-
break
|
72
|
-
end
|
73
|
-
success, payload = compile(value, settings)
|
74
|
-
unless success
|
75
|
-
errors.merge!(payload)
|
76
|
-
next
|
77
|
-
end
|
78
|
-
if payload.nil?
|
79
|
-
errors[key] = "Failed to resolve:\n===>key: #{key}\n\n===>value: #{value}\n\n===>of: #{item}"
|
80
|
-
next
|
81
|
-
end
|
82
|
-
result[key] = payload
|
84
|
+
if payload.nil?
|
85
|
+
errors[key] = "Failed to resolve:\n===>key: #{key}\n\n===>value: #{value}\n\n===>of: #{item}"
|
86
|
+
next
|
83
87
|
end
|
84
|
-
|
85
|
-
|
88
|
+
result[key] = payload
|
89
|
+
end
|
90
|
+
return false, errors unless errors.empty?
|
91
|
+
return true, result
|
92
|
+
when Array
|
93
|
+
result = []
|
94
|
+
item.each do |value|
|
95
|
+
if value.nil?
|
96
|
+
errors[item] = "found a nil value when processing following array:\n #{item.inspect}"
|
97
|
+
break
|
86
98
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
if value.nil?
|
92
|
-
errors[item] = "found a nil value when processing following array:\n #{item.inspect}"
|
93
|
-
break
|
94
|
-
end
|
95
|
-
success, payload = compile(value, settings)
|
96
|
-
unless success
|
97
|
-
errors.merge!(payload)
|
98
|
-
next
|
99
|
-
end
|
100
|
-
if payload.nil?
|
101
|
-
errors[value] = "Failed to resolve:\n===>item #{value}\n\n===>of list: #{item}"
|
102
|
-
next
|
103
|
-
end
|
104
|
-
result << payload
|
99
|
+
success, payload = compile(value, settings, job_collection)
|
100
|
+
unless success
|
101
|
+
errors.merge!(payload)
|
102
|
+
next
|
105
103
|
end
|
106
|
-
|
107
|
-
|
104
|
+
if payload.nil?
|
105
|
+
errors[value] = "Failed to resolve:\n===>item #{value}\n\n===>of list: #{item}"
|
106
|
+
next
|
108
107
|
end
|
109
|
-
|
108
|
+
result << payload
|
109
|
+
end
|
110
|
+
return false, errors unless errors.empty?
|
111
|
+
return true, result
|
110
112
|
end
|
111
|
-
|
113
|
+
[true, item]
|
112
114
|
end
|
113
115
|
end
|
114
116
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2014
|
2
|
+
# Copyright (c) 2014 Constant Contact
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -35,84 +35,119 @@ module JenkinsPipelineBuilder
|
|
35
35
|
#
|
36
36
|
# @raise [ArgumentError] when required options are not provided.
|
37
37
|
#
|
38
|
-
def initialize(
|
38
|
+
def initialize(client)
|
39
39
|
@client = client
|
40
40
|
@logger = @client.logger
|
41
|
-
|
41
|
+
# @logger.level = (@debug) ? Logger::DEBUG : Logger::INFO;
|
42
42
|
@job_templates = {}
|
43
43
|
@job_collection = {}
|
44
|
-
|
45
|
-
@
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
44
|
+
@extensions = {}
|
45
|
+
@remote_depends = {}
|
46
|
+
|
47
|
+
@module_registry = ModuleRegistry.new(
|
48
|
+
job: {
|
49
|
+
description: JobBuilder.method(:change_description),
|
50
|
+
scm_params: JobBuilder.method(:apply_scm_params),
|
51
|
+
hipchat: JobBuilder.method(:hipchat_notifier),
|
52
|
+
parameters: JobBuilder.method(:build_parameters),
|
53
|
+
priority: JobBuilder.method(:use_specific_priority),
|
54
|
+
discard_old: JobBuilder.method(:discard_old_param),
|
55
|
+
throttle: JobBuilder.method(:throttle_job),
|
56
|
+
prepare_environment: JobBuilder.method(:prepare_environment),
|
57
|
+
concurrent_build: JobBuilder.method(:concurrent_build),
|
58
|
+
builders: {
|
59
|
+
registry: {
|
60
|
+
multi_job: Builders.method(:build_multijob),
|
61
|
+
inject_vars_file: Builders.method(:build_environment_vars_injector),
|
62
|
+
shell_command: Builders.method(:build_shell_command),
|
63
|
+
maven3: Builders.method(:build_maven3),
|
64
|
+
blocking_downstream: Builders.method(:blocking_downstream),
|
65
|
+
remote_job: Builders.method(:start_remote_job)
|
66
|
+
},
|
67
|
+
method:
|
68
|
+
->(registry, params, n_xml) { @module_registry.run_registry_on_path('//builders', registry, params, n_xml) }
|
69
|
+
},
|
70
|
+
publishers: {
|
71
|
+
registry: {
|
72
|
+
git: Publishers.method(:push_to_git),
|
73
|
+
hipchat: Publishers.method(:push_to_hipchat),
|
74
|
+
description_setter: Publishers.method(:description_setter),
|
75
|
+
downstream: Publishers.method(:push_to_projects),
|
76
|
+
junit_result: Publishers.method(:publish_junit),
|
77
|
+
coverage_result: Publishers.method(:publish_rcov),
|
78
|
+
post_build_script: Publishers.method(:post_build_script),
|
79
|
+
groovy_postbuild: Publishers.method(:groovy_postbuild)
|
80
|
+
},
|
81
|
+
method:
|
82
|
+
->(registry, params, n_xml) { @module_registry.run_registry_on_path('//publishers', registry, params, n_xml) }
|
83
|
+
},
|
84
|
+
wrappers: {
|
85
|
+
registry: {
|
86
|
+
timestamp: Wrappers.method(:console_timestamp),
|
87
|
+
ansicolor: Wrappers.method(:ansicolor),
|
88
|
+
artifactory: Wrappers.method(:publish_to_artifactory),
|
89
|
+
rvm: Wrappers.method(:run_with_rvm),
|
90
|
+
rvm05: Wrappers.method(:run_with_rvm05),
|
91
|
+
inject_env_var: Wrappers.method(:inject_env_vars),
|
92
|
+
inject_passwords: Wrappers.method(:inject_passwords),
|
93
|
+
maven3artifactory: Wrappers.method(:artifactory_maven3_configurator)
|
94
|
+
},
|
95
|
+
method:
|
96
|
+
->(registry, params, n_xml) { @module_registry.run_registry_on_path('//buildWrappers', registry, params, n_xml) }
|
97
|
+
},
|
98
|
+
triggers: {
|
99
|
+
registry: {
|
100
|
+
git_push: Triggers.method(:enable_git_push),
|
101
|
+
scm_polling: Triggers.method(:enable_scm_polling),
|
102
|
+
periodic_build: Triggers.method(:enable_periodic_build),
|
103
|
+
upstream: Triggers.method(:enable_upstream_check)
|
104
|
+
},
|
105
|
+
method:
|
106
|
+
->(registry, params, n_xml) { @module_registry.run_registry_on_path('//triggers', registry, params, n_xml) }
|
103
107
|
}
|
104
|
-
|
108
|
+
}
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def load_extensions(path)
|
113
|
+
path = "#{path}/extensions"
|
114
|
+
path = File.expand_path(path, Dir.getwd)
|
115
|
+
if File.directory?(path)
|
116
|
+
@logger.info "Loading extensions from folder #{path}"
|
117
|
+
Dir[File.join(path, '/*.yaml'), File.join(path, '/*.yml')].each do |file|
|
118
|
+
@logger.info "Loading file #{file}"
|
119
|
+
yaml = YAML.load_file(file)
|
120
|
+
yaml.each do |ext|
|
121
|
+
Utils.symbolize_keys_deep!(ext)
|
122
|
+
ext = ext[:extension]
|
123
|
+
name = ext[:name]
|
124
|
+
type = ext[:type]
|
125
|
+
function = ext[:function]
|
126
|
+
fail "Duplicate extension with name '#{name}' was detected." if @extensions.key?(name)
|
127
|
+
@extensions[name.to_s] = { name: name.to_s, type: type, function: function }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
@extensions.each_value do |ext|
|
132
|
+
name = ext[:name].to_sym
|
133
|
+
registry = @module_registry.registry[:job]
|
134
|
+
function = eval "Proc.new {|params,xml| #{ext[:function]} }"
|
135
|
+
type = ext[:type].downcase.pluralize.to_sym if ext[:type]
|
136
|
+
if type
|
137
|
+
registry[type][:registry][name] = function
|
138
|
+
else
|
139
|
+
registry[name] = function
|
140
|
+
end
|
141
|
+
end
|
105
142
|
end
|
106
143
|
|
107
|
-
attr_accessor :client
|
108
144
|
def debug=(value)
|
109
145
|
@debug = value
|
110
|
-
@logger.level = (value) ? Logger::DEBUG : Logger::INFO
|
111
|
-
end
|
112
|
-
def debug
|
113
|
-
@debug
|
146
|
+
@logger.level = (value) ? Logger::DEBUG : Logger::INFO
|
114
147
|
end
|
115
|
-
|
148
|
+
|
149
|
+
attr_reader :debug
|
150
|
+
attr_accessor :client
|
116
151
|
attr_accessor :no_files
|
117
152
|
attr_accessor :job_collection
|
118
153
|
|
@@ -124,12 +159,12 @@ module JenkinsPipelineBuilder
|
|
124
159
|
JenkinsPipelineBuilder::View.new(self)
|
125
160
|
end
|
126
161
|
|
127
|
-
def load_collection_from_path(path, recursively = false)
|
128
|
-
path = File.expand_path(path,
|
162
|
+
def load_collection_from_path(path, recursively = false, remote = false)
|
163
|
+
path = File.expand_path(path, Dir.getwd)
|
129
164
|
if File.directory?(path)
|
130
165
|
@logger.info "Generating from folder #{path}"
|
131
166
|
Dir[File.join(path, '/*.yaml'), File.join(path, '/*.yml')].each do |file|
|
132
|
-
if File.directory?(file)
|
167
|
+
if File.directory?(file) # TODO: This doesn't work unless the folder contains .yml or .yaml at the end
|
133
168
|
if recursively
|
134
169
|
load_collection_from_path(File.join(path, file), recursively)
|
135
170
|
else
|
@@ -138,23 +173,35 @@ module JenkinsPipelineBuilder
|
|
138
173
|
end
|
139
174
|
@logger.info "Loading file #{file}"
|
140
175
|
yaml = YAML.load_file(file)
|
141
|
-
load_job_collection(yaml)
|
176
|
+
load_job_collection(yaml, remote)
|
142
177
|
end
|
143
178
|
else
|
144
179
|
@logger.info "Loading file #{path}"
|
145
180
|
yaml = YAML.load_file(path)
|
146
|
-
load_job_collection(yaml)
|
181
|
+
load_job_collection(yaml, remote)
|
147
182
|
end
|
148
183
|
end
|
149
184
|
|
150
|
-
def load_job_collection(yaml)
|
185
|
+
def load_job_collection(yaml, remote = false)
|
151
186
|
yaml.each do |section|
|
152
187
|
Utils.symbolize_keys_deep!(section)
|
153
188
|
key = section.keys.first
|
154
189
|
value = section[key]
|
190
|
+
if key == :dependencies
|
191
|
+
@logger.info 'Resolving Dependencies for remote project'
|
192
|
+
return load_remote_yaml(value)
|
193
|
+
end
|
194
|
+
|
155
195
|
name = value[:name]
|
156
|
-
|
157
|
-
|
196
|
+
if @job_collection.key?(name)
|
197
|
+
if remote
|
198
|
+
@logger.info "Duplicate item with name '#{name}' was detected from the remote folder."
|
199
|
+
else
|
200
|
+
fail "Duplicate item with name '#{name}' was detected."
|
201
|
+
end
|
202
|
+
else
|
203
|
+
@job_collection[name.to_s] = { name: name.to_s, type: key, value: value }
|
204
|
+
end
|
158
205
|
end
|
159
206
|
end
|
160
207
|
|
@@ -162,33 +209,116 @@ module JenkinsPipelineBuilder
|
|
162
209
|
@job_collection[name.to_s]
|
163
210
|
end
|
164
211
|
|
165
|
-
def
|
166
|
-
|
167
|
-
|
212
|
+
def load_template(path, template)
|
213
|
+
# If we specify what folder the yaml is in, load that
|
214
|
+
if template[:folder]
|
215
|
+
path = File.join(path, template[:folder])
|
216
|
+
else
|
217
|
+
path = File.join(path, template[:name]) unless template[:name] == 'default'
|
218
|
+
# If we are looking for the newest version or no version was set
|
219
|
+
if (template[:version].nil? || template[:version] == 'newest') && File.directory?(path)
|
220
|
+
folders = Dir.entries(path)
|
221
|
+
highest = '0' # Default to v1
|
222
|
+
folders.each do |f|
|
223
|
+
highest = f if f > highest # Note: to_i returns any integers in the folder name
|
224
|
+
end
|
225
|
+
template[:version] = highest unless highest == 0
|
226
|
+
end
|
227
|
+
path = File.join(path, template[:version]) unless template[:version].nil?
|
228
|
+
path = File.join(path, 'pipeline')
|
229
|
+
end
|
168
230
|
|
169
|
-
|
170
|
-
|
231
|
+
if File.directory?(path)
|
232
|
+
@logger.info "Loading from #{path}"
|
233
|
+
load_collection_from_path(path, false, true)
|
234
|
+
true
|
235
|
+
else
|
236
|
+
false
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def download_yaml(url, file)
|
241
|
+
@remote_depends[url] = file
|
242
|
+
@logger.info "Downloading #{url} to #{file}.tar"
|
243
|
+
open("#{file}.tar", 'w') do |local_file|
|
244
|
+
open(url) do |remote_file|
|
245
|
+
local_file.write(Zlib::GzipReader.new(remote_file).read)
|
246
|
+
end
|
247
|
+
end
|
171
248
|
|
172
|
-
#
|
173
|
-
|
249
|
+
# Extract Tar.gz to 'remote' folder
|
250
|
+
@logger.info "Unpacking #{file}.tar to #{file} folder"
|
251
|
+
Archive::Tar::Minitar.unpack("#{file}.tar", file)
|
252
|
+
end
|
253
|
+
|
254
|
+
def load_remote_yaml(dependencies)
|
255
|
+
### Load remote YAML
|
256
|
+
# Download Tar.gz
|
257
|
+
dependencies.each do |source|
|
258
|
+
source = source[:source]
|
259
|
+
url = source[:url]
|
260
|
+
file = "remote-#{@remote_depends.length}"
|
261
|
+
if @remote_depends[url]
|
262
|
+
file = @remote_depends[url]
|
263
|
+
else
|
264
|
+
download_yaml(url, file)
|
265
|
+
end
|
266
|
+
|
267
|
+
path = File.expand_path(file, Dir.getwd)
|
268
|
+
# Load templates recursively
|
269
|
+
unless source[:templates]
|
270
|
+
@logger.info 'No specific template specified'
|
271
|
+
# Try to load the folder or the pipeline folder
|
272
|
+
path = File.join(path, 'pipeline') if Dir.entries(path).include? 'pipeline'
|
273
|
+
return load_collection_from_path(path)
|
274
|
+
end
|
275
|
+
|
276
|
+
load_templates(source[:templates])
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def load_templates(templates)
|
281
|
+
templates.each do |template|
|
282
|
+
version = template[:version] || 'newest'
|
283
|
+
@logger.info "Loading #{template[:name]} at version #{version}"
|
284
|
+
# Move into the remote folder and look for the template folder
|
285
|
+
remote = Dir.entries(path)
|
286
|
+
if remote.include? template[:name]
|
287
|
+
# We found the template name, load this path
|
288
|
+
@logger.info 'We found the template!'
|
289
|
+
load_template(path, template)
|
290
|
+
else
|
291
|
+
# Many cases we must dig one layer deep
|
292
|
+
remote.each do |file|
|
293
|
+
load_template(File.join(path, file), template)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def cleanup_temp_remote
|
300
|
+
@remote_depends.each_value do |file|
|
301
|
+
FileUtils.rm_r file
|
302
|
+
FileUtils.rm_r "#{file}.tar"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def prepare_jobs(jobs)
|
174
307
|
jobs.map! do |job|
|
175
308
|
job.kind_of?(String) ? { job.to_sym => {} } : job
|
176
309
|
end
|
177
|
-
|
178
|
-
|
310
|
+
end
|
311
|
+
|
312
|
+
def process_job_changes(jobs)
|
179
313
|
jobs.each do |job|
|
180
314
|
job_id = job.keys.first
|
181
|
-
|
182
|
-
|
183
|
-
if
|
184
|
-
job[:result] = payload
|
185
|
-
else
|
186
|
-
errors[job_id] = payload
|
187
|
-
end
|
315
|
+
j = get_item(job_id)
|
316
|
+
Utils.hash_merge!(j, job[job_id])
|
317
|
+
j[:value][:name] = j[:job_name] if j[:job_name]
|
188
318
|
end
|
319
|
+
end
|
189
320
|
|
190
|
-
|
191
|
-
views = project_body[:views] || []
|
321
|
+
def process_views(views, project, errors = {})
|
192
322
|
views.map! do |view|
|
193
323
|
view.kind_of?(String) ? { view.to_sym => {} } : view
|
194
324
|
end
|
@@ -203,27 +333,55 @@ module JenkinsPipelineBuilder
|
|
203
333
|
errors[view_id] = payload
|
204
334
|
end
|
205
335
|
end
|
336
|
+
errors
|
337
|
+
end
|
206
338
|
|
207
|
-
|
339
|
+
def process_jobs(jobs, project, errors = {})
|
340
|
+
jobs.each do |job|
|
341
|
+
job_id = job.keys.first
|
342
|
+
settings = project[:settings].clone.merge(job[job_id])
|
343
|
+
success, payload = resolve_job_by_name(job_id, settings)
|
344
|
+
if success
|
345
|
+
job[:result] = payload
|
346
|
+
else
|
347
|
+
errors[job_id] = payload
|
348
|
+
end
|
349
|
+
end
|
350
|
+
errors
|
351
|
+
end
|
352
|
+
|
353
|
+
def resolve_project(project)
|
354
|
+
defaults = get_item('global')
|
355
|
+
settings = defaults.nil? ? {} : defaults[:value] || {}
|
356
|
+
|
357
|
+
project[:settings] = Compiler.get_settings_bag(project, settings) unless project[:settings]
|
358
|
+
project_body = project[:value]
|
359
|
+
|
360
|
+
jobs = prepare_jobs(project_body[:jobs]) if project_body[:jobs]
|
361
|
+
@logger.info project
|
362
|
+
process_job_changes(jobs)
|
363
|
+
errors = process_jobs(jobs, project)
|
364
|
+
errors = process_view(project_body[:views], project, errors) if project_body[:views]
|
365
|
+
|
366
|
+
errors.each do |k, v|
|
208
367
|
puts "Encountered errors processing: #{k}:"
|
209
368
|
v.each do |key, error|
|
210
369
|
puts " key: #{key} had the following error:"
|
211
370
|
puts " #{error.inspect}"
|
212
371
|
end
|
213
372
|
end
|
214
|
-
return false,
|
373
|
+
return false, 'Encountered errors exiting' unless errors.empty?
|
215
374
|
|
216
|
-
|
375
|
+
[true, project]
|
217
376
|
end
|
218
377
|
|
219
378
|
def resolve_job_by_name(name, settings = {})
|
220
379
|
job = get_item(name)
|
221
|
-
|
380
|
+
fail "Failed to locate job by name '#{name}'" if job.nil?
|
222
381
|
job_value = job[:value]
|
223
382
|
@logger.debug "Compiling job #{name}"
|
224
|
-
|
225
|
-
success, payload
|
226
|
-
return success, payload
|
383
|
+
success, payload = Compiler.compile(job_value, settings, @job_collection)
|
384
|
+
[success, payload]
|
227
385
|
end
|
228
386
|
|
229
387
|
def projects
|
@@ -231,7 +389,7 @@ module JenkinsPipelineBuilder
|
|
231
389
|
@job_collection.values.each do |item|
|
232
390
|
result << item if item[:type] == :project
|
233
391
|
end
|
234
|
-
|
392
|
+
result
|
235
393
|
end
|
236
394
|
|
237
395
|
def jobs
|
@@ -239,64 +397,96 @@ module JenkinsPipelineBuilder
|
|
239
397
|
@job_collection.values.each do |item|
|
240
398
|
result << item if item[:type] == :job
|
241
399
|
end
|
242
|
-
|
400
|
+
result
|
401
|
+
end
|
402
|
+
|
403
|
+
def publish_project(project_name, errors = {})
|
404
|
+
projects.each do |project|
|
405
|
+
next if project_name && project[:name] == project_name
|
406
|
+
success, payload = resolve_project(project)
|
407
|
+
if success
|
408
|
+
puts 'successfully resolved project'
|
409
|
+
compiled_project = payload
|
410
|
+
else
|
411
|
+
puts payload
|
412
|
+
return false
|
413
|
+
end
|
414
|
+
|
415
|
+
if compiled_project[:value][:jobs]
|
416
|
+
errors = publish_jobs(compiled_project[:value][:jobs])
|
417
|
+
end
|
418
|
+
if compiled_project[:value][:views]
|
419
|
+
compiled_project[:value][:views].each do |v|
|
420
|
+
compiled_view = v[:result]
|
421
|
+
view.create(compiled_view)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
errors
|
426
|
+
end
|
427
|
+
|
428
|
+
def publish_jobs(jobs, errors = {})
|
429
|
+
jobs.each do |i|
|
430
|
+
puts "Processing #{i}"
|
431
|
+
job = i[:result]
|
432
|
+
fail "Result is empty for #{i}" if job.nil?
|
433
|
+
success, payload = compile_job_to_xml(job)
|
434
|
+
if success
|
435
|
+
create_or_update(job, payload)
|
436
|
+
else
|
437
|
+
errors[job[:name]] = payload
|
438
|
+
end
|
439
|
+
end
|
440
|
+
errors
|
243
441
|
end
|
244
442
|
|
245
443
|
def bootstrap(path, project_name)
|
246
444
|
@logger.info "Bootstrapping pipeline from path #{path}"
|
247
445
|
load_collection_from_path(path)
|
248
|
-
|
446
|
+
cleanup_temp_remote
|
447
|
+
load_extensions(path)
|
249
448
|
errors = {}
|
250
449
|
# Publish all the jobs if the projects are not found
|
251
450
|
if projects.count == 0
|
252
|
-
|
253
|
-
job = i[:value]
|
254
|
-
success, payload = compile_job_to_xml(job)
|
255
|
-
if success
|
256
|
-
create_or_update(job, payload)
|
257
|
-
else
|
258
|
-
errors[job[:name]] = payload
|
259
|
-
end
|
260
|
-
end
|
451
|
+
errors = publish_jobs(jobs)
|
261
452
|
else
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
puts payload
|
270
|
-
return false
|
271
|
-
end
|
272
|
-
|
273
|
-
if compiled_project[:value][:jobs]
|
274
|
-
compiled_project[:value][:jobs].each do |i|
|
275
|
-
puts "Processing #{i}"
|
276
|
-
job = i[:result]
|
277
|
-
fail "Result is empty for #{i}" if job.nil?
|
278
|
-
success, payload = compile_job_to_xml(job)
|
279
|
-
if success
|
280
|
-
create_or_update(job, payload)
|
281
|
-
else
|
282
|
-
errors[job[:name]] = payload
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
453
|
+
errors = publish_project(project_name)
|
454
|
+
end
|
455
|
+
errors.each do |k, v|
|
456
|
+
@logger.error "Encountered errors compiling: #{k}:"
|
457
|
+
@logger.error v
|
458
|
+
end
|
459
|
+
end
|
286
460
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
461
|
+
def pull_request(path, project_name)
|
462
|
+
@logger.info "Pull Request Generator Running from path #{path}"
|
463
|
+
load_collection_from_path(path)
|
464
|
+
cleanup_temp_remote
|
465
|
+
load_extensions(path)
|
466
|
+
jobs = {}
|
467
|
+
projects.each do |project|
|
468
|
+
if project[:name] == project_name || project_name.nil?
|
469
|
+
project_body = project[:value]
|
470
|
+
project_jobs = project_body[:jobs] || []
|
471
|
+
@logger.info "Using Project #{project}"
|
472
|
+
pull_job = nil
|
473
|
+
project_jobs.each do |job|
|
474
|
+
job = @job_collection[job.to_s]
|
475
|
+
pull_job = job if job[:value][:job_type] == 'pull_request_generator'
|
476
|
+
end
|
477
|
+
fail 'No Pull Request Found for Project' unless pull_job
|
478
|
+
pull_jobs = pull_job[:value][:jobs] || []
|
479
|
+
pull_jobs.each do |job|
|
480
|
+
if job.is_a? String
|
481
|
+
jobs[job.to_s] = @job_collection[job.to_s]
|
482
|
+
else
|
483
|
+
jobs[job.keys[0].to_s] = @job_collection[job.keys[0].to_s]
|
292
484
|
end
|
293
485
|
end
|
486
|
+
pull = JenkinsPipelineBuilder::PullRequestGenerator.new(self)
|
487
|
+
pull.run(project, jobs, pull_job)
|
294
488
|
end
|
295
489
|
end
|
296
|
-
errors.each do |k,v|
|
297
|
-
@logger.error "Encountered errors compiling: #{k}:"
|
298
|
-
@logger.error v
|
299
|
-
end
|
300
490
|
end
|
301
491
|
|
302
492
|
def dump(job_name)
|
@@ -323,40 +513,40 @@ module JenkinsPipelineBuilder
|
|
323
513
|
end
|
324
514
|
|
325
515
|
def compile_job_to_xml(job)
|
326
|
-
|
516
|
+
fail 'Job name is not specified' unless job[:name]
|
327
517
|
|
328
518
|
@logger.info "Creating Yaml Job #{job}"
|
329
519
|
job[:job_type] = 'free_style' unless job[:job_type]
|
330
520
|
case job[:job_type]
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
521
|
+
when 'job_dsl'
|
522
|
+
xml = compile_freestyle_job_to_xml(job)
|
523
|
+
payload = update_job_dsl(job, xml)
|
524
|
+
when 'multi_project'
|
525
|
+
xml = compile_freestyle_job_to_xml(job)
|
526
|
+
payload = adjust_multi_project xml
|
527
|
+
when 'build_flow'
|
528
|
+
xml = compile_freestyle_job_to_xml(job)
|
529
|
+
payload = add_job_dsl(job, xml)
|
530
|
+
when 'free_style', 'pull_request_generator'
|
531
|
+
payload = compile_freestyle_job_to_xml job
|
532
|
+
else
|
533
|
+
return false, "Job type: #{job[:job_type]} is not one of job_dsl, multi_project, build_flow or free_style"
|
344
534
|
end
|
345
535
|
|
346
|
-
|
536
|
+
[true, payload]
|
347
537
|
end
|
348
538
|
|
349
539
|
def adjust_multi_project(xml)
|
350
540
|
n_xml = Nokogiri::XML(xml)
|
351
|
-
root = n_xml.root
|
541
|
+
root = n_xml.root
|
352
542
|
root.name = 'com.tikal.jenkins.plugins.multijob.MultiJobProject'
|
353
543
|
n_xml.to_xml
|
354
544
|
end
|
355
545
|
|
356
546
|
def compile_freestyle_job_to_xml(params)
|
357
|
-
if params.
|
547
|
+
if params.key?(:template)
|
358
548
|
template_name = params[:template]
|
359
|
-
|
549
|
+
fail "Job template '#{template_name}' can't be resolved." unless @job_templates.key?(template_name)
|
360
550
|
params.delete(:template)
|
361
551
|
template = @job_templates[template_name]
|
362
552
|
puts "Template found: #{template}"
|
@@ -373,7 +563,7 @@ module JenkinsPipelineBuilder
|
|
373
563
|
end
|
374
564
|
|
375
565
|
def add_job_dsl(job, xml)
|
376
|
-
n_xml
|
566
|
+
n_xml = Nokogiri::XML(xml)
|
377
567
|
n_xml.root.name = 'com.cloudbees.plugins.flow.BuildFlow'
|
378
568
|
Nokogiri::XML::Builder.with(n_xml.root) do |xml|
|
379
569
|
xml.dsl job[:build_flow]
|
@@ -392,25 +582,25 @@ module JenkinsPipelineBuilder
|
|
392
582
|
end
|
393
583
|
|
394
584
|
def generate_job_dsl_body(params)
|
395
|
-
@logger.info
|
585
|
+
@logger.info 'Generating pipeline'
|
396
586
|
|
397
587
|
xml = @client.job.build_freestyle_config(params)
|
398
588
|
|
399
589
|
n_xml = Nokogiri::XML(xml)
|
400
590
|
if n_xml.xpath('//javaposse.jobdsl.plugin.ExecuteDslScripts').empty?
|
401
|
-
p_xml = Nokogiri::XML::Builder.new(:
|
591
|
+
p_xml = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |b_xml|
|
402
592
|
build_job_dsl(params, b_xml)
|
403
593
|
end
|
404
594
|
|
405
|
-
n_xml.xpath('//builders').first.add_child("\r\n" + p_xml.doc.root.to_xml(:
|
595
|
+
n_xml.xpath('//builders').first.add_child("\r\n" + p_xml.doc.root.to_xml(indent: 4) + "\r\n")
|
406
596
|
xml = n_xml.to_xml
|
407
597
|
end
|
408
598
|
xml
|
409
599
|
end
|
410
600
|
|
411
601
|
def build_job_dsl(job, xml)
|
412
|
-
xml.send('javaposse.jobdsl.plugin.ExecuteDslScripts')
|
413
|
-
if job.
|
602
|
+
xml.send('javaposse.jobdsl.plugin.ExecuteDslScripts') do
|
603
|
+
if job.key?(:job_dsl)
|
414
604
|
xml.scriptText job[:job_dsl]
|
415
605
|
xml.usingScriptText true
|
416
606
|
else
|
@@ -419,7 +609,7 @@ module JenkinsPipelineBuilder
|
|
419
609
|
end
|
420
610
|
xml.ignoreExisting false
|
421
611
|
xml.removedJobAction 'IGNORE'
|
422
|
-
|
612
|
+
end
|
423
613
|
end
|
424
614
|
end
|
425
615
|
end
|