cicd-builder 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ cicd-builder
2
+ ============
3
+
4
+ Continuous Integration/ Continuous Deployment builder helper
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler'
7
+ rescue LoadError => e
8
+ warn e.message
9
+ warn "Run `gem install bundler` to install Bundler."
10
+ exit -1
11
+ end
12
+
13
+ begin
14
+ Bundler.setup(:development)
15
+ rescue Bundler::BundlerError => e
16
+ warn e.message
17
+ warn "Run `bundle install` to install missing gems."
18
+ exit e.status_code
19
+ end
20
+
21
+ require 'rake'
22
+
23
+ require 'rubygems/tasks'
24
+ Gem::Tasks.new
25
+
26
+ require 'cucumber/rake/task'
27
+
28
+ Cucumber::Rake::Task.new do |t|
29
+ t.cucumber_opts = %w[--format pretty]
30
+ end
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/cicd/builder/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'cicd-builder'
7
+ gem.version = CiCd::Builder::VERSION
8
+ gem.summary = 'Jenkins builder task for CI/CD'
9
+ gem.description = 'Jenkins builder task for CI/CD'
10
+ gem.license = 'Apachev2'
11
+ gem.authors = ['Christo De Lange']
12
+ gem.email = 'rubygems@dldinternet.com'
13
+ gem.homepage = 'https://rubygems.org/gems/cicd-builder'
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency 'awesome_print', ">= 0.0.0"
21
+ gem.add_dependency 'inifile', '>= 0.0.0'
22
+ gem.add_dependency 'logging', '>= 0.0.0'
23
+ gem.add_dependency 'json', '= 1.7.7'
24
+ gem.add_dependency 'chef', '>= 11.8.2'
25
+ gem.add_dependency 'aws-sdk', '>= 0.0.0'
26
+ gem.add_dependency 'yajl-ruby', '>= 0.0.0'
27
+ gem.add_dependency 'git', '>= 1.2.7'
28
+
29
+ gem.add_development_dependency 'bundler', '~> 1.0'
30
+ gem.add_development_dependency 'rake', '~> 10.3'
31
+ gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
32
+ gem.add_development_dependency 'cucumber'
33
+ end
data/features/.gitkeep ADDED
File without changes
@@ -0,0 +1 @@
1
+ Feature: Blah blah blah
@@ -0,0 +1 @@
1
+ Feature: Blah blah blah
File without changes
@@ -0,0 +1,87 @@
1
+ require 'cicd/builder/version'
2
+ module CiCd
3
+ module Builder
4
+
5
+ require 'awesome_print'
6
+ require 'optparse'
7
+ require 'inifile'
8
+ require 'logging'
9
+ require 'net/http'
10
+ require 'uri'
11
+ require 'fileutils'
12
+ require 'digest'
13
+ require 'yajl/json_gem'
14
+ require 'aws-sdk'
15
+
16
+ _lib=File.dirname(__FILE__)
17
+ $:.unshift(_lib) unless $:.include?(_lib)
18
+
19
+ require 'cicd/builder/version'
20
+ require 'cicd/builder/mixlib/constants'
21
+
22
+ #noinspection ALL
23
+ class BuilderBase
24
+ attr_accessor :default_options
25
+ attr_accessor :options
26
+ attr_accessor :logger
27
+ attr_accessor :vars
28
+
29
+ def initialize()
30
+ @vars = {
31
+ return_code: -1
32
+ }
33
+ @default_options = {
34
+ builder: ::CiCd::Builder::VERSION,
35
+ env_keys: %w(JENKINS_HOME BUILD_NUMBER JOB_NAME)
36
+ }
37
+ end
38
+
39
+ require 'cicd/builder/mixlib/errors'
40
+ require 'cicd/builder/mixlib/utils'
41
+ require 'cicd/builder/mixlib/options'
42
+ require 'cicd/builder/mixlib/environment'
43
+ require 'cicd/builder/mixlib/repo'
44
+ require 'cicd/builder/mixlib/build'
45
+
46
+ # ---------------------------------------------------------------------------------------------------------------
47
+ def getBuilderVersion
48
+ {
49
+ version: VERSION,
50
+ major: MAJOR,
51
+ minor: MINOR,
52
+ patch: PATCH,
53
+ }
54
+ end
55
+
56
+ # ---------------------------------------------------------------------------------------------------------------
57
+ def run()
58
+ $stdout.write("CiCd::Builder v#{@VERSION}\n")
59
+ parseOptions()
60
+
61
+ ret = checkEnvironment()
62
+ if 0 == ret
63
+ ret = getVars()
64
+ if 0 == ret
65
+ ret = prepareBuild()
66
+ if 0 == ret
67
+ ret = makeBuild()
68
+ if 0 == ret
69
+ ret = saveBuild()
70
+ if 0 == ret
71
+ ret = uploadBuildArtifacts()
72
+ if 0 == ret
73
+ # noop
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ @vars[:return_code]
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,160 @@
1
+ require 'json'
2
+
3
+ module CiCd
4
+ module Builder
5
+
6
+ # ---------------------------------------------------------------------------------------------------------------
7
+ def cleanupBuild()
8
+ [ :build_pkg, :build_chk, :build_mdf, :build_mff ].each do |fil|
9
+ if File.exists?(@vars[fil])
10
+ begin
11
+ FileUtils.rm_f(@vars[fil])
12
+ rescue => e
13
+ @logger.error e.to_s
14
+ #raise e
15
+ return -96
16
+ end
17
+ end
18
+ end
19
+ if Dir.exists?(@vars[:build_dir])
20
+ begin
21
+ FileUtils.rm_r(@vars[:build_dir])
22
+ rescue => e
23
+ @logger.error e.to_s
24
+ #raise e
25
+ return -95
26
+ end
27
+ end
28
+ 0
29
+ end
30
+
31
+ # ---------------------------------------------------------------------------------------------------------------
32
+ def prepareBuild()
33
+ meta = {}
34
+ %w[ WORKSPACE PROJECT_NAME VERSION RELEASE ].each do |e|
35
+ unless ENV.has_key?(e)
36
+ raise "#{e} environment variable is required"
37
+ end
38
+ end
39
+ meta[:Version] = ENV['VERSION']
40
+ meta[:Release] = ENV['RELEASE']
41
+
42
+ place = ''
43
+ begin
44
+ place = 'require "git"'
45
+ eval place
46
+
47
+ # Assuming we are in the workspace ...
48
+ place = "Git.open('#{ENV['WORKSPACE']}')"
49
+ git = Git.open(ENV['WORKSPACE'], :log => @logger)
50
+ place = 'git.log'
51
+ meta[:Commit] = git.log[0].sha
52
+ place = 'git.current_branch'
53
+ meta[:Branch] = git.current_branch
54
+
55
+ @vars[:build_bra] = meta[:Branch].gsub(%r([/|]),'.')
56
+ @vars[:build_ver] = "#{meta[:Version]}"
57
+ @vars[:build_vrb] = "#{@vars[:build_ver]}-release-#{meta[:Release]}-#{@vars[:build_bra]}-#{@vars[:variant]}" #
58
+ @vars[:build_nam] = "#{@vars[:project_name]}-#{@vars[:build_vrb]}"
59
+ @vars[:build_rel] = "#{@vars[:build_nam]}-build-#{@vars[:build_num]}"
60
+ @vars[:build_dir] = "#{ENV['WORKSPACE']}/#{@vars[:build_rel]}"
61
+ @vars[:latest_pkg]= "#{@vars[:build_store]}/#{@vars[:build_rel]}.tar.gz"
62
+ @vars[:build_pkg] = "#{@vars[:build_rel]}.tar.gz"
63
+ @vars[:build_chk] = "#{@vars[:build_rel]}.checksum"
64
+ @vars[:build_mff] = "#{@vars[:build_rel]}.manifest"
65
+ @vars[:build_mdf] = "#{@vars[:build_rel]}.meta"
66
+ @vars[:build_mdd] = meta.dup
67
+ #noinspection RubyArgCount
68
+ @vars[:build_mds] = Digest::SHA256.hexdigest(meta.to_s)
69
+
70
+ @vars[:return_code] = 0
71
+
72
+ rescue Exception => e
73
+ @logger.error "#{e.class}:: '#{place}' - #{e.message}"
74
+ @vars[:return_code] = -98
75
+ end
76
+
77
+ @vars[:return_code]
78
+ end
79
+
80
+ # ---------------------------------------------------------------------------------------------------------------
81
+ def makeBuild()
82
+ if @vars.has_key?(:build_dir) and @vars.has_key?(:build_pkg)
83
+ begin
84
+ do_build = false
85
+ if File.exists?(@vars[:build_chk])
86
+ @vars[:build_sha] = IO.readlines(@vars[:build_chk])
87
+ unless @vars[:build_sha].is_a?(Array)
88
+ @logger.error "Unable to parse build checksum from #{@vars[:build_chk]}"
89
+ return -97
90
+ end
91
+ @vars[:build_sha] = @vars[:build_sha][0].chomp()
92
+ else
93
+ @vars[:build_sha] = ''
94
+ do_build = true
95
+ end
96
+ unless File.exists?(@vars[:build_pkg])
97
+ do_build = true
98
+ end
99
+ if do_build
100
+ @vars[:return_code] = cleanupBuild()
101
+ return @vars[:return_code] unless @vars[:return_code] == 0
102
+ @vars[:build_dte] = DateTime.now.strftime("%F %T%:z")
103
+ createMetaData()
104
+ @vars[:return_code] = packageBuild()
105
+ if 0 == @vars[:return_code]
106
+ @vars[:check_sha] = @vars[:build_sha]
107
+ @vars[:build_sha] = Digest::SHA256.file(@vars[:build_pkg]).hexdigest()
108
+ IO.write(@vars[:build_chk], @vars[:build_sha])
109
+ end
110
+ reportStatus()
111
+ reportResult()
112
+ else
113
+ reportStatus()
114
+
115
+ # No need to build again :)
116
+ @logger.info "NO_CHANGE: #{ENV['JOB_NAME']} #{ENV['BUILD_NUMBER']} #{@vars[:build_nam]} #{@vars[:build_pkg]} #{@vars[:build_chk]} [#{@vars[:build_sha]}]"
117
+ @vars[:return_code] = 0
118
+ return 1
119
+ end
120
+ rescue => e
121
+ @logger.error "#{e.class.name} #{e.message}"
122
+ @vars[:return_code] = -99
123
+ end
124
+ else
125
+ @logger.error ":build_dir or :build_pkg is unknown"
126
+ @vars[:return_code] = 2
127
+ end
128
+ @vars[:return_code]
129
+ end
130
+
131
+ # ---------------------------------------------------------------------------------------------------------------
132
+ def packageBuild()
133
+ excludes=%w(*.iml *.txt *.sh *.md .gitignore .editorconfig .jshintrc *.deprecated adminer doc)
134
+ excludes = excludes.map{ |e| "--exclude=#{@vars[:build_nam]}/#{e}" }.join(' ')
135
+ cmd = %(cd #{ENV['WORKSPACE']}; tar zcvf #{@vars[:build_pkg]} #{excludes} #{@vars[:build_nam]} 1>#{@vars[:build_pkg]}.manifest)
136
+ @logger.info cmd
137
+ logger_info = %x(#{cmd})
138
+ ret = $?.exitstatus
139
+ @logger.info logger_info
140
+ FileUtils.rmtree(@vars[:build_dir])
141
+ ret
142
+ end
143
+
144
+ # ---------------------------------------------------------------------------------------------------------------
145
+ def createMetaData()
146
+ @vars[:build_mdd].merge!({
147
+ :Generation => @options[:gen],
148
+ :Project => @vars[:project_name],
149
+ :Variant => @vars[:variant],
150
+ :Build => @vars[:build_num],
151
+ :Date => @vars[:build_dte],
152
+ :Builder => VERSION,
153
+
154
+ })
155
+ json = JSON.pretty_generate( @vars[:build_mdd], { indent: "\t", space: ' '})
156
+ IO.write(@vars[:build_mdf], json)
157
+ end
158
+
159
+ end
160
+ end
@@ -0,0 +1,18 @@
1
+ module CiCd
2
+ module Builder
3
+ #noinspection RubyStringKeysInHashInspection
4
+ LOGLEVELS = {
5
+ 'crit' => :fatal,
6
+ 'critical' => :fatal,
7
+ 'err' => :error,
8
+ 'error' => :error,
9
+ 'warn' => :warn,
10
+ 'warning' => :warn,
11
+ 'info' => :info,
12
+ 'debug' => :debug,
13
+ }
14
+
15
+ MYNAME = File.basename(__FILE__)
16
+
17
+ end
18
+ end
@@ -0,0 +1,194 @@
1
+ module CiCd
2
+ module Builder
3
+ require 'awesome_print'
4
+
5
+ # ---------------------------------------------------------------------------------------------------------------
6
+ def checkEnvironment()
7
+ # [2013-12-30 Christo] Detect CI ...
8
+ unless ENV.has_key?('JENKINS_HOME')
9
+ puts 'Sorry, your CI environment is not supported at this time (2013-12-30) ... Christo De Lange'
10
+ puts 'This script is developed for Jenkins so either you are not using Jenkins or you ran me outside of the CI ecosystem ...'
11
+ return 99
12
+ end
13
+
14
+ # Check for the necessary environment variables
15
+ map_keys = {}
16
+
17
+ @options[:env_keys].each { |k|
18
+ map_keys[k]= (not ENV.has_key?(k))
19
+ }
20
+ missing = map_keys.keys.select{ |k| map_keys[k] }
21
+
22
+ if missing.count() > 0
23
+ ap missing
24
+ raise Exception.new("Need environment variables: #{missing}")
25
+ end
26
+ 0
27
+ end
28
+
29
+ # ---------------------------------------------------------------------------------------------------------------
30
+ def getVars()
31
+ @vars ||= {}
32
+ @vars[:release] = 'latest'
33
+ @vars[:build_store] = '/tmp'
34
+ @vars[:variant] = 'SNAPSHOT'
35
+
36
+ if ENV.has_key?('PROJECT_NAME')
37
+ @vars[:project_name] = ENV['PROJECT_NAME']
38
+ end
39
+
40
+ if ENV.has_key?('RELEASE')
41
+ @vars[:release] = ENV['RELEASE']
42
+ end
43
+
44
+ if ENV.has_key?('BUILD_STORE')
45
+ @vars[:build_store] = "#{ENV['BUILD_STORE']}"
46
+ end
47
+
48
+ if ENV.has_key?('VARIANT')
49
+ @vars[:variant] = "#{ENV['VARIANT']}"
50
+ end
51
+
52
+ if ENV.has_key?('BUILD_NUMBER')
53
+ @vars[:build_num] = "#{ENV['BUILD_NUMBER']}"
54
+ end
55
+
56
+ @vars[:return_code] = getLatest()
57
+ end
58
+
59
+ def getLatest
60
+ ret = 0
61
+ @vars[:vars_fil] = "#{@vars[:build_store]}/#{ENV['JOB_NAME']}-#{@vars[:variant]}.env"
62
+ @vars[:latest_fil] = "#{@vars[:build_store]}/#{ENV['JOB_NAME']}-#{@vars[:variant]}.latest"
63
+ @vars[:latest_ver] = ''
64
+ @vars[:latest_sha] = ''
65
+ @vars[:latest_pkg] = ''
66
+ if @vars[:build_nam]
67
+ @vars[:latest_pkg]= "#{@vars[:build_store]}/#{@vars[:build_nam]}.tar.gz"
68
+ end
69
+ if File.exists?(@vars[:latest_fil])
70
+ @vars[:latest_ver] = IO.readlines(@vars[:latest_fil])
71
+ unless @vars[:latest_ver].is_a?(Array)
72
+ @logger.error "Unable to parse latest version from #{@vars[:latest_fil]}"
73
+ ret = -97
74
+ end
75
+ @vars[:latest_sha] = @vars[:latest_ver][1].chomp() if (@vars[:latest_ver].length > 1)
76
+ @vars[:latest_ver] = @vars[:latest_ver][0].chomp()
77
+ end
78
+ ret
79
+ end
80
+
81
+ # ---------------------------------------------------------------------------------------------------------------
82
+ def saveEnvironment(ignored=[])
83
+ @logger.info "Save environment to #{@vars[:vars_fil]}"
84
+ vstr = ['[global]']
85
+ ENV.to_hash.sort.each{|k,v|
86
+ vstr << %(#{k}="#{v}") unless ignored.include?(k)
87
+ }
88
+
89
+ IO.write(@vars[:vars_fil], vstr.join("\n"))
90
+ end
91
+
92
+ # ---------------------------------------------------------------------------------------------------------------
93
+ def saveBuild()
94
+ begin
95
+ raise 'ERROR: Checksum not read' unless @vars.has_key?(:latest_sha)
96
+ raise 'ERROR: Checksum not calculated' unless @vars.has_key?(:build_sha)
97
+ change = false
98
+ if @vars[:latest_sha] != @vars[:build_sha]
99
+ change = true
100
+ @logger.info "CHANGE: Checksum [#{@vars[:latest_sha]}] => [#{@vars[:build_sha]}]"
101
+ end
102
+ if @vars[:latest_ver] != @vars[:build_ver]
103
+ change = true
104
+ @logger.info "CHANGE: Release [#{@vars[:latest_ver]}] => [#{@vars[:build_ver]}]"
105
+ end
106
+ unless File.file?(@vars[:build_pkg])
107
+ change = true
108
+ @logger.info "CHANGE: No #{@vars[:build_pkg]}"
109
+ end
110
+ unless File.symlink?(@vars[:latest_pkg])
111
+ change = true
112
+ @logger.info "CHANGE: No #{@vars[:latest_pkg]}"
113
+ end
114
+
115
+ if change
116
+ if @vars[:latest_pkg] != @vars[:build_pkg]
117
+ @logger.info "Link #{@vars[:latest_pkg]} to #{@vars[:build_pkg]}"
118
+ begin
119
+ File.unlink(@vars[:latest_pkg])
120
+ rescue
121
+ # noop
122
+ end
123
+ File.symlink(@vars[:build_pkg], @vars[:latest_pkg])
124
+ end
125
+ @logger.info "Save latest build info to #{@vars[:latest_fil]}"
126
+ IO.write(@vars[:latest_fil], "#{@vars[:build_ver]}\n#{@vars[:build_sha]}")
127
+ saveEnvironment(['LS_COLORS','AWS_ACCESS_KEY_ID','AWS_SECRET_ACCESS_KEY'])
128
+ # NOTE the '.note'!
129
+ @logger.note "CHANGE: #{ENV['JOB_NAME']} (#{@vars[:build_ver]}[#{@vars[:build_sha]}])"
130
+
131
+ else
132
+ @logger.info "Artifact #{@vars[:latest_pkg]} unchanged (#{@vars[:latest_ver]} [#{@vars[:latest_sha]}])"
133
+ @logger.info "NO_CHANGE: #{ENV['JOB_NAME']} #{@vars[:latest_ver]}"
134
+ end
135
+ @vars[:return_code] = 0
136
+ rescue => e
137
+ @logger.error "#{e.backtrace[0]}: #{e.class.name} #{e.message}"
138
+ @vars[:return_code] = 2
139
+ end
140
+ @vars[:return_code]
141
+ end
142
+
143
+ def reportResult()
144
+ if 0 == @vars[:return_code]
145
+ # NOTE the '.note'!
146
+ @logger.note "CHANGE: #{ENV['JOB_NAME']} #{ENV['BUILD_NUMBER']} #{@vars[:build_nam]} (#{@vars[:build_pkg]}) [#{@vars[:check_sha]}] => [#{@vars[:build_sha]}]"
147
+ else
148
+ @logger.error "FAILURE: #{ENV['JOB_NAME']} #{ENV['BUILD_NUMBER']} #{@vars[:build_pkg]} #{@vars[:return_code]}"
149
+ end
150
+ end
151
+
152
+ # ---------------------------------------------------------------------------------------------------------------
153
+ def reportStatus(ignored=[])
154
+ # [2013-12-30 Christo] Report status,environment, etc.
155
+
156
+ if @logger.level < ::Logging::LEVELS['warn']
157
+ @logger.info '='*100
158
+ @logger.info Dir.getwd()
159
+ @logger.info '='*100
160
+
161
+ @logger.info "Config:"
162
+ @options.each{|k,v|
163
+ unless ignored.include?(k)
164
+ @logger.info sprintf("%25s: %s", "#{k.to_s}", "#{v.to_s}")
165
+ end
166
+ }
167
+
168
+ @logger.info '='*100
169
+
170
+ @logger.info "Parameters:"
171
+ @vars.sort.each{|k,v|
172
+ unless ignored.include?(k)
173
+ @logger.info sprintf("%25s: %s", "#{k.to_s}", "#{v.to_s}")
174
+ end
175
+ }
176
+
177
+ @logger.info '='*100
178
+ end
179
+
180
+ if @logger.level < ::Logging::LEVELS['info']
181
+ @logger.debug '='*100
182
+ @logger.debug "Environment:"
183
+ ENV.sort.each{|k,v|
184
+ unless ignored.include?(k)
185
+ @logger.debug sprintf("%25s: %s", "#{k.to_s}", "#{v.to_s}")
186
+ end
187
+ }
188
+
189
+ @logger.debug '='*100
190
+ end
191
+ end
192
+
193
+ end
194
+ end