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
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