release_manager 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'puppetfile'
3
+ require_relative 'puppet_module'
4
+ require 'highline/import'
5
+ require 'tempfile'
6
+
7
+ class R10kDeployer
8
+ attr_reader :options, :path, :previous_branch
9
+
10
+ include ReleaseManager::Logger
11
+ include ReleaseManager::VCSManager
12
+ include ReleaseManager::Git::Utilities
13
+
14
+ def initialize(path, opts)
15
+ @path = path
16
+ @options = opts
17
+ end
18
+
19
+ def run
20
+ begin
21
+ @previous_branch = options[:src_ref]
22
+ mr, branch_name = create_mr(options[:src_ref], options[:dest_ref], options[:remote])
23
+ ensure
24
+ # cleanup branch, checkout previous branch
25
+ end
26
+ puts mr.web_url if mr
27
+ end
28
+
29
+ def cleanup(branch = nil)
30
+ control_repo.checkout_branch(previous_branch, strategy: :force)
31
+ control_repo.delete_branch(branch) if branch
32
+ end
33
+
34
+ alias_method :control_repo_path, :path
35
+
36
+ def self.run(path, options)
37
+ begin
38
+ deploy = new(path, options)
39
+ deploy.check_requirements
40
+ deploy.logger.info "Deploying R10k-Control #{options[:dest_ref]} with version: #{options[:src_ref]}"
41
+ deploy.run
42
+ rescue Gitlab::Error::Forbidden => e
43
+ logger.fatal(e.message)
44
+ logger.fatal("You don't have access to modify the repository")
45
+ rescue Gitlab::Error::MissingCredentials => e
46
+ deploy.logger.fatal(e.message)
47
+ code = 1
48
+ rescue PatchError => e
49
+ deploy.logger.fatal(e.message)
50
+ code = 1
51
+ rescue ModNotFoundException => e
52
+ deploy.logger.fatal(e.message)
53
+ code = 1
54
+ rescue InvalidBranchName => e
55
+ deploy.logger.fatal(e.message)
56
+ code = 1
57
+ rescue InvalidMetadataSource
58
+ deploy.logger.fatal "The puppet module's metadata.json source field must be a git url: ie. git@someserver.com:devops/module.git"
59
+ code = 1
60
+ rescue PuppetfileNotFoundException
61
+ deploy.logger.fatal "Cannot find the puppetfile at #{puppetfile_path}"
62
+ code = 1
63
+ rescue InvalidModuleNameException => e
64
+ deploy.logger.fatal e.message
65
+ code = 1
66
+ rescue Gitlab::Error::NotFound => e
67
+ deploy.logger.fatal e.message
68
+ deploy.logger.fatal "Either the project does not exist or you do not have enough permissions"
69
+ code = 1
70
+ rescue Exception => e
71
+ deploy.logger.fatal e.message
72
+ deploy.logger.fatal e.backtrace.join("\n")
73
+ code = 1
74
+ ensure
75
+ exit code.to_i
76
+ end
77
+ end
78
+
79
+ def control_repo
80
+ @control_repo ||= setup_control_repo(puppetfile.source)
81
+ end
82
+
83
+ def check_requirements
84
+ raise PuppetfileNotFoundException unless File.exists?(control_repo_path)
85
+ end
86
+
87
+ private
88
+
89
+ def create_mr(src_ref, dest_ref, remote = false)
90
+ url = puppetfile.source
91
+ message = "auto deploy #{src_ref} to #{dest_ref}"
92
+ branch_name = "#{dest_ref}_#{rand(10000)}"
93
+ control_repo.create_branch(branch_name, "upstream/#{dest_ref}")
94
+ control_repo.checkout_branch(branch_name, strategy: :force )
95
+ diff = control_repo.create_diff(src_ref,branch_name)
96
+ return control_repo.logger.info("nothing to commit or deploy") if diff.deltas.count < 1
97
+ Tempfile.open('git_patch') do |patchfile|
98
+ patchfile.write(diff.patch)
99
+ control_repo.apply_patch(patchfile.path)
100
+ control_repo.add_all
101
+ end
102
+ successful_commit = control_repo.commit(message, nil, nil, false)
103
+ control_repo.push_branch('myfork', branch_name, true) if successful_commit
104
+ mr = control_repo.create_merge_request(control_repo.url, message, {
105
+ source_branch: branch_name,
106
+ target_branch: dest_ref,
107
+ remove_source_branch: true,
108
+ target_project_url: url
109
+ }) if successful_commit
110
+ return [mr, branch_name]
111
+ end
112
+
113
+ # @return [ControlRepo] - creates a new control repo object and clones the url unless already cloned
114
+ # @param [String] url - the url to clone and fork
115
+ def setup_control_repo(url)
116
+ # clone r10k unless already cloned
117
+ fork = create_repo_fork(url)
118
+ c = ControlRepo.create(control_repo_path, fork.ssh_url_to_repo)
119
+ c.add_remote(fork.ssh_url_to_repo, 'myfork',true)
120
+ c.fetch('myfork')
121
+ c.fetch('origin')
122
+ c.add_remote(url, 'upstream', true)
123
+ c.fetch('upstream')
124
+ c
125
+ end
126
+
127
+ # @return [Puppetfile] - instance of Puppetfile object
128
+ def puppetfile
129
+ @puppetfile ||= begin
130
+ file = options[:puppetfile] || File.join(path, 'Puppetfile')
131
+ Puppetfile.new(file)
132
+ end
133
+ end
134
+
135
+ end
@@ -1,20 +1,3 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # Author: Corey Osman
4
- # Purpose: release a new version of a module or r10k-control from the src branch by performing
5
- # the following tasks:
6
- # - bump version in metadata file
7
- # - bump changelog version using version in metadata file
8
- # - tag the code matching the version in the metadata file
9
- # - push to upstream
10
- # This script can be used on modules or r10k-control. If using on a module
11
- # be sure to pass in the repo path using --repo. The repo is where this script
12
- # pushes too.
13
- #
14
- # You should also use the -d feature which simulates a run of the script without doing
15
- # anything harmful.
16
- #
17
- # Run with -h to see the help
18
1
  require 'json'
19
2
  require_relative 'puppet_module'
20
3
 
@@ -37,15 +20,19 @@ class Release
37
20
 
38
21
  # @returns [String] the version found in the metadata file
39
22
  def version
40
- dry_run? ? puppet_module.version.next : puppet_module.version
23
+ dry_run? ? next_version : puppet_module.version
41
24
  end
42
25
 
43
- def tag
26
+ def next_version
27
+ puppet_module.version.next
28
+ end
29
+
30
+ def tag(id)
44
31
  if dry_run?
45
32
  logger.info "Would have just tagged the module to #{version}"
46
33
  return
47
34
  end
48
- puppet_module.tag_module
35
+ puppet_module.tag_module(options[:remote], id)
49
36
  end
50
37
 
51
38
  def bump
@@ -53,18 +40,20 @@ class Release
53
40
  logger.info "Would have just bumped the version to #{version}"
54
41
  return
55
42
  end
56
- puppet_module.bump_patch_version unless options[:bump]
43
+ raise TagExists.new("Tag v#{version} already exists") if puppet_module.tag_exists?("v#{next_version}", options[:remote])
44
+ version = puppet_module.bump_patch_version unless options[:bump]
57
45
  # save the update version to the metadata file, then commit
58
- puppet_module.commit_metadata
46
+ puppet_module.commit_metadata(options[:remote])
59
47
  end
60
48
 
49
+ # @return [String] - sha of the commit
61
50
  def bump_log
62
51
  if dry_run?
63
52
  logger.info "Would have just bumped the CHANGELOG to version #{version}"
64
53
  return
65
54
  end
66
55
  log = Changelog.new(puppet_module.path, version, {:commit => true})
67
- log.run
56
+ log.run(options[:remote], puppet_module.src_branch)
68
57
  end
69
58
 
70
59
  def push
@@ -84,22 +73,25 @@ class Release
84
73
  end
85
74
 
86
75
  def check_requirements
76
+ @loop_count = @loop_count.to_i + 1
87
77
  begin
88
78
  PuppetModule.check_requirements(puppet_module.path)
79
+ raise AlreadyReleased.new("No new changes, skipping release") if puppet_module.already_latest?
89
80
  Changelog.check_requirements(puppet_module.path)
90
81
  rescue NoUnreleasedLine
91
82
  logger.fatal "No Unreleased line in the CHANGELOG.md file, please add a Unreleased line and retry"
92
- exit 1
83
+ return false
93
84
  rescue UpstreamSourceMatch
94
- logger.fatal "The upstream remote url does not match the source url in the metadata.json source"
85
+ logger.warn "The upstream remote url does not match the source url in the metadata.json source"
95
86
  add_upstream_remote
96
- exit 1
87
+ return false if @loop_count > 2
88
+ check_requirements
97
89
  rescue InvalidMetadataSource
98
90
  logger.fatal "The puppet module's metadata.json source field must be a git url: ie. git@someserver.com:devops/module.git"
99
- exit 1
91
+ return false
100
92
  rescue NoChangeLogFile
101
93
  logger.fatal "CHANGELOG.md does not exist, please create one"
102
- exit 1
94
+ return false
103
95
  end
104
96
  end
105
97
 
@@ -115,12 +107,12 @@ class Release
115
107
  end
116
108
  end
117
109
 
118
- # updates the metadata.js file to the next version
110
+ # updates the metadata.json file to the next version
119
111
  bump
120
112
  # updates the changelog to the next version based on the metadata file
121
- bump_log
113
+ id = bump_log
122
114
  # tags the r10k-module with the version found in the metadata.json file
123
- tag
115
+ tag(id)
124
116
  # pushes the updated code and tags to the upstream repo
125
117
  if auto_release?
126
118
  push
@@ -138,6 +130,10 @@ class Release
138
130
  end
139
131
 
140
132
  def add_upstream_remote
133
+ if auto_release?
134
+ puppet_module.add_upstream_remote
135
+ return
136
+ end
141
137
  answer = nil
142
138
  while answer !~ /y|n/
143
139
  print "Ok to change your upstream remote from #{puppet_module.upstream}\n to #{puppet_module.source}? (y/n): "
@@ -152,7 +148,7 @@ class Release
152
148
 
153
149
  def run
154
150
  begin
155
- check_requirements
151
+ exit -1 unless check_requirements
156
152
  puppet_module.create_dev_branch
157
153
  value = release
158
154
  unless value
@@ -162,10 +158,21 @@ class Release
162
158
  logger.info "Version #{version} has been released successfully"
163
159
  puts "This was a dry run so nothing actually happen".green if dry_run?
164
160
  exit 0
165
- rescue GitError
166
- logger.fatal "There was an issue when running a git command"
161
+ rescue Gitlab::Error::Forbidden => e
162
+ logger.fatal(e.message)
163
+ logger.fatal("You don't have access to modify the repository")
164
+ exit -1
165
+ rescue TagExists => e
166
+ logger.fatal(e.message)
167
+ exit -1
168
+ rescue GitError => e
169
+ logger.fatal "There was an issue when running a git command\n #{e.message}"
167
170
  rescue InvalidMetadataSource
168
171
  logger.fatal "The puppet module's metadata.json source field must be a git url: ie. git@someserver.com:devops/module.git"
172
+ exit -1
173
+ rescue AlreadyReleased => e
174
+ logger.warn(e.message)
175
+ exit 0
169
176
  rescue ModNotFoundException
170
177
  logger.fatal "Invalid module path for #{path}"
171
178
  exit -1
@@ -0,0 +1,68 @@
1
+ require 'json'
2
+ require_relative 'puppet_module'
3
+
4
+ class RemoteRelease < Release
5
+ attr_reader :path, :options
6
+ include ReleaseManager::Logger
7
+
8
+ # runs all the required steps to release the software
9
+ # currently this must be done manually by a release manager
10
+ #
11
+ def release
12
+ unless auto_release?
13
+ print "Have you merged your code? Did you fetch and rebase against the upstream? Want to continue (y/n)?: ".yellow
14
+ answer = gets.downcase.chomp
15
+ if answer == 'n'
16
+ return false
17
+ end
18
+ print "Ready to release version #{version.next} to #{puppet_module.source}\n and forever change history(y/n)?: ".yellow
19
+ answer = gets.downcase.chomp
20
+ if answer != 'y'
21
+ puts "Nah, forget it, this release wasn't that cool anyways.".yellow
22
+ return false
23
+ end
24
+ end
25
+ # updates the metadata.js file to the next version
26
+ bump
27
+ # updates the changelog to the next version based on the metadata file
28
+ id = bump_log
29
+ # tags the r10k-module with the version found in the metadata.json file
30
+ tag(id)
31
+ end
32
+
33
+ def run
34
+ begin
35
+ check_requirements
36
+ exit 1 unless release
37
+ logger.info "Releasing Version #{version} to #{puppet_module.source}"
38
+ logger.info "Version #{version} has been released successfully"
39
+ puts "This was a dry run so nothing actually happen".green if dry_run?
40
+ exit 0
41
+ rescue Gitlab::Error::NotFound => e
42
+ logger.fatal(e.message)
43
+ logger.fatal("This probably means the user attached to the token does not have access")
44
+ exit -1
45
+ rescue Gitlab::Error::MissingCredentials
46
+ logger.fatal(e.message)
47
+ exit -1
48
+ rescue Gitlab::Error::Forbidden => e
49
+ logger.fatal(e.message)
50
+ logger.fatal("You don't have access to modify the repository")
51
+ exit -1
52
+ rescue AlreadyReleased => e
53
+ logger.warn(e.message)
54
+ exit 0
55
+ rescue TagExists => e
56
+ logger.fatal(e.message)
57
+ exit -1
58
+ rescue GitError
59
+ logger.fatal "There was an issue when running a git command"
60
+ rescue InvalidMetadataSource
61
+ logger.fatal "The puppet module's metadata.json source field must be a git url: ie. git@someserver.com:devops/module.git"
62
+ exit -1
63
+ rescue ModNotFoundException
64
+ logger.fatal "Invalid module path for #{path}, is there a metadata.json file?"
65
+ exit -1
66
+ end
67
+ end
68
+ end
@@ -5,19 +5,21 @@ require 'rugged'
5
5
  require 'fileutils'
6
6
  require 'release_manager/logger'
7
7
  require 'release_manager/vcs_manager'
8
+ require 'forwardable'
8
9
 
9
10
  class Sandbox
10
11
  attr_reader :modules, :name, :repos_dir, :options,
11
- :control_repo, :module_names, :control_repo_path, :vcs
12
+ :control_repo, :module_names, :control_repo_path
12
13
 
14
+ include ReleaseManager::VCSManager
13
15
  include ReleaseManager::Logger
16
+ include ReleaseManager::Git::Utilities
14
17
 
15
18
  def initialize(name, modules, control_repo_path, repos_dir = nil, options = {})
16
19
  @name = name
17
20
  @repos_dir = repos_dir
18
21
  @module_names = modules
19
22
  @control_repo_path = control_repo_path
20
- @vcs = ReleaseManager::VCSManager.default_instance
21
23
  @options = options
22
24
  end
23
25
 
@@ -141,6 +143,9 @@ class Sandbox
141
143
  begin
142
144
  mod = puppetfile.find_mod(mod_name)
143
145
  setup_module_repo(mod)
146
+ rescue Rugged::CheckoutError => e
147
+ logger.fatal(e.message)
148
+ exit 1
144
149
  rescue InvalidModuleNameException => e
145
150
  logger.error(e.message)
146
151
  value = nil
@@ -157,23 +162,16 @@ class Sandbox
157
162
  @control_repo.checkout_branch(name)
158
163
  puppetfile.write_to_file
159
164
  logger.info("Committing Puppetfile changes to r10k-control branch: #{name}")
160
- puppetfile.commit("Sandbox Creation for #{name} environment")
161
- logger.info("Pushing new environment branch: #{name} to upstream")
162
- puppetfile.push('upstream', name, true)
165
+ committed = puppetfile.commit("Sandbox Creation for #{name} environment")
166
+ # no need to push if we didn't commit anything
167
+ if committed
168
+ logger.info("Pushing new environment branch: #{name} to upstream")
169
+ puppetfile.push('upstream', name, true)
170
+ end
171
+ logger.info("Sandbox created successfully")
163
172
  return self
164
173
  end
165
174
 
166
- # @param [String] url - a git url
167
- # @return [String] a string representing the project id from gitlab
168
- # gets the project id from gitlab using the remote API
169
- def repo_id(url)
170
- # ie. git@server:namespace/project.git
171
- proj = url.match(/:(.*\/.*)\.git/)
172
- raise RepoNotFound unless proj
173
- # the gitlab api is supposed to encode the slash, but currently that doesn't seem to work
174
- proj[1].gsub('/', '%2F')
175
- end
176
-
177
175
  # TODO: extract this out to an adapter
178
176
  def verify_api_token
179
177
  begin
@@ -183,56 +181,6 @@ class Sandbox
183
181
  end
184
182
  end
185
183
 
186
- # TODO: extract this out to an adapter
187
- # replaces namespace from the url with the supplied or default namespace
188
- def swap_namespace(url, namespace = nil)
189
- url.gsub(/\:([\w-]+)\//, ":#{namespace || Gitlab.user.username}/")
190
- end
191
-
192
- # @return [Gitlab::ObjectifiedHash] Information about the forked project
193
- # @param [ControlMod] the module you want to fork
194
- # TODO: extract this out to an adapter
195
- def create_repo_fork(url, namespace = nil )
196
- new_url = swap_namespace(url, namespace)
197
- repo = repo_exists?(new_url)
198
- unless repo
199
- upstream_repo_id = repo_id(url)
200
- logger.info("Forking project from #{url} to #{new_url}")
201
- repo = Gitlab.create_fork(upstream_repo_id)
202
- # gitlab lies about having completed the forking process, so lets sleep until it is actually done
203
- loop do
204
- sleep(1)
205
- break if repo_exists?(repo.ssh_url_to_repo)
206
- end
207
- end
208
- vcs.add_permissions(repo.id, options[:default_members])
209
- repo
210
- end
211
-
212
- # @param [String] url - the git url of the repository
213
- # @return [Boolean] returns the project object (true) if found, false otherwise
214
- # TODO: extract this out to an adapter
215
- def repo_exists?(url)
216
- upstream_repo_id = repo_id(url)
217
- begin
218
- Gitlab.project(upstream_repo_id)
219
- rescue
220
- false
221
- end
222
- end
223
-
224
- # @return String - the branch name that was created
225
- # TODO: extract this out to an adapter
226
- def create_repo_branch(repo_id, branch_name)
227
- Gitlab.repo_create_branch(repo_id, branch_name)
228
- end
229
-
230
- # TODO: extract this out to an adapter
231
- def clone_repo(mod_name, url)
232
- path = File.join(repos_dir, mod_name)
233
- Rugged::Repository.clone_at(url, path, checkout_branch: name)
234
- end
235
-
236
184
  # @returns [Hash[PuppetModules]] an hash of puppet modules
237
185
  def modules
238
186
  @modules ||= puppetfile.find_mods(module_names)
@@ -244,7 +192,7 @@ class Sandbox
244
192
 
245
193
  def check_requirements
246
194
  begin
247
- vcs.add_ssh_key
195
+ add_ssh_key
248
196
  rescue InvalidModuleNameException => e
249
197
  logger.error(e.message)
250
198
  exit 1
@@ -268,5 +216,4 @@ class Sandbox
268
216
  box.verify_api_token
269
217
  box.create(options[:r10k_repo_url])
270
218
  end
271
-
272
219
  end