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
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZjNiOGM2YmE3ZWUwNWU3NWIzMDA3NWQ3NjViZWYxNmQ5M2I1NzU5MQ==
5
- data.tar.gz: !binary |-
6
- Yzc4NmM2NjllNzIyMzVlNWNlZjViMjFhZmYwMzJkYWU0Y2M2ZmJiNw==
2
+ SHA1:
3
+ metadata.gz: 2435d7d3bfbce44945a8ac65b3a2e974447659aa
4
+ data.tar.gz: 91f22ff5ea5254f056d3ebbd2966b2ddb1a968d2
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- OWFiYTMxYWFjNjQ4Zjc4MTU3OWU5OTJhNWUxOTJkMTRkM2Y1ZjA3ZDBjZWU2
10
- MzllZDExOGQ5MGU1YjhhMjFmMmVlMTEyYmE4M2YzZjEzMDYyMjg1NWU4OTJi
11
- Y2UzYTdmNjkxMzZlYzA4MmU4YzM1YzZhMjhlY2NiYzRlZTE5NGU=
12
- data.tar.gz: !binary |-
13
- ODVkNDhlNjdjY2M3YzkyMGNlMzBiM2VmYzhjMjE3MzMxZDFiZjY1MTEyMTk4
14
- MmMxMjZkMzlmOTE4YzNiYTA0ZDU5MmZiNDAwNDc2ODY3MDM1ZTJjYmQ2MWY3
15
- YjI5NTA4OWRhZjIxZmU3ZGNjYmZkZDZjNTZmNGY0ZTE4M2JmN2I=
6
+ metadata.gz: e3eaad5a500bbe56cfd70dac4d56ee3834d041e935b984386432b0d84e7eacc2db8541348f79d19e083a0e412089232d36cc5400d7f646e00a6eda8cbfb90936
7
+ data.tar.gz: 8b55b43d229e060aca4165941242acdbaca512ad3b645314c76a13ac780496ee65312952fcf2aab456fbe46cfb4def734f41eed798efe7fc6bba4e5c30e56acb
@@ -3,25 +3,25 @@
3
3
  description: 'Pipeline'
4
4
  job_type: multi_project
5
5
  wrappers:
6
- - timestamp: true
7
- - ansicolor: true
8
- - rvm: '{{rvm}}'
6
+ - timestamp: true
7
+ - ansicolor: true
8
+ - rvm: '{{rvm}}'
9
9
  builders:
10
- - multi_job:
11
- phases:
12
- Commit:
13
- jobs:
14
- - name: '{{name}}-Commit'
15
- continue_condition: SUCCESSFUL
16
- Acceptance:
17
- jobs:
18
- - name: '{{name}}-Acceptance'
19
- continue_condiction: SUCCESSFUL
20
- Upgrade:
21
- jobs:
22
- - name: '{{name}}-Upgrade'
23
- continue_condiction: SUCCESSFUL
24
- Release:
25
- jobs:
26
- - name: '{{name}}-Release'
27
- continue_condiction: SUCCESSFUL
10
+ - multi_job:
11
+ phases:
12
+ Commit:
13
+ jobs:
14
+ - name: '{{name}}-Commit'
15
+ continue_condition: SUCCESSFUL
16
+ Acceptance:
17
+ jobs:
18
+ - name: '{{name}}-Acceptance'
19
+ continue_condition: SUCCESSFUL
20
+ Upgrade:
21
+ jobs:
22
+ - name: '{{name}}-Upgrade'
23
+ continue_condition: SUCCESSFUL
24
+ Release:
25
+ jobs:
26
+ - name: '{{name}}-Release'
27
+ continue_condition: SUCCESSFUL
@@ -0,0 +1,32 @@
1
+ - promotion:
2
+ name: '01. Stage Promotion'
3
+ promotion_description: 'Describe the promotion process in play'
4
+ job_type: promotion
5
+ block_when_downstream_building: false # unless defined? false
6
+ block_when_upstream_building: false # unless defined? false
7
+ promotion_icon: 'silver' # defaults to gold, silver white blue green orange purple red
8
+ promotion_conditions: # follow parameters
9
+ - manual:
10
+ users: 'authenticated' # || ''
11
+ - self_promotion:
12
+ even_if_unstable: true # unless defined? false
13
+ - parameterized_self_promotion:
14
+ parameter_name: 'SOME_ENV_VAR'
15
+ parameter_value: true
16
+ even_if_unstable: true # unless defined? false
17
+ - downstream_pass:
18
+ jobs: '{{name}}-Commit'
19
+ even_if_unstable: true # unless defined? false
20
+ - upstream_promotion:
21
+ promotion_name: '01. Staging Promotion'
22
+ build_steps: # follow builders
23
+ - triggered_job:
24
+ name: '{{name}}-Release' # maps to <projects>
25
+ block_condition: # included in xml only if checked on UI
26
+ build_step_failure_threshold: 'FAILURE' # 'FAILURE' # %s(never FAILURE UNSTABLE SUCCESS)
27
+ failure_threshold: 'FAILURE' # || # %s(never FAILURE UNSTABLE SUCCESS)
28
+ unstable_threshold: 'UNSTABLE' # || # %s(never FAILURE UNSTABLE SUCCESS)
29
+ build_parameters:
30
+ current: true # unless defined? false
31
+ - keep_builds_forever:
32
+ value: true
@@ -7,6 +7,8 @@
7
7
  local_branch: '{{git_branch}}'
8
8
  recursive_update: true
9
9
  wipe_workspace: true
10
+ promoted_builds:
11
+ - '01. Stage Promotion'
10
12
  builders:
11
13
  - shell_command: |
12
14
  sleep 60
@@ -24,3 +24,5 @@
24
24
  - '{{name}}-Acceptance' # from template (see dependencies section)
25
25
  - '{{name}}-Upgrade'
26
26
  - '{{name}}-Release'
27
+ promotions:
28
+ - '01. Stage Promotion'
@@ -40,6 +40,7 @@ automating Job & Pipeline creation from the YAML files checked-in with your appl
40
40
  spec.add_development_dependency 'equivalent-xml'
41
41
  spec.add_development_dependency 'yard-thor'
42
42
  spec.add_development_dependency 'yard'
43
+ spec.add_development_dependency 'pry'
43
44
  spec.add_development_dependency 'rspec_junit_formatter'
44
45
  spec.add_development_dependency 'webmock', '~> 1.0'
45
46
  spec.add_development_dependency 'rubocop', '= 0.40.0'
@@ -25,12 +25,14 @@ require 'active_support/core_ext'
25
25
 
26
26
  require 'jenkins_pipeline_builder/version'
27
27
  require 'jenkins_pipeline_builder/utils'
28
+ require 'jenkins_pipeline_builder/custom_errors'
28
29
  require 'jenkins_pipeline_builder/compiler'
29
30
  require 'jenkins_pipeline_builder/module_registry'
30
31
  require 'jenkins_pipeline_builder/pull_request_generator'
31
32
  require 'jenkins_pipeline_builder/view'
32
33
  require 'jenkins_pipeline_builder/job_collection'
33
34
  require 'jenkins_pipeline_builder/job'
35
+ require 'jenkins_pipeline_builder/promotion'
34
36
  require 'jenkins_pipeline_builder/remote_dependencies'
35
37
  require 'jenkins_pipeline_builder/generator'
36
38
 
@@ -75,6 +77,7 @@ module JenkinsPipelineBuilder
75
77
  end
76
78
  end
77
79
  end
80
+
78
81
  JenkinsPipelineBuilder.generator
79
82
  require 'jenkins_pipeline_builder/extensions'
80
83
  require 'jenkins_pipeline_builder/extension_dsl'
@@ -89,6 +92,8 @@ require 'jenkins_pipeline_builder/extensions/job_attributes'
89
92
  require 'jenkins_pipeline_builder/extensions/wrappers'
90
93
  require 'jenkins_pipeline_builder/extensions/publishers'
91
94
  require 'jenkins_pipeline_builder/extensions/triggers'
95
+ require 'jenkins_pipeline_builder/extensions/build_steps'
96
+ require 'jenkins_pipeline_builder/extensions/promotion_conditions'
92
97
 
93
98
  require 'jenkins_pipeline_builder/cli/helper'
94
99
  require 'jenkins_pipeline_builder/cli/view'
@@ -24,7 +24,7 @@ module JenkinsPipelineBuilder
24
24
  module CLI
25
25
  entries = JenkinsPipelineBuilder.registry.entries.keys
26
26
  entries << :job_attributes
27
- entries.each do |entry|
27
+ entries.try(:each) do |entry|
28
28
  klass_name = entry.to_s.classify
29
29
  # rubocop:disable Style/AccessModifierIndentation
30
30
  klass = Class.new(Thor) do
@@ -34,7 +34,7 @@ module JenkinsPipelineBuilder
34
34
  JenkinsPipelineBuilder.registry.registry[:job][entry]
35
35
  end
36
36
 
37
- extensions.each do |key, extset|
37
+ extensions.try(:each) do |key, extset|
38
38
  # TODO: don't just take the first
39
39
  ext = extset.extensions.first
40
40
  desc key, "Details for #{ext.name}"
@@ -52,10 +52,12 @@ module JenkinsPipelineBuilder
52
52
  # rubocop:enable Style/AccessModifierIndentation
53
53
  Module.const_set(klass_name, klass)
54
54
  end
55
+
55
56
  class Describe < Thor
56
57
  entries = JenkinsPipelineBuilder.registry.entries.keys
57
58
  entries << :job_attributes
58
- entries.each do |entry, _path|
59
+
60
+ entries.try(:each) do |entry, _path|
59
61
  klass_name = entry.to_s.classify
60
62
  singular_model = entry.to_s.singularize
61
63
 
@@ -0,0 +1,20 @@
1
+ module CustomErrors
2
+ class ParseError < StandardError
3
+ def initialize(msg, path = nil)
4
+ super(format_msg(msg, path).squeeze(' '))
5
+ end
6
+
7
+ private
8
+
9
+ def format_msg(msg, path)
10
+ if path.nil?
11
+ %(There was an error while parsing a file:
12
+ #{msg})
13
+ else
14
+ %(There was an error while parsing a file:
15
+ #{path}
16
+ #{msg})
17
+ end
18
+ end
19
+ end
20
+ end
@@ -6,7 +6,7 @@ JenkinsPipelineBuilder.registry.entries.each do |type, path|
6
6
 
7
7
  JenkinsPipelineBuilder.registry.register([:job, type], set)
8
8
  versions = set.extensions.map(&:min_version)
9
- puts "Successfully registered #{set.name} for versions #{versions}" if set.announced
9
+ JenkinsPipelineBuilder.logger.info "Successfully registered #{set.name} for versions #{versions}" if set.announced
10
10
  true
11
11
  end
12
12
  end
@@ -17,6 +17,6 @@ def job_attribute(&block)
17
17
 
18
18
  JenkinsPipelineBuilder.registry.register([:job], set)
19
19
  versions = set.extensions.map(&:min_version)
20
- puts "Successfully registered #{set.name} for versions #{versions}" if set.announced
20
+ JenkinsPipelineBuilder.logger.info "Successfully registered #{set.name} for versions #{versions}" if set.announced
21
21
  true
22
22
  end
@@ -8,6 +8,7 @@ module JenkinsPipelineBuilder
8
8
  :announced,
9
9
  :type
10
10
  ].freeze
11
+
11
12
  SET_METHODS.each do |method_name|
12
13
  define_method method_name do |value = nil|
13
14
  return settings[method_name] if value.nil?
@@ -66,7 +67,8 @@ module JenkinsPipelineBuilder
66
67
  extension = versions[highest_allowed_version]
67
68
 
68
69
  unless extension
69
- raise "Can't find version of #{name} lte #{installed_version}, available versions: #{versions.keys.map(&:to_s)}"
70
+ raise %(Can't find version of #{name} lte #{installed_version}.
71
+ Available versions: #{versions.keys.map(&:to_s)})
70
72
  end
71
73
  extension
72
74
  end
@@ -79,7 +81,7 @@ module JenkinsPipelineBuilder
79
81
  mismatch << "The values for #{method_name} do not match '#{val1}' : '#{val2}'" unless val1 == val2
80
82
  end
81
83
  mismatch.each do |error|
82
- puts error
84
+ logger.error error
83
85
  end
84
86
  raise 'Values did not match, cannot merge extension sets' if mismatch.any?
85
87
 
@@ -129,8 +131,8 @@ module JenkinsPipelineBuilder
129
131
  valid = errors.empty?
130
132
  unless valid
131
133
  name ||= 'A plugin with no name provided'
132
- puts "Encountered errors while registering #{name}"
133
- puts errors.map { |k, v| "#{k}: #{v}" }.join(', ')
134
+ logger.error "Encountered errors while registering #{name}"
135
+ logger.error errors.map { |k, v| "#{k}: #{v}" }.join(', ')
134
136
  end
135
137
  valid
136
138
  end
@@ -154,6 +156,10 @@ module JenkinsPipelineBuilder
154
156
 
155
157
  private
156
158
 
159
+ def logger
160
+ JenkinsPipelineBuilder.logger
161
+ end
162
+
157
163
  def highest_allowed_version
158
164
  ordered_version_list.each do |version|
159
165
  return version if version <= installed_version
@@ -173,7 +179,8 @@ module JenkinsPipelineBuilder
173
179
  end
174
180
 
175
181
  def deprecation_warning(name, block)
176
- puts "WARNING: #{name} set the version in the #{block} block, this is deprecated. Please use a version block."
182
+ logger.warn %(WARNING: #{name} set the version in the #{block} block, this is deprecated.
183
+ Please use a version block.)
177
184
  end
178
185
  end
179
186
  end
@@ -36,6 +36,7 @@ module JenkinsPipelineBuilder
36
36
  xml: false,
37
37
  parameters: []
38
38
  }.freeze
39
+
39
40
  EXT_METHODS.keys.each do |method_name|
40
41
  define_method method_name do |value = nil|
41
42
  return instance_variable_get("@#{method_name}") if value.nil?
@@ -57,10 +58,11 @@ module JenkinsPipelineBuilder
57
58
 
58
59
  def execute(value, n_xml)
59
60
  errors = check_parameters value
60
- errors.each do |error|
61
- JenkinsPipelineBuilder.logger.error error
62
- end
63
- return false if errors.any?
61
+ raise ArgumentError, errors.join("\n") if errors.any?
62
+ raise ArgumentError, %(Extension #{name} has no valid path
63
+ Check ModuleRegistry#entries and the definition of the extension
64
+ Note: job_attributes have no implicit path and must be set in the builder
65
+ ).squeeze(' ') unless path
64
66
 
65
67
  n_builders = n_xml.xpath(path).first
66
68
  n_builders.instance_exec(value, &before) if before
@@ -93,7 +95,6 @@ module JenkinsPipelineBuilder
93
95
  def build_extension_xml(n_builders, value)
94
96
  Nokogiri::XML::Builder.with(n_builders) do |builder|
95
97
  include_helper value, builder
96
- helper.extension = self
97
98
  builder.instance_exec helper, &xml
98
99
  end
99
100
  end
@@ -101,7 +102,7 @@ module JenkinsPipelineBuilder
101
102
  def include_helper(params, builder)
102
103
  klass = "#{name.to_s.camelize}Helper".safe_constantize
103
104
  klass ||= ExtensionHelper
104
- self.helper = klass.new params, builder
105
+ self.helper = klass.new self, params, builder
105
106
  end
106
107
  end
107
108
  end
@@ -0,0 +1,93 @@
1
+ # Technically triggered_job takes multiple jobs in the xml
2
+ # Since Nokogiri builders don't allow modifying existing nodes this is not
3
+ # straightforward to abstract away the underlying XML from the user.
4
+ #
5
+ # 1. So either we need to only accept 1 triggered job per promotion process OR
6
+ # 2. Make it take array of triggered jobs
7
+ #
8
+ # Current implementation went with option 1.
9
+ #
10
+ # This is very similar to the implementation for :blocking_downstream, but
11
+ # unfortunately sharing code between extensions doesn't seem to be possible
12
+
13
+ build_step do
14
+ name :triggered_job
15
+ plugin_id 'parameterized-trigger'
16
+ parameters [
17
+ :name,
18
+ # block_condition: { }
19
+ # or
20
+ # block_condition: _ # for the defaults
21
+ :block_condition,
22
+ # build_parameters: [
23
+ # [ :current, _ ],
24
+ # [ :predefined, {val: x, val2: y} ],
25
+ # [ :file, 'filpath' ] ]
26
+ :build_parameters
27
+ ]
28
+
29
+ xml do |state|
30
+ send('hudson.plugins.parameterizedtrigger.TriggerBuilder',
31
+ 'plugin' => 'parameterized-trigger@2.31') do
32
+ configs do
33
+ send('hudson.plugins.parameterizedtrigger.BlockableBuildTriggerConfig') do
34
+ if state[:build_parameters].present? && state[:build_parameters].respond_to?(:each)
35
+ configs do
36
+ state[:build_parameters].each do |param_key, param_val|
37
+ case param_key
38
+ when /current/i, :current
39
+ send('hudson.plugins.parameterizedtrigger.CurrentBuildParameters')
40
+
41
+ when /predefined/i, :predefined
42
+ send('hudson.plugins.parameterizedtrigger.PredefinedBuildParameters') do
43
+ properties param_val.map { |k, v| "#{k.upcase}=#{v}" }.join(' ')
44
+ end
45
+
46
+ when /file/i, :file
47
+ send('hudson.plugins.parameterizedtrigger.FileBuildParameters') do
48
+ propertiesFile param_val
49
+ failTriggerOnMissing false
50
+ useMatrixChild false
51
+ onlyExactRuns false
52
+ end
53
+ end
54
+ end
55
+ end
56
+ else
57
+ configs(class: 'empty-list') {}
58
+ end
59
+
60
+ projects state[:name]
61
+ condition 'ALWAYS'
62
+ triggerWithNoParameters false
63
+
64
+ block do
65
+ buildStepFailureThreshold do
66
+ state.generate_for_threshold(self,
67
+ state.resolve_block_condition(:build_step_failure_threshold) || :failure)
68
+ end
69
+ unstableThreshold do
70
+ state.generate_for_threshold(self,
71
+ state.resolve_block_condition(:unstable_threshold) || :unstable)
72
+ end
73
+ failureThreshold do
74
+ state.generate_for_threshold(self,
75
+ state.resolve_block_condition(:failure_threshold) || :failure)
76
+ end
77
+ end if state.block_condition?
78
+
79
+ buildAllNodesWithLabel false
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ build_step do
87
+ name :keep_builds_forever
88
+ plugin_id 'promoted-builds'
89
+
90
+ xml do
91
+ send('hudson.plugins.promoted__builds.KeepBuildForeverAction')
92
+ end
93
+ end
@@ -0,0 +1,36 @@
1
+ class TriggeredJobHelper < ExtensionHelper
2
+ # @param xml_builder [Nokogiri::Builder] this will be self inside an extension
3
+ # @param threshold_type [String, Symbol] case-insensitive string or symbol
4
+ # from the %(failure unstable success)
5
+ # @returns [Nokogiri::Builder] continues the mutation of the passed in builder
6
+ def generate_for_threshold(xml_builder, threshold_type)
7
+ case threshold_type
8
+ when /failure/i, :failure
9
+ generate_threshold_xml({ name: 'FAILURE', ordinal: 2, color: 'RED' }, xml_builder)
10
+ when /unstable/i, :unstable
11
+ generate_threshold_xml({ name: 'UNSTABLE', ordinal: 1, color: 'YELLOW' }, xml_builder)
12
+ when /success/i, :success
13
+ generate_threshold_xml({ name: 'SUCCESS', ordinal: 0, color: 'BLUE' }, xml_builder)
14
+ else
15
+ raise ArgumentError("Input should be one of the following either as a case insensitive string or symbol: \n
16
+ 'failure', 'unstable', 'success'")
17
+ end
18
+ end
19
+
20
+ def resolve_block_condition(key)
21
+ try(:block_condition).try([], key)
22
+ end
23
+
24
+ def block_condition?
25
+ respond_to?(:block_condition) && block_condition != false
26
+ end
27
+
28
+ private
29
+
30
+ def generate_threshold_xml(data, xml)
31
+ xml.send(:name, data[:name])
32
+ xml.send(:ordinal, data[:ordinal])
33
+ xml.send(:color, data[:color])
34
+ xml.send(:completeBuild, 'true')
35
+ end
36
+ end