release_manager 0.3.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.bash_profile +27 -0
  3. data/.dockerignore +1 -0
  4. data/.env +3 -0
  5. data/.gitignore +13 -0
  6. data/.rspec +2 -0
  7. data/CHANGELOG.md +55 -0
  8. data/Dockerfile +14 -0
  9. data/Gemfile +14 -0
  10. data/Gemfile.lock +72 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +311 -0
  13. data/Rakefile +6 -0
  14. data/app_startup_script.sh +4 -0
  15. data/bin/console +14 -0
  16. data/bin/setup +8 -0
  17. data/docker-compose.yml +37 -0
  18. data/exe/bump-changelog +5 -0
  19. data/exe/deploy-mod +5 -0
  20. data/exe/release-mod +5 -0
  21. data/exe/sandbox-create +13 -0
  22. data/lib/release_manager.rb +33 -0
  23. data/lib/release_manager/changelog.rb +130 -0
  24. data/lib/release_manager/cli/deploy_mod_cli.rb +44 -0
  25. data/lib/release_manager/cli/release_mod_cli.rb +43 -0
  26. data/lib/release_manager/cli/sandbox_create_cli.rb +138 -0
  27. data/lib/release_manager/control_mod.rb +83 -0
  28. data/lib/release_manager/control_repo.rb +35 -0
  29. data/lib/release_manager/errors.rb +13 -0
  30. data/lib/release_manager/git/credentials.rb +98 -0
  31. data/lib/release_manager/git/utilites.rb +263 -0
  32. data/lib/release_manager/logger.rb +52 -0
  33. data/lib/release_manager/module_deployer.rb +77 -0
  34. data/lib/release_manager/puppet_module.rb +211 -0
  35. data/lib/release_manager/puppetfile.rb +148 -0
  36. data/lib/release_manager/release.rb +174 -0
  37. data/lib/release_manager/sandbox.rb +272 -0
  38. data/lib/release_manager/vcs_manager.rb +22 -0
  39. data/lib/release_manager/vcs_manager/gitlab_adapter.rb +112 -0
  40. data/lib/release_manager/vcs_manager/vcs_adapter.rb +22 -0
  41. data/lib/release_manager/version.rb +3 -0
  42. data/lib/release_manager/workflow_action.rb +5 -0
  43. data/release_manager.gemspec +38 -0
  44. data/setup_repos.rb +95 -0
  45. metadata +175 -0
@@ -0,0 +1,52 @@
1
+ require 'logger'
2
+
3
+ module ReleaseManager
4
+ module Logger
5
+ def logger
6
+ unless @logger
7
+ @logger = ::Logger.new(STDOUT)
8
+ @logger.level = log_level
9
+ @logger.progname = 'ReleaseManager'
10
+ @logger.formatter = proc do |severity, datetime, progname, msg|
11
+ "#{severity} - #{progname}: #{msg}\n".send(color(severity))
12
+ end
13
+ end
14
+ @logger
15
+ end
16
+
17
+ def color(severity)
18
+ case severity
19
+ when ::Logger::Severity::WARN, 'WARN'
20
+ :yellow
21
+ when ::Logger::Severity::INFO, 'INFO'
22
+ :green
23
+ when ::Logger::Severity::FATAL, 'FATAL'
24
+ :fatal
25
+ when ::Logger::Severity::ERROR, 'ERROR'
26
+ :fatal
27
+ when ::Logger::Severity::DEBUG, 'DEBUG'
28
+ :green
29
+ else
30
+ :green
31
+ end
32
+ end
33
+
34
+ def log_level
35
+ level = ENV['LOG_LEVEL'].downcase if ENV['LOG_LEVEL']
36
+ case level
37
+ when 'warn'
38
+ ::Logger::Severity::WARN
39
+ when 'fatal'
40
+ ::Logger::Severity::FATAL
41
+ when 'debug'
42
+ ::Logger::Severity::DEBUG
43
+ when 'info'
44
+ ::Logger::Severity::INFO
45
+ when 'error'
46
+ ::Logger::Severity::ERROR
47
+ else
48
+ ::Logger::Severity::INFO
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'puppetfile'
3
+ require_relative 'puppet_module'
4
+ class ModuleDeployer
5
+ attr_reader :options
6
+
7
+ def initialize(opts)
8
+ opts[:modulepath] = Dir.getwd if opts[:modulepath].nil?
9
+ @options = opts
10
+ end
11
+
12
+ def puppetfile_path
13
+ @puppetfile_path ||= options[:puppetfile] || ENV['PUPPET_FILE_PATH'] || File.expand_path("~/repos/r10k-control/Puppetfile")
14
+ end
15
+
16
+ def mod_path
17
+ @mod_path ||= options[:modulepath] || ENV['MOD_DIR'] || File.expand_path(Dir.getwd)
18
+ end
19
+
20
+ def puppet_module
21
+ @puppet_module ||= PuppetModule.new(mod_path)
22
+ end
23
+
24
+ def latest_version
25
+ puppet_module.latest_tag
26
+ end
27
+
28
+ def check_requirements
29
+ raise PuppetfileNotFoundException unless File.exists?(puppetfile_path)
30
+ raise ModNotFoundException if !mod_path || ! File.exists?(mod_path)
31
+ end
32
+
33
+ def control_repo_remote
34
+ @control_repo_remote ||= options[:remote] || puppetfile.source
35
+ end
36
+
37
+ def puppetfile
38
+ @puppetfile ||= Puppetfile.new(puppetfile_path)
39
+ end
40
+
41
+ def run
42
+ begin
43
+ check_requirements
44
+ puts "Found module #{puppet_module.name} with version: #{latest_version}".green
45
+ if options[:dry_run]
46
+ puts "Would have updated module #{puppet_module.name} in Puppetfile to version: #{latest_version}".green
47
+ puts "Would have committed with message: bump #{puppet_module.name} to version: #{latest_version}".green if options[:commit]
48
+ puts "Would have just pushed branch: #{puppetfile.current_branch} to remote: #{control_repo_remote}".green if options[:push]
49
+ else
50
+ puts "Updated module #{puppet_module.name} in Puppetfile to version: #{latest_version}".green
51
+ puppetfile.write_version(puppet_module.name, latest_version)
52
+ puppetfile.write_source(puppet_module.name, puppet_module.source)
53
+ puppetfile.write_to_file
54
+ if options[:commit]
55
+ puppetfile.commit("bump #{puppet_module.name} to version #{latest_version}")
56
+ puts "Commited with message: bump #{puppet_module.name} to version #{latest_version}".green
57
+ end
58
+ if options[:push]
59
+ puppetfile.push(control_repo_remote, puppetfile.current_branch)
60
+ puts "Just pushed branch: #{puppetfile.current_branch} to remote: #{control_repo_remote}".green
61
+ end
62
+ end
63
+ rescue InvalidMetadataSource
64
+ puts "The puppet module's metadata.json source field must be a git url: ie. git@someserver.com:devops/module.git".red
65
+ rescue PuppetfileNotFoundException
66
+ puts "Cannot find the puppetfile at #{puppetfile_path}".red
67
+ exit -1
68
+ rescue InvalidModuleNameException => e
69
+ puts e.message
70
+ exit 1
71
+ rescue ModNotFoundException
72
+ puts "Invalid module path for #{mod_path}".red
73
+ puts "This means that the metadata.json name field does not match\nthe module name found in the Puppetfile or this is not a puppet module".fatal
74
+ exit -1
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,211 @@
1
+ require 'json'
2
+ require 'release_manager/errors'
3
+ require 'release_manager/workflow_action'
4
+ require 'release_manager/git/utilites'
5
+ require 'rugged'
6
+
7
+
8
+ class PuppetModule < WorkflowAction
9
+ attr_reader :name, :metadata_file, :path, :version, :upstream
10
+ attr_writer :version, :source
11
+
12
+ include ReleaseManager::Git::Utilities
13
+ include ReleaseManager::Logger
14
+
15
+ def initialize(mod_path, upstream = nil)
16
+ raise ModNotFoundException if mod_path.nil?
17
+ @path = mod_path
18
+ @upstream = upstream
19
+ @metadata_file = File.join(mod_path, 'metadata.json')
20
+ end
21
+
22
+ def repo
23
+ @repo ||= Rugged::Repository.new(path)
24
+ end
25
+
26
+ def self.check_requirements(path)
27
+ pm = new(path)
28
+ raise InvalidMetadataSource if pm.source !~ /\Agit\@/
29
+ raise UpstreamSourceMatch unless pm.git_upstream_set?
30
+ end
31
+
32
+ def name
33
+ namespaced_name.split(/\/|\-/).last
34
+ end
35
+
36
+ def namespaced_name
37
+ metadata['name']
38
+ end
39
+
40
+ # @returns [Hash] the metadata object as a ruby hash
41
+ def metadata
42
+ unless @metadata
43
+ raise ModNotFoundException unless File.exists?(metadata_file)
44
+ @metadata ||= JSON.parse(File.read(metadata_file))
45
+ end
46
+ @metadata
47
+ end
48
+
49
+ def add_upstream_remote
50
+ if upstream != source
51
+ `#{git_command} remote rm upstream`
52
+ end
53
+ `#{git_command} remote add upstream #{source}`
54
+ end
55
+
56
+ def git_upstream_url
57
+ `#{git_command} config --get remote.upstream.url`.chomp
58
+ end
59
+
60
+ def git_upstream_set?
61
+ source == git_upstream_url
62
+ end
63
+
64
+ def tags
65
+ `#{git_command} tag`.split("\n").map{|v| pad_version_string(v)}
66
+ end
67
+
68
+ def source=(s)
69
+ metadata['source'] = s
70
+ end
71
+
72
+ def source
73
+ metadata['source']
74
+ end
75
+
76
+ def pad_version_string(version_string)
77
+ parts = version_string.split('.').reject {|x| x == '*'}
78
+ while parts.length < 3
79
+ parts << '0'
80
+ end
81
+ parts.join '.'
82
+ end
83
+
84
+ def latest_tag
85
+ Gem::Version.new('0.0.12') >= Gem::Version.new('0.0.2')
86
+ v = tags.sort do |a,b|
87
+ Gem::Version.new(a.tr('v', '')) <=> Gem::Version.new(b.tr('v', ''))
88
+ end
89
+ v.last
90
+ end
91
+
92
+ # @returns [String] the name of the module found in the metadata file
93
+ def mod_name
94
+ metadata['name']
95
+ end
96
+
97
+ def version=(v)
98
+ metadata['version'] = v
99
+ end
100
+
101
+ # @returns [String] the version found in the metadata file
102
+ def version
103
+ metadata['version']
104
+ end
105
+
106
+ def tag_module
107
+ `git --git-dir=#{path}/.git tag -m 'v#{version}' v#{version}`
108
+ end
109
+
110
+ def bump_patch_version
111
+ return unless version
112
+ pieces = version.split('.')
113
+ raise "invalid semver structure #{version}" if pieces.count != 3
114
+ pieces[2] = pieces[2].next
115
+ metadata['version'] = pieces.join('.')
116
+ end
117
+
118
+ def bump_minor_version
119
+ return unless version
120
+ pieces = version.split('.')
121
+ raise "invalid semver structure #{version}" if pieces.count != 3
122
+ pieces[2] = '0'
123
+ pieces[1] = pieces[1].next
124
+ metadata['version'] = pieces.join('.')
125
+ end
126
+
127
+ def bump_major_version
128
+ return unless version
129
+ pieces = version.split('.')
130
+ raise "invalid semver structure #{version}" if pieces.count != 3
131
+ pieces[2] = '0'
132
+ pieces[1] = '0'
133
+ pieces[0] = pieces[0].next
134
+ metadata['version'] = pieces.join('.')
135
+ end
136
+
137
+ def to_s
138
+ JSON.pretty_generate(metadata)
139
+ end
140
+
141
+ def r10k_module?
142
+ name =~ /r10k_control/i
143
+ end
144
+
145
+ def branch_exists?(name)
146
+ `#{git_command} branch |grep '#{name}$'`
147
+ $?.success?
148
+ end
149
+
150
+ def git_command
151
+ @git_command ||= "git --work-tree=#{path} --git-dir=#{path}/.git"
152
+ end
153
+
154
+ def upstream
155
+ @upstream ||= git_upstream_url
156
+ end
157
+
158
+ # ensures the dev branch has been created and is up to date
159
+ def create_dev_branch
160
+ `#{git_command} fetch upstream`
161
+ raise GitError unless $?.success?
162
+ #puts "#{git_command} checkout -b #{src_branch} upstream/#{src_branch}"
163
+ `#{git_command} checkout -b #{src_branch} upstream/#{src_branch}` unless branch_exists?(src_branch)
164
+ raise GitError unless $?.success?
165
+ # ensure we have updated our local branch
166
+ #puts "#{git_command} checkout #{src_branch}"
167
+ `#{git_command} checkout #{src_branch}`
168
+ raise GitError unless $?.success?
169
+ #puts "#{git_command} rebase upstream/#{src_branch}"
170
+ `#{git_command} rebase upstream/#{src_branch}`
171
+ raise GitError unless $?.success?
172
+ end
173
+
174
+ # @returns [String] - the source branch to push to
175
+ # if r10k-control this branch will be dev, otherwise master
176
+ def src_branch
177
+ r10k_module? ? 'dev' : 'master'
178
+ end
179
+
180
+ def push_to_upstream
181
+ push_branch(source, src_branch)
182
+ push_tags(source)
183
+ end
184
+
185
+ # @return [String] the oid of the commit that was created
186
+ def commit_metadata
187
+ to_metadata_file
188
+ add_file(metadata_file)
189
+ create_commit("[ReleaseManager] - bump version to #{version}")
190
+ end
191
+
192
+ # @return [String] the oid of the commit that was created
193
+ def commit_metadata_source
194
+ to_metadata_file
195
+ add_file(metadata_file)
196
+ create_commit("[ReleaseManager] - change source to #{source}")
197
+ end
198
+
199
+ def to_metadata_file
200
+ logger.info("Writing to file #{metadata_file}")
201
+ File.write(metadata_file, to_s)
202
+ end
203
+
204
+ # @return [ControlRepo] - creates a new control repo object and clones the url unless already cloned
205
+ def self.create(path, url, branch = 'master')
206
+ c = PuppetModule.new(path, url)
207
+ c.clone(url, path)
208
+ c
209
+ end
210
+
211
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'control_mod'
3
+ require_relative 'errors'
4
+ require 'json'
5
+ require 'release_manager/puppet_module'
6
+
7
+ class Puppetfile
8
+ attr_accessor :modules, :puppetfile, :data, :base_path, :puppetmodule
9
+ BUMP_TYPES = %w{patch minor major}
10
+
11
+ # @param [String] puppetfile - the path to the puppetfile
12
+ def initialize(puppetfile = 'Puppetfile')
13
+ @puppetfile = puppetfile
14
+ @puppetmodule = PuppetModule.new(base_path)
15
+ end
16
+
17
+ def source
18
+ puppetmodule.source
19
+ end
20
+
21
+ def base_path
22
+ @base_path ||= File.dirname(puppetfile)
23
+ end
24
+
25
+ def git_command
26
+ "git --work-tree=#{base_path} --git-dir=#{base_path}/.git"
27
+ end
28
+
29
+ def commit(message)
30
+ puts `#{git_command} add #{puppetfile}`
31
+ puts `#{git_command} commit -n -m "[ReleaseManager] - #{message}"`
32
+ end
33
+
34
+ def current_branch
35
+ `#{git_command} rev-parse --abbrev-ref HEAD`
36
+ end
37
+
38
+ def add_module(name, metadata)
39
+ modules[name] = ControlMod.new(name, metadata)
40
+ end
41
+
42
+ def push(remote, branch, force = false)
43
+ opts = force ? '-f' : ''
44
+ `#{git_command} push #{remote} #{branch} #{opts}`
45
+ end
46
+
47
+ def data
48
+ unless @data
49
+ @data = File.read(puppetfile)
50
+ end
51
+ @data
52
+ end
53
+
54
+ # @return [Array[ControlMod]] - a list of control mod objects
55
+ def modules
56
+ unless @modules
57
+ @modules = {}
58
+ instance_eval(data) if data
59
+ end
60
+ @modules
61
+ end
62
+
63
+ def self.from_string(s)
64
+ instance = new
65
+ instance.data = s
66
+ instance
67
+ end
68
+
69
+ # @param Array[String] names - find all mods with the following names
70
+ # @return Hash[String, ControlMod] - returns the pupppet modules in a hash
71
+ def find_mods(names)
72
+ mods = {}
73
+ return mods if names.nil?
74
+ names.each do | mod_name |
75
+ m = find_mod(mod_name)
76
+ mods[m.name] = m
77
+ end
78
+ mods
79
+ end
80
+
81
+ # @param [String] name - the name of the mod you wish to find in the puppetfile
82
+ # @return ControlMod - a ControlMod object
83
+ def find_mod(name)
84
+ mod_name = name.strip.downcase
85
+ mod = modules[mod_name] || modules.find{ |module_name, mod| mod.repo =~ /#{mod_name}/i }
86
+ raise InvalidModuleNameException.new("Invalid module name #{name}, cannot locate in Puppetfile") unless mod
87
+ # since find returns an array we need to grab the element ouf of the array first
88
+ return mod.last if mod.instance_of?(Array)
89
+ mod
90
+ end
91
+
92
+ def write_version(mod_name, version)
93
+ mod = find_mod(mod_name)
94
+ mod.pin_version(version)
95
+ end
96
+
97
+ # @param [String] mod_name - the module name found in the puppetfile
98
+ # @param [String] src - the git url to the source
99
+ # @option [String] branch - the branch name to pin to if provided
100
+ # @return [ControlMod]
101
+ def write_source(mod_name, src, branch = nil)
102
+ mod = find_mod(mod_name)
103
+ mod.pin_url(src)
104
+ mod.pin_branch(branch) if branch
105
+ mod
106
+ end
107
+
108
+ def bump(mod_name, type = 'patch')
109
+ raise "Invalid type, must be one of #{BUMP_TYPES}" unless BUMP_TYPES.include?(type)
110
+ mod = find_mod(mod_name)
111
+ find_mod(mod_name).send("bump_#{type}_version")
112
+ end
113
+
114
+ def to_json(pretty = false)
115
+ if pretty
116
+ JSON.pretty_generate(modules)
117
+ else
118
+ modules.to_json
119
+ end
120
+ end
121
+
122
+ def write_to_file
123
+ File.write(puppetfile, to_s)
124
+ end
125
+
126
+ def to_s
127
+ modules.collect {|n, mod| mod.to_s }.join("\n\n")
128
+ end
129
+
130
+ def self.to_puppetfile(json_data)
131
+ obj = JSON.parse(json_data)
132
+ mods = obj.collect do |name, metadata|
133
+ name = "mod '#{name}',"
134
+ data = metadata.sort.map { |k, v| ":#{k} => '#{v}'" }.join(",\n\ ")
135
+ "#{name}\n #{data}\n"
136
+ end.join("\n")
137
+ mods
138
+ end
139
+
140
+ def mod(name, *args)
141
+ @modules[name] = ControlMod.new(name, args.flatten.first)
142
+ end
143
+
144
+ def forge(name, *args)
145
+ # skip this for now
146
+ end
147
+
148
+ end