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.
- checksums.yaml +7 -0
- data/.bash_profile +27 -0
- data/.dockerignore +1 -0
- data/.env +3 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +55 -0
- data/Dockerfile +14 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +21 -0
- data/README.md +311 -0
- data/Rakefile +6 -0
- data/app_startup_script.sh +4 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +37 -0
- data/exe/bump-changelog +5 -0
- data/exe/deploy-mod +5 -0
- data/exe/release-mod +5 -0
- data/exe/sandbox-create +13 -0
- data/lib/release_manager.rb +33 -0
- data/lib/release_manager/changelog.rb +130 -0
- data/lib/release_manager/cli/deploy_mod_cli.rb +44 -0
- data/lib/release_manager/cli/release_mod_cli.rb +43 -0
- data/lib/release_manager/cli/sandbox_create_cli.rb +138 -0
- data/lib/release_manager/control_mod.rb +83 -0
- data/lib/release_manager/control_repo.rb +35 -0
- data/lib/release_manager/errors.rb +13 -0
- data/lib/release_manager/git/credentials.rb +98 -0
- data/lib/release_manager/git/utilites.rb +263 -0
- data/lib/release_manager/logger.rb +52 -0
- data/lib/release_manager/module_deployer.rb +77 -0
- data/lib/release_manager/puppet_module.rb +211 -0
- data/lib/release_manager/puppetfile.rb +148 -0
- data/lib/release_manager/release.rb +174 -0
- data/lib/release_manager/sandbox.rb +272 -0
- data/lib/release_manager/vcs_manager.rb +22 -0
- data/lib/release_manager/vcs_manager/gitlab_adapter.rb +112 -0
- data/lib/release_manager/vcs_manager/vcs_adapter.rb +22 -0
- data/lib/release_manager/version.rb +3 -0
- data/lib/release_manager/workflow_action.rb +5 -0
- data/release_manager.gemspec +38 -0
- data/setup_repos.rb +95 -0
- 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
|