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.
Files changed (43) hide show
  1. checksums.yaml +5 -13
  2. data/example/pipeline/Example-Pipeline.yaml +21 -21
  3. data/example/pipeline/Example-Promotion.yaml +32 -0
  4. data/example/pipeline/Example-Release.yaml +2 -0
  5. data/example/pipeline/project.yaml +2 -0
  6. data/jenkins_pipeline_builder.gemspec +1 -0
  7. data/lib/jenkins_pipeline_builder.rb +5 -0
  8. data/lib/jenkins_pipeline_builder/cli/describe.rb +5 -3
  9. data/lib/jenkins_pipeline_builder/custom_errors.rb +20 -0
  10. data/lib/jenkins_pipeline_builder/extension_dsl.rb +2 -2
  11. data/lib/jenkins_pipeline_builder/extension_set.rb +12 -5
  12. data/lib/jenkins_pipeline_builder/extensions.rb +7 -6
  13. data/lib/jenkins_pipeline_builder/extensions/build_steps.rb +93 -0
  14. data/lib/jenkins_pipeline_builder/extensions/helpers/build_steps/triggered_job.rb +36 -0
  15. data/lib/jenkins_pipeline_builder/extensions/helpers/builders/blocking_downstream_helper.rb +2 -2
  16. data/lib/jenkins_pipeline_builder/extensions/helpers/builders/maven3_helper.rb +2 -2
  17. data/lib/jenkins_pipeline_builder/extensions/helpers/extension_helper.rb +7 -7
  18. data/lib/jenkins_pipeline_builder/extensions/helpers/triggers/upstream_helper.rb +2 -2
  19. data/lib/jenkins_pipeline_builder/extensions/job_attributes.rb +60 -1
  20. data/lib/jenkins_pipeline_builder/extensions/promotion_conditions.rb +80 -0
  21. data/lib/jenkins_pipeline_builder/generator.rb +65 -57
  22. data/lib/jenkins_pipeline_builder/job.rb +1 -7
  23. data/lib/jenkins_pipeline_builder/job_collection.rb +16 -14
  24. data/lib/jenkins_pipeline_builder/module_registry.rb +4 -4
  25. data/lib/jenkins_pipeline_builder/promotion.rb +82 -0
  26. data/lib/jenkins_pipeline_builder/utils.rb +6 -0
  27. data/lib/jenkins_pipeline_builder/version.rb +1 -1
  28. data/lib/jenkins_pipeline_builder/view.rb +1 -4
  29. data/spec/lib/jenkins_pipeline_builder/extensions/build_steps_spec.rb +198 -0
  30. data/spec/lib/jenkins_pipeline_builder/extensions/builders_spec.rb +1 -3
  31. data/spec/lib/jenkins_pipeline_builder/extensions/job_attributes_spec.rb +63 -0
  32. data/spec/lib/jenkins_pipeline_builder/extensions/promotion_conditions_spec.rb +281 -0
  33. data/spec/lib/jenkins_pipeline_builder/extensions/registered_spec.rb +11 -3
  34. data/spec/lib/jenkins_pipeline_builder/fixtures/generator_tests/sample_pipeline/SamplePipeline-30-Release.yaml +3 -1
  35. data/spec/lib/jenkins_pipeline_builder/fixtures/generator_tests/sample_pipeline/SamplePipeline-40-Promotion.yaml +31 -0
  36. data/spec/lib/jenkins_pipeline_builder/fixtures/generator_tests/sample_pipeline/project.yaml +3 -1
  37. data/spec/lib/jenkins_pipeline_builder/fixtures/promotions_test/sample_promotion.yaml +31 -0
  38. data/spec/lib/jenkins_pipeline_builder/generator_spec.rb +2 -2
  39. data/spec/lib/jenkins_pipeline_builder/job_spec.rb +1 -19
  40. data/spec/lib/jenkins_pipeline_builder/promotion_spec.rb +88 -0
  41. data/spec/lib/jenkins_pipeline_builder/spec_helper.rb +6 -0
  42. metadata +86 -59
  43. 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,6 +1,6 @@
1
1
  class Maven3Helper < ExtensionHelper
2
- def initialize(params, builder)
3
- super params, builder, defaults
2
+ def initialize(extension, params, builder)
3
+ super extension, params, builder, defaults
4
4
  end
5
5
 
6
6
  def defaults
@@ -1,7 +1,6 @@
1
1
  class ExtensionHelper < SimpleDelegator
2
2
  attr_reader :params, :builder
3
- attr_accessor :extension
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
- super @params
16
- end
14
+ @extension = extension
17
15
 
18
- def method_missing(name, *args, &block)
19
- return super unless extension.parameters.include? name
20
- self[name]
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?
@@ -1,7 +1,7 @@
1
1
  class UpstreamHelper < ExtensionHelper
2
2
  attr_reader :color, :name, :ordinal
3
- def initialize(params, builder)
4
- super params, builder
3
+ def initialize(extension, params, builder)
4
+ super extension, params, builder
5
5
 
6
6
  case params[:status]
7
7
  when 'unstable'
@@ -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
- jobs = prepare_jobs(project_body[:jobs]) if project_body[:jobs]
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
- puts 'Encountered errors processing:'
147
- puts error.inspect
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 process_views(views, project, errors = {})
170
- views.map! do |view|
171
- view.is_a?(String) ? { view.to_sym => {} } : view
172
- end
173
- views.each do |view|
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
- job[:result] = payload
179
+ item[:result] = payload
194
180
  else
195
- errors[job_id] = payload
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]) if compiled_project[:value][:jobs]
216
- return errors unless compiled_project[:value][:views]
217
- create_views compiled_project[:value][:views]
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
- if JenkinsPipelineBuilder.client.job.exists?(name)
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
- result = []
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 "There was an error while parsing a file #{err.message}"
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