release_manager 0.4.0 → 0.5.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.
@@ -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