jenkins_pipeline_builder 0.15.4 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/example/pipeline/Example-Pipeline.yaml +21 -21
- data/example/pipeline/Example-Promotion.yaml +32 -0
- data/example/pipeline/Example-Release.yaml +2 -0
- data/example/pipeline/project.yaml +2 -0
- data/jenkins_pipeline_builder.gemspec +1 -0
- data/lib/jenkins_pipeline_builder.rb +5 -0
- data/lib/jenkins_pipeline_builder/cli/describe.rb +5 -3
- data/lib/jenkins_pipeline_builder/custom_errors.rb +20 -0
- data/lib/jenkins_pipeline_builder/extension_dsl.rb +2 -2
- data/lib/jenkins_pipeline_builder/extension_set.rb +12 -5
- data/lib/jenkins_pipeline_builder/extensions.rb +7 -6
- data/lib/jenkins_pipeline_builder/extensions/build_steps.rb +93 -0
- data/lib/jenkins_pipeline_builder/extensions/helpers/build_steps/triggered_job.rb +36 -0
- data/lib/jenkins_pipeline_builder/extensions/helpers/builders/blocking_downstream_helper.rb +2 -2
- data/lib/jenkins_pipeline_builder/extensions/helpers/builders/maven3_helper.rb +2 -2
- data/lib/jenkins_pipeline_builder/extensions/helpers/extension_helper.rb +7 -7
- data/lib/jenkins_pipeline_builder/extensions/helpers/triggers/upstream_helper.rb +2 -2
- data/lib/jenkins_pipeline_builder/extensions/job_attributes.rb +60 -1
- data/lib/jenkins_pipeline_builder/extensions/promotion_conditions.rb +80 -0
- data/lib/jenkins_pipeline_builder/generator.rb +65 -57
- data/lib/jenkins_pipeline_builder/job.rb +1 -7
- data/lib/jenkins_pipeline_builder/job_collection.rb +16 -14
- data/lib/jenkins_pipeline_builder/module_registry.rb +4 -4
- data/lib/jenkins_pipeline_builder/promotion.rb +82 -0
- data/lib/jenkins_pipeline_builder/utils.rb +6 -0
- data/lib/jenkins_pipeline_builder/version.rb +1 -1
- data/lib/jenkins_pipeline_builder/view.rb +1 -4
- data/spec/lib/jenkins_pipeline_builder/extensions/build_steps_spec.rb +198 -0
- data/spec/lib/jenkins_pipeline_builder/extensions/builders_spec.rb +1 -3
- data/spec/lib/jenkins_pipeline_builder/extensions/job_attributes_spec.rb +63 -0
- data/spec/lib/jenkins_pipeline_builder/extensions/promotion_conditions_spec.rb +281 -0
- data/spec/lib/jenkins_pipeline_builder/extensions/registered_spec.rb +11 -3
- data/spec/lib/jenkins_pipeline_builder/fixtures/generator_tests/sample_pipeline/SamplePipeline-30-Release.yaml +3 -1
- data/spec/lib/jenkins_pipeline_builder/fixtures/generator_tests/sample_pipeline/SamplePipeline-40-Promotion.yaml +31 -0
- data/spec/lib/jenkins_pipeline_builder/fixtures/generator_tests/sample_pipeline/project.yaml +3 -1
- data/spec/lib/jenkins_pipeline_builder/fixtures/promotions_test/sample_promotion.yaml +31 -0
- data/spec/lib/jenkins_pipeline_builder/generator_spec.rb +2 -2
- data/spec/lib/jenkins_pipeline_builder/job_spec.rb +1 -19
- data/spec/lib/jenkins_pipeline_builder/promotion_spec.rb +88 -0
- data/spec/lib/jenkins_pipeline_builder/spec_helper.rb +6 -0
- metadata +86 -59
- data/lib/jenkins_pipeline_builder/project.rb +0 -26
@@ -1,7 +1,7 @@
|
|
1
1
|
class BlockingDownstreamHelper < ExtensionHelper
|
2
2
|
attr_reader :colors
|
3
|
-
def initialize(params, builder)
|
4
|
-
super params, builder, defaults
|
3
|
+
def initialize(extension, params, builder)
|
4
|
+
super extension, params, builder, defaults
|
5
5
|
@colors = {
|
6
6
|
'SUCCESS' => { ordinal: 0, color: 'BLUE' },
|
7
7
|
'FAILURE' => { ordinal: 2, color: 'RED' },
|
@@ -1,7 +1,6 @@
|
|
1
1
|
class ExtensionHelper < SimpleDelegator
|
2
2
|
attr_reader :params, :builder
|
3
|
-
|
4
|
-
def initialize(params, builder, defaults = {})
|
3
|
+
def initialize(extension, params, builder, defaults = {})
|
5
4
|
# TODO: We should allow for default values to be passed in here
|
6
5
|
# That will allow for defaults to be pulled out of the extension and it
|
7
6
|
# will also let better enable overriding of those values that do not have
|
@@ -12,12 +11,13 @@ class ExtensionHelper < SimpleDelegator
|
|
12
11
|
params
|
13
12
|
end
|
14
13
|
@builder = builder
|
15
|
-
|
16
|
-
end
|
14
|
+
@extension = extension
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
@extension.parameters.try(:each) do |method_name|
|
17
|
+
define_singleton_method(method_name) { self[method_name] }
|
18
|
+
end
|
19
|
+
|
20
|
+
super @params
|
21
21
|
end
|
22
22
|
|
23
23
|
# TODO: Method missing that pulls out of params?
|
@@ -20,6 +20,64 @@
|
|
20
20
|
# THE SOFTWARE.
|
21
21
|
#
|
22
22
|
|
23
|
+
# Promotion specific job attributes
|
24
|
+
job_attribute do
|
25
|
+
name :promotion_description
|
26
|
+
plugin_id 'builtin'
|
27
|
+
description 'This is the description of your promotion.'
|
28
|
+
jenkins_name 'Description'
|
29
|
+
announced false
|
30
|
+
|
31
|
+
xml path: '//hudson.plugins.promoted__builds.PromotionProcess' do |description|
|
32
|
+
description description.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
job_attribute do
|
37
|
+
name :block_when_downstream_building
|
38
|
+
plugin_id 'builtin'
|
39
|
+
description 'Prevents new builds from being executed until the downstream jobs have finished.'
|
40
|
+
|
41
|
+
xml path: '//hudson.plugins.promoted__builds.PromotionProcess' do |is_enabled|
|
42
|
+
blockBuildWhenDownstreamBuilding is_enabled
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
job_attribute do
|
47
|
+
name :block_when_upstream_building
|
48
|
+
plugin_id 'builtin'
|
49
|
+
description 'Prevents new builds from being executed until the upstream jobs have finished.'
|
50
|
+
|
51
|
+
xml path: '//hudson.plugins.promoted__builds.PromotionProcess' do |is_enabled|
|
52
|
+
blockBuildWhenUpstreamBuilding is_enabled
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
job_attribute do
|
57
|
+
name :is_visible
|
58
|
+
plugin_id 'builtin'
|
59
|
+
# TODO: Verify that this description is actually what this does
|
60
|
+
description 'Set a promotion process to be visible in the UI'
|
61
|
+
|
62
|
+
xml path: '//hudson.plugins.promoted__builds.PromotionProcess' do |is_enabled|
|
63
|
+
isVisible if is_enabled
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
job_attribute do
|
68
|
+
name :promotion_icon
|
69
|
+
plugin_id 'builtin'
|
70
|
+
description 'Set the star color for a promotion process'
|
71
|
+
|
72
|
+
# Should be one main color %[gold silver white blue green orange purple red]
|
73
|
+
# With an optional fill color "e" for empty "w" for white
|
74
|
+
# e.g. "gold" or "gold-w"
|
75
|
+
xml path: '//hudson.plugins.promoted__builds.PromotionProcess' do |icon_name|
|
76
|
+
icon "star-#{icon_name}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Job attributes for jobs
|
23
81
|
job_attribute do
|
24
82
|
name :description
|
25
83
|
plugin_id 'builtin'
|
@@ -27,7 +85,7 @@ job_attribute do
|
|
27
85
|
jenkins_name 'Description'
|
28
86
|
announced false
|
29
87
|
|
30
|
-
before do
|
88
|
+
before do |_param|
|
31
89
|
xpath('//project/description').remove
|
32
90
|
end
|
33
91
|
|
@@ -367,6 +425,7 @@ job_attribute do
|
|
367
425
|
end
|
368
426
|
end
|
369
427
|
end
|
428
|
+
|
370
429
|
job_attribute do
|
371
430
|
name :promoted_builds
|
372
431
|
plugin_id 'promoted-builds'
|
@@ -0,0 +1,80 @@
|
|
1
|
+
promotion_condition do
|
2
|
+
name :manual
|
3
|
+
plugin_id 'promoted-builds'
|
4
|
+
parameters [
|
5
|
+
:users
|
6
|
+
]
|
7
|
+
|
8
|
+
xml do |params|
|
9
|
+
send('hudson.plugins.promoted__builds.conditions.ManualCondition') do
|
10
|
+
users params[:users]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
promotion_condition do
|
16
|
+
name :self_promotion
|
17
|
+
plugin_id 'promoted-builds'
|
18
|
+
parameters [
|
19
|
+
:even_if_unstable
|
20
|
+
]
|
21
|
+
|
22
|
+
xml do |params|
|
23
|
+
send('hudson.plugins.promoted__builds.conditions.SelfPromotionCondition') do
|
24
|
+
evenIfUnstable true if params[:even_if_unstable].nil?
|
25
|
+
evenIfUnstable params[:even_if_unstable]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
promotion_condition do
|
31
|
+
name :parameterized_self_promotion
|
32
|
+
plugin_id 'promoted-builds'
|
33
|
+
parameters [
|
34
|
+
:parameter_name,
|
35
|
+
:parameter_value,
|
36
|
+
:even_if_unstable
|
37
|
+
]
|
38
|
+
|
39
|
+
xml do |params|
|
40
|
+
send('hudson.plugins.promoted__builds.conditions.ParameterizedSelfPromotionCondition') do
|
41
|
+
parameterName params[:parameter_name]
|
42
|
+
parameterValue true if params[:parameter_value].nil?
|
43
|
+
evenIfUnstable true if params[:even_if_unstable].nil?
|
44
|
+
parameterValue params[:parameter_value]
|
45
|
+
evenIfUnstable params[:even_if_unstable]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
promotion_condition do
|
51
|
+
name :downstream_pass
|
52
|
+
plugin_id 'promoted-builds'
|
53
|
+
parameters [
|
54
|
+
:jobs,
|
55
|
+
:even_if_unstable
|
56
|
+
]
|
57
|
+
|
58
|
+
xml do |params|
|
59
|
+
send('hudson.plugins.promoted__builds.conditions.DownstreamPassCondition') do
|
60
|
+
jobs params[:jobs] || '{{Example}}-Commit'
|
61
|
+
evenIfUnstable true if params[:even_if_unstable].nil?
|
62
|
+
evenIfUnstable params[:even_if_unstable]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
promotion_condition do
|
68
|
+
name :upstream_promotion
|
69
|
+
plugin_id 'promoted-builds'
|
70
|
+
parameters [
|
71
|
+
:promotion_name
|
72
|
+
]
|
73
|
+
|
74
|
+
xml do |params|
|
75
|
+
send('hudson.plugins.promoted__builds.conditions.UpstreamPromotionCondition') do
|
76
|
+
promotionName '01. Staging Promotion' if params[:promotion_name].nil?
|
77
|
+
promotionName params[:promotion_name]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -84,16 +84,6 @@ module JenkinsPipelineBuilder
|
|
84
84
|
File.open(job_name + '.xml', 'w') { |f| f.write xml }
|
85
85
|
end
|
86
86
|
|
87
|
-
def resolve_job_by_name(name, settings = {})
|
88
|
-
job = job_collection.get_item(name)
|
89
|
-
raise "Failed to locate job by name '#{name}'" if job.nil?
|
90
|
-
job_value = job[:value]
|
91
|
-
logger.debug "Compiling job #{name}"
|
92
|
-
compiler = JenkinsPipelineBuilder::Compiler.new self
|
93
|
-
success, payload = compiler.compile_job(job_value, settings)
|
94
|
-
[success, payload]
|
95
|
-
end
|
96
|
-
|
97
87
|
def resolve_project(project)
|
98
88
|
defaults = job_collection.defaults
|
99
89
|
settings = defaults.nil? ? {} : defaults[:value] || {}
|
@@ -101,12 +91,25 @@ module JenkinsPipelineBuilder
|
|
101
91
|
project[:settings] = compiler.get_settings_bag(project, settings)
|
102
92
|
|
103
93
|
errors = process_project project
|
94
|
+
|
104
95
|
print_project_errors errors
|
105
96
|
return false, 'Encountered errors exiting' unless errors.empty?
|
106
97
|
|
107
98
|
[true, project]
|
108
99
|
end
|
109
100
|
|
101
|
+
# Works for jobs, views, and promotions
|
102
|
+
def resolve_job_by_name(name, settings = {})
|
103
|
+
job = job_collection.get_item(name)
|
104
|
+
raise "Failed to locate job by name '#{name}'" if job.nil?
|
105
|
+
job_value = job[:value]
|
106
|
+
logger.debug "Compiling job #{name}"
|
107
|
+
compiler = JenkinsPipelineBuilder::Compiler.new self
|
108
|
+
success, payload = compiler.compile_job(job_value, settings)
|
109
|
+
[success, payload]
|
110
|
+
end
|
111
|
+
alias resolve_section_by_name resolve_job_by_name
|
112
|
+
|
110
113
|
private
|
111
114
|
|
112
115
|
def load_job_collection(path)
|
@@ -125,12 +128,18 @@ module JenkinsPipelineBuilder
|
|
125
128
|
end
|
126
129
|
|
127
130
|
def process_project(project)
|
131
|
+
errors = {}
|
128
132
|
project_body = project[:value]
|
129
|
-
|
133
|
+
|
134
|
+
%i(jobs views promotions).each do |key|
|
135
|
+
next unless project_body[key]
|
136
|
+
|
137
|
+
Utils.symbolize_with_empty_hash!(project_body[key])
|
138
|
+
process_job_changes!(project_body[:jobs]) if key == :jobs
|
139
|
+
process_pipeline_section(project_body[key], project, errors)
|
140
|
+
end
|
141
|
+
|
130
142
|
logger.info project
|
131
|
-
process_job_changes(jobs)
|
132
|
-
errors = process_jobs(jobs, project)
|
133
|
-
errors = process_views(project_body[:views], project, errors) if project_body[:views]
|
134
143
|
errors
|
135
144
|
end
|
136
145
|
|
@@ -143,18 +152,12 @@ module JenkinsPipelineBuilder
|
|
143
152
|
|
144
153
|
def print_project_errors(errors)
|
145
154
|
errors.each do |error|
|
146
|
-
|
147
|
-
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def prepare_jobs(jobs)
|
152
|
-
jobs.map! do |job|
|
153
|
-
job.is_a?(String) ? { job.to_sym => {} } : job
|
155
|
+
logger.error 'Encountered errors processing:'
|
156
|
+
logger.error error.inspect
|
154
157
|
end
|
155
158
|
end
|
156
159
|
|
157
|
-
def process_job_changes(jobs)
|
160
|
+
def process_job_changes!(jobs)
|
158
161
|
jobs.each do |job|
|
159
162
|
job_id = job.keys.first
|
160
163
|
j = job_collection.get_item(job_id)
|
@@ -166,45 +169,21 @@ module JenkinsPipelineBuilder
|
|
166
169
|
end
|
167
170
|
end
|
168
171
|
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
view_id = view.keys.first
|
175
|
-
settings = project[:settings].clone.merge(view[view_id])
|
176
|
-
# TODO: rename resolve_job_by_name properly
|
177
|
-
success, payload = resolve_job_by_name(view_id, settings)
|
178
|
-
if success
|
179
|
-
view[:result] = payload
|
180
|
-
else
|
181
|
-
errors[view_id] = payload
|
182
|
-
end
|
183
|
-
end
|
184
|
-
errors
|
185
|
-
end
|
172
|
+
def process_pipeline_section(section, project, errors = {})
|
173
|
+
section.each do |item|
|
174
|
+
item_id = item.keys.first
|
175
|
+
settings = project[:settings].clone.merge(item[item_id])
|
176
|
+
success, payload = resolve_section_by_name(item_id, settings)
|
186
177
|
|
187
|
-
def process_jobs(jobs, project, errors = {})
|
188
|
-
jobs.each do |job|
|
189
|
-
job_id = job.keys.first
|
190
|
-
settings = project[:settings].clone.merge(job[job_id])
|
191
|
-
success, payload = resolve_job_by_name(job_id, settings)
|
192
178
|
if success
|
193
|
-
|
179
|
+
item[:result] = payload
|
194
180
|
else
|
195
|
-
errors[
|
181
|
+
errors[item_id] = payload
|
196
182
|
end
|
197
183
|
end
|
198
184
|
errors
|
199
185
|
end
|
200
186
|
|
201
|
-
def create_views(views)
|
202
|
-
views.each do |v|
|
203
|
-
compiled_view = v[:result]
|
204
|
-
view.create(compiled_view)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
187
|
def create_jobs_and_views(project)
|
209
188
|
success, payload = resolve_project(project)
|
210
189
|
return { project_name: 'Failed to resolve' } unless success
|
@@ -212,9 +191,15 @@ module JenkinsPipelineBuilder
|
|
212
191
|
logger.info 'successfully resolved project'
|
213
192
|
compiled_project = payload
|
214
193
|
|
215
|
-
errors = publish_jobs(compiled_project[:value][:jobs])
|
216
|
-
|
217
|
-
|
194
|
+
errors = publish_jobs(compiled_project[:value][:jobs])
|
195
|
+
|
196
|
+
if compiled_project[:value][:views]
|
197
|
+
publish_views(compiled_project[:value][:views])
|
198
|
+
end
|
199
|
+
|
200
|
+
if compiled_project[:value][:promotions]
|
201
|
+
publish_promotions(compiled_project[:value][:promotions], compiled_project[:value][:jobs])
|
202
|
+
end
|
218
203
|
errors
|
219
204
|
end
|
220
205
|
|
@@ -223,6 +208,29 @@ module JenkinsPipelineBuilder
|
|
223
208
|
create_jobs_and_views(project || raise("Project #{project_name} not found!"))
|
224
209
|
end
|
225
210
|
|
211
|
+
def publish_promotions(promotions, jobs)
|
212
|
+
# Converts a list of jobs that might have a list of promoted_builds to
|
213
|
+
# A hash of promoted_builds names => associated job names
|
214
|
+
promotion_job_pairs = jobs.each_with_object({}) do |j, acc|
|
215
|
+
j[:result][:promoted_builds].each do |promotion_name|
|
216
|
+
acc[promotion_name] = j[:result][:name]
|
217
|
+
end if j[:result][:promoted_builds]
|
218
|
+
end
|
219
|
+
|
220
|
+
promotions.each do |promotion|
|
221
|
+
compiled_promotion = promotion[:result]
|
222
|
+
associated_job_name = promotion_job_pairs[compiled_promotion[:name]]
|
223
|
+
Promotion.new(self).create(compiled_promotion, associated_job_name)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def publish_views(views)
|
228
|
+
views.each do |view|
|
229
|
+
compiled_view = view[:result]
|
230
|
+
View.new(self).create(compiled_view)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
226
234
|
def publish_jobs(jobs, errors = {})
|
227
235
|
jobs.each do |i|
|
228
236
|
logger.info "Processing #{i}"
|
@@ -20,11 +20,7 @@ module JenkinsPipelineBuilder
|
|
20
20
|
xml = payload
|
21
21
|
return local_output(xml) if JenkinsPipelineBuilder.debug || JenkinsPipelineBuilder.file_mode
|
22
22
|
|
23
|
-
|
24
|
-
JenkinsPipelineBuilder.client.job.update(name, xml)
|
25
|
-
else
|
26
|
-
JenkinsPipelineBuilder.client.job.create(name, xml)
|
27
|
-
end
|
23
|
+
JenkinsPipelineBuilder.client.job.create_or_update(name, xml)
|
28
24
|
[true, nil]
|
29
25
|
end
|
30
26
|
|
@@ -115,9 +111,7 @@ module JenkinsPipelineBuilder
|
|
115
111
|
raise "Job template '#{template_name}' can't be resolved." unless @job_templates.key?(template_name)
|
116
112
|
params.delete(:template)
|
117
113
|
template = @job_templates[template_name]
|
118
|
-
puts "Template found: #{template}"
|
119
114
|
params = template.deep_merge(params)
|
120
|
-
puts "Template merged: #{template}"
|
121
115
|
end
|
122
116
|
|
123
117
|
xml = JenkinsPipelineBuilder.client.job.build_freestyle_config(params)
|
@@ -18,24 +18,16 @@ module JenkinsPipelineBuilder
|
|
18
18
|
JenkinsPipelineBuilder.logger
|
19
19
|
end
|
20
20
|
|
21
|
-
def projects
|
22
|
-
result = []
|
23
|
-
collection.values.each do |item|
|
24
|
-
result << item if item[:type] == :project
|
25
|
-
end
|
26
|
-
result
|
27
|
-
end
|
28
|
-
|
29
21
|
def standalone_jobs
|
30
22
|
jobs.map { |job| { result: job } }
|
31
23
|
end
|
32
24
|
|
25
|
+
def projects
|
26
|
+
collect_type :project
|
27
|
+
end
|
28
|
+
|
33
29
|
def jobs
|
34
|
-
|
35
|
-
collection.values.each do |item|
|
36
|
-
result << item if item[:type] == :job
|
37
|
-
end
|
38
|
-
result
|
30
|
+
collect_type :job
|
39
31
|
end
|
40
32
|
|
41
33
|
def defaults
|
@@ -69,6 +61,10 @@ module JenkinsPipelineBuilder
|
|
69
61
|
|
70
62
|
private
|
71
63
|
|
64
|
+
def collect_type(type_name)
|
65
|
+
collection.values.select { |item| item if item[:type] == type_name }
|
66
|
+
end
|
67
|
+
|
72
68
|
def load_file(path, remote = false)
|
73
69
|
hash = if path.end_with? 'json'
|
74
70
|
JSON.parse(IO.read(path))
|
@@ -80,7 +76,7 @@ module JenkinsPipelineBuilder
|
|
80
76
|
load_section section, remote
|
81
77
|
end
|
82
78
|
rescue StandardError => err
|
83
|
-
raise
|
79
|
+
raise CustomErrors::ParseError.new err.message, path
|
84
80
|
end
|
85
81
|
|
86
82
|
def load_section(section, remote)
|
@@ -92,6 +88,12 @@ module JenkinsPipelineBuilder
|
|
92
88
|
remote_dependencies.load value
|
93
89
|
return
|
94
90
|
end
|
91
|
+
|
92
|
+
raise TypeError, %(Expected Hash received #{value.class}.
|
93
|
+
Verify that the pipeline section is made up of a single {key: Hash/Object} pair
|
94
|
+
See the definition for:
|
95
|
+
\t#{section}).squeeze(' ') unless value.is_a? Hash
|
96
|
+
|
95
97
|
name = value[:name]
|
96
98
|
process_collection! name, key, value, remote
|
97
99
|
end
|