jenkins_pipeline_builder 0.15.4 → 0.16.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.
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