jenkins_pipeline_builder 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +65 -0
  3. data/LICENSE +1 -1
  4. data/README.md +64 -2
  5. data/Rakefile +13 -11
  6. data/bin/generate +2 -4
  7. data/jenkins_pipeline_builder.gemspec +14 -11
  8. data/lib/jenkins_pipeline_builder.rb +2 -1
  9. data/lib/jenkins_pipeline_builder/builders.rb +89 -33
  10. data/lib/jenkins_pipeline_builder/cli/base.rb +21 -29
  11. data/lib/jenkins_pipeline_builder/cli/helper.rb +12 -15
  12. data/lib/jenkins_pipeline_builder/cli/pipeline.rb +6 -1
  13. data/lib/jenkins_pipeline_builder/cli/view.rb +2 -2
  14. data/lib/jenkins_pipeline_builder/compiler.rb +58 -56
  15. data/lib/jenkins_pipeline_builder/generator.rb +362 -172
  16. data/lib/jenkins_pipeline_builder/job_builder.rb +48 -45
  17. data/lib/jenkins_pipeline_builder/module_registry.rb +4 -6
  18. data/lib/jenkins_pipeline_builder/publishers.rb +53 -38
  19. data/lib/jenkins_pipeline_builder/pull_request.rb +156 -0
  20. data/lib/jenkins_pipeline_builder/triggers.rb +24 -25
  21. data/lib/jenkins_pipeline_builder/utils.rb +13 -7
  22. data/lib/jenkins_pipeline_builder/version.rb +2 -2
  23. data/lib/jenkins_pipeline_builder/view.rb +120 -98
  24. data/lib/jenkins_pipeline_builder/wrappers.rb +44 -44
  25. data/lib/jenkins_pipeline_builder/xml_helper.rb +4 -4
  26. data/spec/func_tests/spec_helper.rb +2 -2
  27. data/spec/func_tests/view_spec.rb +6 -6
  28. data/spec/unit_tests/compiler_spec.rb +7 -7
  29. data/spec/unit_tests/fixtures/files/Job-Gem-Build.xml +2 -2
  30. data/spec/unit_tests/fixtures/files/Job-Gem-Build.yaml +1 -0
  31. data/spec/unit_tests/fixtures/files/concurrent_build.xml +17 -0
  32. data/spec/unit_tests/fixtures/files/concurrent_build.yaml +4 -0
  33. data/spec/unit_tests/fixtures/files/downstream_blocking.xml +19 -0
  34. data/spec/unit_tests/fixtures/files/downstream_blocking.yaml +15 -0
  35. data/spec/unit_tests/fixtures/files/groovy_postbuild.xml +29 -0
  36. data/spec/unit_tests/fixtures/files/groovy_postbuild.yaml +9 -0
  37. data/spec/unit_tests/generator_spec.rb +30 -25
  38. data/spec/unit_tests/module_registry_spec.rb +9 -9
  39. data/spec/unit_tests/resolve_dependencies_spec.rb +108 -89
  40. data/spec/unit_tests/spec_helper.rb +1 -1
  41. metadata +62 -4
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2014 Igor Moochnick
2
+ # Copyright (c) 2014 Constant Contact
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -21,49 +21,41 @@
21
21
  #
22
22
 
23
23
  require 'thor'
24
- #require "#{File.dirname(__FILE__)}/pipeline.rb"
25
24
 
26
25
  module JenkinsPipelineBuilder
27
26
  module CLI
28
27
  class Base < Thor
28
+ class_option :username, aliases: '-u', desc: 'Name of Jenkins user'
29
+ class_option :password, aliases: '-p', desc: 'Password of Jenkins user'
30
+ class_option :password_base64, aliases: '-b', desc: 'Base 64 encoded password of Jenkins user'
31
+ class_option :server_ip, aliases: '-s', desc: 'Jenkins server IP address'
32
+ class_option :server_port, aliases: '-o', desc: 'Jenkins port'
33
+ class_option :creds_file, aliases: '-c', desc: 'Credentials file for communicating with Jenkins server'
34
+ class_option :debug, type: :boolean, aliases: '-d', desc: 'Run in debug mode (no Jenkins changes)', default: false
29
35
 
30
- class_option :username, :aliases => "-u", :desc => "Name of Jenkins user"
31
- class_option :password, :aliases => "-p",
32
- :desc => "Password of Jenkins user"
33
- class_option :password_base64, :aliases => "-b",
34
- :desc => "Base 64 encoded password of Jenkins user"
35
- class_option :server_ip, :aliases => "-s",
36
- :desc => "Jenkins server IP address"
37
- class_option :server_port, :aliases => "-o", :desc => "Jenkins port"
38
- class_option :creds_file, :aliases => "-c",
39
- :desc => "Credentials file for communicating with Jenkins server"
40
- class_option :debug, :type => :boolean, :aliases => "-d", :desc => "Run in debug mode (no Jenkins changes)",
41
- :default => false
36
+ map '-v' => :version
42
37
 
43
-
44
- map "-v" => :version
45
-
46
- desc "version", "Shows current version"
38
+ desc 'version', 'Shows current version'
47
39
  # CLI command that returns the version of Jenkins API Client
48
40
  def version
49
41
  puts JenkinsPipelineBuilder::VERSION
50
42
  end
51
43
 
52
- # Register the CLI::Pipeline class as "pipeline" subcommand to CLI
44
+ # Register the CLI::Pipeline class as 'pipeline' subcommand to CLI
53
45
  register(
54
- CLI::Pipeline,
55
- 'pipeline',
56
- 'pipeline [subcommand]',
57
- 'Provides functions to access pipeline functions of the Jenkins CI server'
46
+ CLI::Pipeline,
47
+ 'pipeline',
48
+ 'pipeline [subcommand]',
49
+ 'Provides functions to access pipeline functions of the Jenkins CI server'
58
50
  )
59
51
 
60
- # Register the CLI::Job class as "view" subcommand to CLI
52
+ # Register the CLI::Job class as 'view' subcommand to CLI
61
53
  register(
62
- CLI::View,
63
- 'view',
64
- 'view [subcommand]',
65
- 'Provides functions to access view interface of Jenkins CI server'
54
+ CLI::View,
55
+ 'view',
56
+ 'view [subcommand]',
57
+ 'Provides functions to access view interface of Jenkins CI server'
66
58
  )
67
59
  end
68
60
  end
69
- end
61
+ end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2014 Igor Moochnick
2
+ # Copyright (c) 2014 Constant Contact
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,7 +22,11 @@
22
22
 
23
23
  require 'fileutils'
24
24
  require 'yaml'
25
+ require 'json'
25
26
  require 'jenkins_api_client'
27
+ require 'open-uri'
28
+ require 'zlib'
29
+ require 'archive/tar/minitar'
26
30
 
27
31
  module JenkinsPipelineBuilder
28
32
  module CLI
@@ -40,29 +44,22 @@ module JenkinsPipelineBuilder
40
44
  (options[:password] || options[:password_base64])
41
45
  creds = options
42
46
  elsif options[:creds_file]
43
- creds = YAML.load_file(
44
- File.expand_path(options[:creds_file])
45
- #File.expand_path(options[:creds_file], __FILE__)
46
- #options[:creds_file]
47
- )
47
+ creds = YAML.load_file(File.expand_path(options[:creds_file]))
48
48
  elsif File.exist?("#{ENV['HOME']}/.jenkins_api_client/login.yml")
49
49
  creds = YAML.load_file(
50
- File.expand_path(
51
- "#{ENV['HOME']}/.jenkins_api_client/login.yml", __FILE__
52
- )
50
+ File.expand_path("#{ENV['HOME']}/.jenkins_api_client/login.yml", __FILE__)
53
51
  )
54
52
  else
55
- msg = "Credentials are not set. Please pass them as parameters or"
56
- msg << " set them in the default credentials file"
53
+ msg = 'Credentials are not set. Please pass them as parameters or'
54
+ msg << ' set them in the default credentials file'
57
55
  puts msg
58
56
  exit 1
59
57
  end
60
58
 
61
- #creds[:log_level] = Logger::DEBUG
62
59
  client = JenkinsApi::Client.new(creds)
63
- generator = JenkinsPipelineBuilder::Generator.new(options, client)
64
- generator.debug = options[:debug] #== 'debug'
65
- return generator
60
+ generator = JenkinsPipelineBuilder::Generator.new(client)
61
+ generator.debug = options[:debug]
62
+ generator
66
63
  end
67
64
  end
68
65
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2014 Igor Moochnick
2
+ # Copyright (c) 2014 Constant Contact
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -35,6 +35,11 @@ module JenkinsPipelineBuilder
35
35
  def bootstrap(path, project_name = nil)
36
36
  Helper.setup(parent_options).bootstrap(path, project_name)
37
37
  end
38
+
39
+ desc 'pull_request Path', 'Generates jenkins jobs based on a git pull request.'
40
+ def pull_request(path, project_name = nil)
41
+ Helper.setup(parent_options).pull_request(path, project_name)
42
+ end
38
43
  end
39
44
  end
40
45
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2014 Igor Moochnick
2
+ # Copyright (c) 2014 Constant Contact
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -37,4 +37,4 @@ module JenkinsPipelineBuilder
37
37
  end
38
38
  end
39
39
  end
40
- end
40
+ end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2014 Igor Moochnick
2
+ # Copyright (c) 2014 Constant Contact
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,17 +22,27 @@
22
22
 
23
23
  module JenkinsPipelineBuilder
24
24
  class Compiler
25
- def self.resolve_value(value, settings)
25
+ def self.resolve_value(value, settings, job_collection)
26
26
  settings = settings.with_indifferent_access
27
27
  value_s = value.to_s.clone
28
- vars = value_s.scan(/{{([^{}]+)}}/).flatten
28
+ # First we try to do job name correction
29
+ vars = value_s.scan(/{{job@(.*)}}/).flatten
30
+ if vars.count > 0
31
+ vars.select! do |var|
32
+ var_val = job_collection[var.to_s]
33
+ value_s.gsub!("{{job@#{var}}}", var_val[:value][:name]) unless var_val.nil?
34
+ var_val.nil?
35
+ end
36
+ end
37
+ # Then we look for normal values to replace
38
+ vars = value_s.scan(/{{([^{}@]+)}}/).flatten
29
39
  vars.select! do |var|
30
40
  var_val = settings[var]
31
- value_s.gsub!("{{#{var.to_s}}}", var_val) unless var_val.nil?
41
+ value_s.gsub!("{{#{var}}}", var_val) unless var_val.nil?
32
42
  var_val.nil?
33
43
  end
34
44
  return nil if vars.count != 0
35
- return value_s
45
+ value_s
36
46
  end
37
47
 
38
48
  def self.get_settings_bag(item_bag, settings_bag = {})
@@ -42,73 +52,65 @@ module JenkinsPipelineBuilder
42
52
  item.keys.each do |k|
43
53
  val = item[k]
44
54
  if val.kind_of? String
45
- new_value = resolve_value(val, settings_bag)
55
+ new_value = resolve_value(val, settings_bag, {})
46
56
  return nil if new_value.nil?
47
57
  bag[k] = new_value
48
58
  end
49
59
  end
50
60
  my_settings_bag = settings_bag.clone
51
- return my_settings_bag.merge(bag)
61
+ my_settings_bag.merge(bag)
52
62
  end
53
63
 
54
- def self.compile(item, settings = {})
64
+ def self.compile(item, settings = {}, job_collection = {})
55
65
  errors = {}
56
66
  case item
57
- when String
58
- new_value = resolve_value(item, settings)
59
- if new_value.nil?
60
- errors[item] = "Failed to resolve #{item}"
67
+ when String
68
+ new_value = resolve_value(item, settings, job_collection)
69
+ errors[item] = "Failed to resolve #{item}" if new_value.nil?
70
+ return false, errors unless errors.empty?
71
+ return true, new_value
72
+ when Hash
73
+ result = {}
74
+ item.each do |key, value|
75
+ if value.nil?
76
+ errors[key] = "key: #{key} has a nil value, this is likely a yaml syntax error. Skipping children and siblings"
77
+ break
61
78
  end
62
- unless errors.empty?
63
- return false, errors
79
+ success, payload = compile(value, settings, job_collection)
80
+ unless success
81
+ errors.merge!(payload)
82
+ next
64
83
  end
65
- return true, new_value
66
- when Hash
67
- result = {}
68
- item.each do |key, value|
69
- if value.nil?
70
- errors[key] = "key: #{key} has a nil value, this is likely a yaml syntax error. Skipping children and siblings"
71
- break
72
- end
73
- success, payload = compile(value, settings)
74
- unless success
75
- errors.merge!(payload)
76
- next
77
- end
78
- if payload.nil?
79
- errors[key] = "Failed to resolve:\n===>key: #{key}\n\n===>value: #{value}\n\n===>of: #{item}"
80
- next
81
- end
82
- result[key] = payload
84
+ if payload.nil?
85
+ errors[key] = "Failed to resolve:\n===>key: #{key}\n\n===>value: #{value}\n\n===>of: #{item}"
86
+ next
83
87
  end
84
- unless errors.empty?
85
- return false, errors
88
+ result[key] = payload
89
+ end
90
+ return false, errors unless errors.empty?
91
+ return true, result
92
+ when Array
93
+ result = []
94
+ item.each do |value|
95
+ if value.nil?
96
+ errors[item] = "found a nil value when processing following array:\n #{item.inspect}"
97
+ break
86
98
  end
87
- return true, result
88
- when Array
89
- result = []
90
- item.each do |value|
91
- if value.nil?
92
- errors[item] = "found a nil value when processing following array:\n #{item.inspect}"
93
- break
94
- end
95
- success, payload = compile(value, settings)
96
- unless success
97
- errors.merge!(payload)
98
- next
99
- end
100
- if payload.nil?
101
- errors[value] = "Failed to resolve:\n===>item #{value}\n\n===>of list: #{item}"
102
- next
103
- end
104
- result << payload
99
+ success, payload = compile(value, settings, job_collection)
100
+ unless success
101
+ errors.merge!(payload)
102
+ next
105
103
  end
106
- unless errors.empty?
107
- return false, errors
104
+ if payload.nil?
105
+ errors[value] = "Failed to resolve:\n===>item #{value}\n\n===>of list: #{item}"
106
+ next
108
107
  end
109
- return true, result
108
+ result << payload
109
+ end
110
+ return false, errors unless errors.empty?
111
+ return true, result
110
112
  end
111
- return true, item
113
+ [true, item]
112
114
  end
113
115
  end
114
116
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2014 Igor Moochnick
2
+ # Copyright (c) 2014 Constant Contact
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -35,84 +35,119 @@ module JenkinsPipelineBuilder
35
35
  #
36
36
  # @raise [ArgumentError] when required options are not provided.
37
37
  #
38
- def initialize(args, client)
38
+ def initialize(client)
39
39
  @client = client
40
40
  @logger = @client.logger
41
- #@logger.level = (@debug) ? Logger::DEBUG : Logger::INFO;
41
+ # @logger.level = (@debug) ? Logger::DEBUG : Logger::INFO;
42
42
  @job_templates = {}
43
43
  @job_collection = {}
44
-
45
- @module_registry = ModuleRegistry.new ({
46
- job: {
47
- description: JobBuilder.method(:change_description),
48
- scm_params: JobBuilder.method(:apply_scm_params),
49
- hipchat: JobBuilder.method(:hipchat_notifier),
50
- parameters: JobBuilder.method(:build_parameters),
51
- priority: JobBuilder.method(:use_specific_priority),
52
- discard_old: JobBuilder.method(:discard_old_param),
53
- throttle: JobBuilder.method(:throttle_job),
54
- prepare_environment: JobBuilder.method(:prepare_environment),
55
- builders: {
56
- registry: {
57
- multi_job: Builders.method(:build_multijob),
58
- inject_vars_file: Builders.method(:build_environment_vars_injector),
59
- shell_command: Builders.method(:build_shell_command),
60
- maven3: Builders.method(:build_maven3),
61
- remote_job: Builders.method(:start_remote_job)
62
- },
63
- method:
64
- lambda { |registry, params, n_xml| @module_registry.run_registry_on_path('//builders', registry, params, n_xml) }
65
- },
66
- publishers: {
67
- registry: {
68
- git: Publishers.method(:push_to_git),
69
- hipchat: Publishers.method(:push_to_hipchat),
70
- description_setter: Publishers.method(:description_setter),
71
- downstream: Publishers.method(:push_to_projects),
72
- junit_result: Publishers.method(:publish_junit),
73
- coverage_result: Publishers.method(:publish_rcov),
74
- post_build_script: Publishers.method(:post_build_script)
75
- },
76
- method:
77
- lambda { |registry, params, n_xml| @module_registry.run_registry_on_path('//publishers', registry, params, n_xml) }
78
- },
79
- wrappers: {
80
- registry: {
81
- timestamp: Wrappers.method(:console_timestamp),
82
- ansicolor: Wrappers.method(:ansicolor),
83
- artifactory: Wrappers.method(:publish_to_artifactory),
84
- rvm: Wrappers.method(:run_with_rvm),
85
- rvm05: Wrappers.method(:run_with_rvm05),
86
- inject_env_var: Wrappers.method(:inject_env_vars),
87
- inject_passwords: Wrappers.method(:inject_passwords),
88
- maven3artifactory: Wrappers.method(:artifactory_maven3_configurator)
89
- },
90
- method:
91
- lambda { |registry, params, n_xml| @module_registry.run_registry_on_path('//buildWrappers', registry, params, n_xml) }
92
- },
93
- triggers: {
94
- registry: {
95
- git_push: Triggers.method(:enable_git_push),
96
- scm_polling: Triggers.method(:enable_scm_polling),
97
- periodic_build: Triggers.method(:enable_periodic_build),
98
- upstream: Triggers.method(:enable_upstream_check)
99
- },
100
- method:
101
- lambda { |registry, params, n_xml| @module_registry.run_registry_on_path('//triggers', registry, params, n_xml) }
102
- }
44
+ @extensions = {}
45
+ @remote_depends = {}
46
+
47
+ @module_registry = ModuleRegistry.new(
48
+ job: {
49
+ description: JobBuilder.method(:change_description),
50
+ scm_params: JobBuilder.method(:apply_scm_params),
51
+ hipchat: JobBuilder.method(:hipchat_notifier),
52
+ parameters: JobBuilder.method(:build_parameters),
53
+ priority: JobBuilder.method(:use_specific_priority),
54
+ discard_old: JobBuilder.method(:discard_old_param),
55
+ throttle: JobBuilder.method(:throttle_job),
56
+ prepare_environment: JobBuilder.method(:prepare_environment),
57
+ concurrent_build: JobBuilder.method(:concurrent_build),
58
+ builders: {
59
+ registry: {
60
+ multi_job: Builders.method(:build_multijob),
61
+ inject_vars_file: Builders.method(:build_environment_vars_injector),
62
+ shell_command: Builders.method(:build_shell_command),
63
+ maven3: Builders.method(:build_maven3),
64
+ blocking_downstream: Builders.method(:blocking_downstream),
65
+ remote_job: Builders.method(:start_remote_job)
66
+ },
67
+ method:
68
+ ->(registry, params, n_xml) { @module_registry.run_registry_on_path('//builders', registry, params, n_xml) }
69
+ },
70
+ publishers: {
71
+ registry: {
72
+ git: Publishers.method(:push_to_git),
73
+ hipchat: Publishers.method(:push_to_hipchat),
74
+ description_setter: Publishers.method(:description_setter),
75
+ downstream: Publishers.method(:push_to_projects),
76
+ junit_result: Publishers.method(:publish_junit),
77
+ coverage_result: Publishers.method(:publish_rcov),
78
+ post_build_script: Publishers.method(:post_build_script),
79
+ groovy_postbuild: Publishers.method(:groovy_postbuild)
80
+ },
81
+ method:
82
+ ->(registry, params, n_xml) { @module_registry.run_registry_on_path('//publishers', registry, params, n_xml) }
83
+ },
84
+ wrappers: {
85
+ registry: {
86
+ timestamp: Wrappers.method(:console_timestamp),
87
+ ansicolor: Wrappers.method(:ansicolor),
88
+ artifactory: Wrappers.method(:publish_to_artifactory),
89
+ rvm: Wrappers.method(:run_with_rvm),
90
+ rvm05: Wrappers.method(:run_with_rvm05),
91
+ inject_env_var: Wrappers.method(:inject_env_vars),
92
+ inject_passwords: Wrappers.method(:inject_passwords),
93
+ maven3artifactory: Wrappers.method(:artifactory_maven3_configurator)
94
+ },
95
+ method:
96
+ ->(registry, params, n_xml) { @module_registry.run_registry_on_path('//buildWrappers', registry, params, n_xml) }
97
+ },
98
+ triggers: {
99
+ registry: {
100
+ git_push: Triggers.method(:enable_git_push),
101
+ scm_polling: Triggers.method(:enable_scm_polling),
102
+ periodic_build: Triggers.method(:enable_periodic_build),
103
+ upstream: Triggers.method(:enable_upstream_check)
104
+ },
105
+ method:
106
+ ->(registry, params, n_xml) { @module_registry.run_registry_on_path('//triggers', registry, params, n_xml) }
103
107
  }
104
- })
108
+ }
109
+ )
110
+ end
111
+
112
+ def load_extensions(path)
113
+ path = "#{path}/extensions"
114
+ path = File.expand_path(path, Dir.getwd)
115
+ if File.directory?(path)
116
+ @logger.info "Loading extensions from folder #{path}"
117
+ Dir[File.join(path, '/*.yaml'), File.join(path, '/*.yml')].each do |file|
118
+ @logger.info "Loading file #{file}"
119
+ yaml = YAML.load_file(file)
120
+ yaml.each do |ext|
121
+ Utils.symbolize_keys_deep!(ext)
122
+ ext = ext[:extension]
123
+ name = ext[:name]
124
+ type = ext[:type]
125
+ function = ext[:function]
126
+ fail "Duplicate extension with name '#{name}' was detected." if @extensions.key?(name)
127
+ @extensions[name.to_s] = { name: name.to_s, type: type, function: function }
128
+ end
129
+ end
130
+ end
131
+ @extensions.each_value do |ext|
132
+ name = ext[:name].to_sym
133
+ registry = @module_registry.registry[:job]
134
+ function = eval "Proc.new {|params,xml| #{ext[:function]} }"
135
+ type = ext[:type].downcase.pluralize.to_sym if ext[:type]
136
+ if type
137
+ registry[type][:registry][name] = function
138
+ else
139
+ registry[name] = function
140
+ end
141
+ end
105
142
  end
106
143
 
107
- attr_accessor :client
108
144
  def debug=(value)
109
145
  @debug = value
110
- @logger.level = (value) ? Logger::DEBUG : Logger::INFO;
111
- end
112
- def debug
113
- @debug
146
+ @logger.level = (value) ? Logger::DEBUG : Logger::INFO
114
147
  end
115
- # TODO: WTF?
148
+
149
+ attr_reader :debug
150
+ attr_accessor :client
116
151
  attr_accessor :no_files
117
152
  attr_accessor :job_collection
118
153
 
@@ -124,12 +159,12 @@ module JenkinsPipelineBuilder
124
159
  JenkinsPipelineBuilder::View.new(self)
125
160
  end
126
161
 
127
- def load_collection_from_path(path, recursively = false)
128
- path = File.expand_path(path, relative_to=Dir.getwd)
162
+ def load_collection_from_path(path, recursively = false, remote = false)
163
+ path = File.expand_path(path, Dir.getwd)
129
164
  if File.directory?(path)
130
165
  @logger.info "Generating from folder #{path}"
131
166
  Dir[File.join(path, '/*.yaml'), File.join(path, '/*.yml')].each do |file|
132
- if File.directory?(file)
167
+ if File.directory?(file) # TODO: This doesn't work unless the folder contains .yml or .yaml at the end
133
168
  if recursively
134
169
  load_collection_from_path(File.join(path, file), recursively)
135
170
  else
@@ -138,23 +173,35 @@ module JenkinsPipelineBuilder
138
173
  end
139
174
  @logger.info "Loading file #{file}"
140
175
  yaml = YAML.load_file(file)
141
- load_job_collection(yaml)
176
+ load_job_collection(yaml, remote)
142
177
  end
143
178
  else
144
179
  @logger.info "Loading file #{path}"
145
180
  yaml = YAML.load_file(path)
146
- load_job_collection(yaml)
181
+ load_job_collection(yaml, remote)
147
182
  end
148
183
  end
149
184
 
150
- def load_job_collection(yaml)
185
+ def load_job_collection(yaml, remote = false)
151
186
  yaml.each do |section|
152
187
  Utils.symbolize_keys_deep!(section)
153
188
  key = section.keys.first
154
189
  value = section[key]
190
+ if key == :dependencies
191
+ @logger.info 'Resolving Dependencies for remote project'
192
+ return load_remote_yaml(value)
193
+ end
194
+
155
195
  name = value[:name]
156
- raise "Duplicate item with name '#{name}' was detected." if @job_collection.has_key?(name)
157
- @job_collection[name.to_s] = { name: name.to_s, type: key, value: value }
196
+ if @job_collection.key?(name)
197
+ if remote
198
+ @logger.info "Duplicate item with name '#{name}' was detected from the remote folder."
199
+ else
200
+ fail "Duplicate item with name '#{name}' was detected."
201
+ end
202
+ else
203
+ @job_collection[name.to_s] = { name: name.to_s, type: key, value: value }
204
+ end
158
205
  end
159
206
  end
160
207
 
@@ -162,33 +209,116 @@ module JenkinsPipelineBuilder
162
209
  @job_collection[name.to_s]
163
210
  end
164
211
 
165
- def resolve_project(project)
166
- defaults = get_item('global')
167
- settings = defaults.nil? ? {} : defaults[:value] || {}
212
+ def load_template(path, template)
213
+ # If we specify what folder the yaml is in, load that
214
+ if template[:folder]
215
+ path = File.join(path, template[:folder])
216
+ else
217
+ path = File.join(path, template[:name]) unless template[:name] == 'default'
218
+ # If we are looking for the newest version or no version was set
219
+ if (template[:version].nil? || template[:version] == 'newest') && File.directory?(path)
220
+ folders = Dir.entries(path)
221
+ highest = '0' # Default to v1
222
+ folders.each do |f|
223
+ highest = f if f > highest # Note: to_i returns any integers in the folder name
224
+ end
225
+ template[:version] = highest unless highest == 0
226
+ end
227
+ path = File.join(path, template[:version]) unless template[:version].nil?
228
+ path = File.join(path, 'pipeline')
229
+ end
168
230
 
169
- project[:settings] = Compiler.get_settings_bag(project, settings) unless project[:settings]
170
- project_body = project[:value]
231
+ if File.directory?(path)
232
+ @logger.info "Loading from #{path}"
233
+ load_collection_from_path(path, false, true)
234
+ true
235
+ else
236
+ false
237
+ end
238
+ end
239
+
240
+ def download_yaml(url, file)
241
+ @remote_depends[url] = file
242
+ @logger.info "Downloading #{url} to #{file}.tar"
243
+ open("#{file}.tar", 'w') do |local_file|
244
+ open(url) do |remote_file|
245
+ local_file.write(Zlib::GzipReader.new(remote_file).read)
246
+ end
247
+ end
171
248
 
172
- # Process jobs
173
- jobs = project_body[:jobs] || []
249
+ # Extract Tar.gz to 'remote' folder
250
+ @logger.info "Unpacking #{file}.tar to #{file} folder"
251
+ Archive::Tar::Minitar.unpack("#{file}.tar", file)
252
+ end
253
+
254
+ def load_remote_yaml(dependencies)
255
+ ### Load remote YAML
256
+ # Download Tar.gz
257
+ dependencies.each do |source|
258
+ source = source[:source]
259
+ url = source[:url]
260
+ file = "remote-#{@remote_depends.length}"
261
+ if @remote_depends[url]
262
+ file = @remote_depends[url]
263
+ else
264
+ download_yaml(url, file)
265
+ end
266
+
267
+ path = File.expand_path(file, Dir.getwd)
268
+ # Load templates recursively
269
+ unless source[:templates]
270
+ @logger.info 'No specific template specified'
271
+ # Try to load the folder or the pipeline folder
272
+ path = File.join(path, 'pipeline') if Dir.entries(path).include? 'pipeline'
273
+ return load_collection_from_path(path)
274
+ end
275
+
276
+ load_templates(source[:templates])
277
+ end
278
+ end
279
+
280
+ def load_templates(templates)
281
+ templates.each do |template|
282
+ version = template[:version] || 'newest'
283
+ @logger.info "Loading #{template[:name]} at version #{version}"
284
+ # Move into the remote folder and look for the template folder
285
+ remote = Dir.entries(path)
286
+ if remote.include? template[:name]
287
+ # We found the template name, load this path
288
+ @logger.info 'We found the template!'
289
+ load_template(path, template)
290
+ else
291
+ # Many cases we must dig one layer deep
292
+ remote.each do |file|
293
+ load_template(File.join(path, file), template)
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ def cleanup_temp_remote
300
+ @remote_depends.each_value do |file|
301
+ FileUtils.rm_r file
302
+ FileUtils.rm_r "#{file}.tar"
303
+ end
304
+ end
305
+
306
+ def prepare_jobs(jobs)
174
307
  jobs.map! do |job|
175
308
  job.kind_of?(String) ? { job.to_sym => {} } : job
176
309
  end
177
- errors = {}
178
- @logger.info project
310
+ end
311
+
312
+ def process_job_changes(jobs)
179
313
  jobs.each do |job|
180
314
  job_id = job.keys.first
181
- settings = project[:settings].clone.merge(job[job_id])
182
- success, payload = resolve_job_by_name(job_id, settings)
183
- if success
184
- job[:result] = payload
185
- else
186
- errors[job_id] = payload
187
- end
315
+ j = get_item(job_id)
316
+ Utils.hash_merge!(j, job[job_id])
317
+ j[:value][:name] = j[:job_name] if j[:job_name]
188
318
  end
319
+ end
189
320
 
190
- # Process views
191
- views = project_body[:views] || []
321
+ def process_views(views, project, errors = {})
192
322
  views.map! do |view|
193
323
  view.kind_of?(String) ? { view.to_sym => {} } : view
194
324
  end
@@ -203,27 +333,55 @@ module JenkinsPipelineBuilder
203
333
  errors[view_id] = payload
204
334
  end
205
335
  end
336
+ errors
337
+ end
206
338
 
207
- errors.each do |k,v|
339
+ def process_jobs(jobs, project, errors = {})
340
+ jobs.each do |job|
341
+ job_id = job.keys.first
342
+ settings = project[:settings].clone.merge(job[job_id])
343
+ success, payload = resolve_job_by_name(job_id, settings)
344
+ if success
345
+ job[:result] = payload
346
+ else
347
+ errors[job_id] = payload
348
+ end
349
+ end
350
+ errors
351
+ end
352
+
353
+ def resolve_project(project)
354
+ defaults = get_item('global')
355
+ settings = defaults.nil? ? {} : defaults[:value] || {}
356
+
357
+ project[:settings] = Compiler.get_settings_bag(project, settings) unless project[:settings]
358
+ project_body = project[:value]
359
+
360
+ jobs = prepare_jobs(project_body[:jobs]) if project_body[:jobs]
361
+ @logger.info project
362
+ process_job_changes(jobs)
363
+ errors = process_jobs(jobs, project)
364
+ errors = process_view(project_body[:views], project, errors) if project_body[:views]
365
+
366
+ errors.each do |k, v|
208
367
  puts "Encountered errors processing: #{k}:"
209
368
  v.each do |key, error|
210
369
  puts " key: #{key} had the following error:"
211
370
  puts " #{error.inspect}"
212
371
  end
213
372
  end
214
- return false, "Encountered errors exiting" unless errors.empty?
373
+ return false, 'Encountered errors exiting' unless errors.empty?
215
374
 
216
- return true, project
375
+ [true, project]
217
376
  end
218
377
 
219
378
  def resolve_job_by_name(name, settings = {})
220
379
  job = get_item(name)
221
- raise "Failed to locate job by name '#{name}'" if job.nil?
380
+ fail "Failed to locate job by name '#{name}'" if job.nil?
222
381
  job_value = job[:value]
223
382
  @logger.debug "Compiling job #{name}"
224
-
225
- success, payload = Compiler.compile(job_value, settings)
226
- return success, payload
383
+ success, payload = Compiler.compile(job_value, settings, @job_collection)
384
+ [success, payload]
227
385
  end
228
386
 
229
387
  def projects
@@ -231,7 +389,7 @@ module JenkinsPipelineBuilder
231
389
  @job_collection.values.each do |item|
232
390
  result << item if item[:type] == :project
233
391
  end
234
- return result
392
+ result
235
393
  end
236
394
 
237
395
  def jobs
@@ -239,64 +397,96 @@ module JenkinsPipelineBuilder
239
397
  @job_collection.values.each do |item|
240
398
  result << item if item[:type] == :job
241
399
  end
242
- return result
400
+ result
401
+ end
402
+
403
+ def publish_project(project_name, errors = {})
404
+ projects.each do |project|
405
+ next if project_name && project[:name] == project_name
406
+ success, payload = resolve_project(project)
407
+ if success
408
+ puts 'successfully resolved project'
409
+ compiled_project = payload
410
+ else
411
+ puts payload
412
+ return false
413
+ end
414
+
415
+ if compiled_project[:value][:jobs]
416
+ errors = publish_jobs(compiled_project[:value][:jobs])
417
+ end
418
+ if compiled_project[:value][:views]
419
+ compiled_project[:value][:views].each do |v|
420
+ compiled_view = v[:result]
421
+ view.create(compiled_view)
422
+ end
423
+ end
424
+ end
425
+ errors
426
+ end
427
+
428
+ def publish_jobs(jobs, errors = {})
429
+ jobs.each do |i|
430
+ puts "Processing #{i}"
431
+ job = i[:result]
432
+ fail "Result is empty for #{i}" if job.nil?
433
+ success, payload = compile_job_to_xml(job)
434
+ if success
435
+ create_or_update(job, payload)
436
+ else
437
+ errors[job[:name]] = payload
438
+ end
439
+ end
440
+ errors
243
441
  end
244
442
 
245
443
  def bootstrap(path, project_name)
246
444
  @logger.info "Bootstrapping pipeline from path #{path}"
247
445
  load_collection_from_path(path)
248
-
446
+ cleanup_temp_remote
447
+ load_extensions(path)
249
448
  errors = {}
250
449
  # Publish all the jobs if the projects are not found
251
450
  if projects.count == 0
252
- jobs.each do |i|
253
- job = i[:value]
254
- success, payload = compile_job_to_xml(job)
255
- if success
256
- create_or_update(job, payload)
257
- else
258
- errors[job[:name]] = payload
259
- end
260
- end
451
+ errors = publish_jobs(jobs)
261
452
  else
262
- projects.each do |project|
263
- success, payload = resolve_project(project)
264
- if payload[:value][:name] == project_name || project_name == nil # If we specify a project name, only use that project
265
- if success
266
- puts 'successfully resolved project'
267
- compiled_project = payload
268
- else
269
- puts payload
270
- return false
271
- end
272
-
273
- if compiled_project[:value][:jobs]
274
- compiled_project[:value][:jobs].each do |i|
275
- puts "Processing #{i}"
276
- job = i[:result]
277
- fail "Result is empty for #{i}" if job.nil?
278
- success, payload = compile_job_to_xml(job)
279
- if success
280
- create_or_update(job, payload)
281
- else
282
- errors[job[:name]] = payload
283
- end
284
- end
285
- end
453
+ errors = publish_project(project_name)
454
+ end
455
+ errors.each do |k, v|
456
+ @logger.error "Encountered errors compiling: #{k}:"
457
+ @logger.error v
458
+ end
459
+ end
286
460
 
287
- if compiled_project[:value][:views]
288
- compiled_project[:value][:views].each do |v|
289
- _view = v[:result]
290
- view.create(_view)
291
- end
461
+ def pull_request(path, project_name)
462
+ @logger.info "Pull Request Generator Running from path #{path}"
463
+ load_collection_from_path(path)
464
+ cleanup_temp_remote
465
+ load_extensions(path)
466
+ jobs = {}
467
+ projects.each do |project|
468
+ if project[:name] == project_name || project_name.nil?
469
+ project_body = project[:value]
470
+ project_jobs = project_body[:jobs] || []
471
+ @logger.info "Using Project #{project}"
472
+ pull_job = nil
473
+ project_jobs.each do |job|
474
+ job = @job_collection[job.to_s]
475
+ pull_job = job if job[:value][:job_type] == 'pull_request_generator'
476
+ end
477
+ fail 'No Pull Request Found for Project' unless pull_job
478
+ pull_jobs = pull_job[:value][:jobs] || []
479
+ pull_jobs.each do |job|
480
+ if job.is_a? String
481
+ jobs[job.to_s] = @job_collection[job.to_s]
482
+ else
483
+ jobs[job.keys[0].to_s] = @job_collection[job.keys[0].to_s]
292
484
  end
293
485
  end
486
+ pull = JenkinsPipelineBuilder::PullRequestGenerator.new(self)
487
+ pull.run(project, jobs, pull_job)
294
488
  end
295
489
  end
296
- errors.each do |k,v|
297
- @logger.error "Encountered errors compiling: #{k}:"
298
- @logger.error v
299
- end
300
490
  end
301
491
 
302
492
  def dump(job_name)
@@ -323,40 +513,40 @@ module JenkinsPipelineBuilder
323
513
  end
324
514
 
325
515
  def compile_job_to_xml(job)
326
- raise 'Job name is not specified' unless job[:name]
516
+ fail 'Job name is not specified' unless job[:name]
327
517
 
328
518
  @logger.info "Creating Yaml Job #{job}"
329
519
  job[:job_type] = 'free_style' unless job[:job_type]
330
520
  case job[:job_type]
331
- when 'job_dsl'
332
- xml = compile_freestyle_job_to_xml(job)
333
- payload = update_job_dsl(job, xml)
334
- when 'multi_project'
335
- xml = compile_freestyle_job_to_xml(job)
336
- payload = adjust_multi_project xml
337
- when 'build_flow'
338
- xml = compile_freestyle_job_to_xml(job)
339
- payload = add_job_dsl(job, xml)
340
- when 'free_style'
341
- payload = compile_freestyle_job_to_xml job
342
- else
343
- return false, "Job type: #{job[:job_type]} is not one of job_dsl, multi_project, build_flow or free_style"
521
+ when 'job_dsl'
522
+ xml = compile_freestyle_job_to_xml(job)
523
+ payload = update_job_dsl(job, xml)
524
+ when 'multi_project'
525
+ xml = compile_freestyle_job_to_xml(job)
526
+ payload = adjust_multi_project xml
527
+ when 'build_flow'
528
+ xml = compile_freestyle_job_to_xml(job)
529
+ payload = add_job_dsl(job, xml)
530
+ when 'free_style', 'pull_request_generator'
531
+ payload = compile_freestyle_job_to_xml job
532
+ else
533
+ return false, "Job type: #{job[:job_type]} is not one of job_dsl, multi_project, build_flow or free_style"
344
534
  end
345
535
 
346
- return true, payload
536
+ [true, payload]
347
537
  end
348
538
 
349
539
  def adjust_multi_project(xml)
350
540
  n_xml = Nokogiri::XML(xml)
351
- root = n_xml.root()
541
+ root = n_xml.root
352
542
  root.name = 'com.tikal.jenkins.plugins.multijob.MultiJobProject'
353
543
  n_xml.to_xml
354
544
  end
355
545
 
356
546
  def compile_freestyle_job_to_xml(params)
357
- if params.has_key?(:template)
547
+ if params.key?(:template)
358
548
  template_name = params[:template]
359
- raise "Job template '#{template_name}' can't be resolved." unless @job_templates.has_key?(template_name)
549
+ fail "Job template '#{template_name}' can't be resolved." unless @job_templates.key?(template_name)
360
550
  params.delete(:template)
361
551
  template = @job_templates[template_name]
362
552
  puts "Template found: #{template}"
@@ -373,7 +563,7 @@ module JenkinsPipelineBuilder
373
563
  end
374
564
 
375
565
  def add_job_dsl(job, xml)
376
- n_xml = Nokogiri::XML(xml)
566
+ n_xml = Nokogiri::XML(xml)
377
567
  n_xml.root.name = 'com.cloudbees.plugins.flow.BuildFlow'
378
568
  Nokogiri::XML::Builder.with(n_xml.root) do |xml|
379
569
  xml.dsl job[:build_flow]
@@ -392,25 +582,25 @@ module JenkinsPipelineBuilder
392
582
  end
393
583
 
394
584
  def generate_job_dsl_body(params)
395
- @logger.info "Generating pipeline"
585
+ @logger.info 'Generating pipeline'
396
586
 
397
587
  xml = @client.job.build_freestyle_config(params)
398
588
 
399
589
  n_xml = Nokogiri::XML(xml)
400
590
  if n_xml.xpath('//javaposse.jobdsl.plugin.ExecuteDslScripts').empty?
401
- p_xml = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |b_xml|
591
+ p_xml = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |b_xml|
402
592
  build_job_dsl(params, b_xml)
403
593
  end
404
594
 
405
- n_xml.xpath('//builders').first.add_child("\r\n" + p_xml.doc.root.to_xml(:indent => 4) + "\r\n")
595
+ n_xml.xpath('//builders').first.add_child("\r\n" + p_xml.doc.root.to_xml(indent: 4) + "\r\n")
406
596
  xml = n_xml.to_xml
407
597
  end
408
598
  xml
409
599
  end
410
600
 
411
601
  def build_job_dsl(job, xml)
412
- xml.send('javaposse.jobdsl.plugin.ExecuteDslScripts') {
413
- if job.has_key?(:job_dsl)
602
+ xml.send('javaposse.jobdsl.plugin.ExecuteDslScripts') do
603
+ if job.key?(:job_dsl)
414
604
  xml.scriptText job[:job_dsl]
415
605
  xml.usingScriptText true
416
606
  else
@@ -419,7 +609,7 @@ module JenkinsPipelineBuilder
419
609
  end
420
610
  xml.ignoreExisting false
421
611
  xml.removedJobAction 'IGNORE'
422
- }
612
+ end
423
613
  end
424
614
  end
425
615
  end