Module: PdkSync

Includes:
Constants
Defined in:
lib/pdksync.rb,
lib/pdksync/constants.rb

Defined Under Namespace

Modules: Constants

Constant Summary

Constants included from Constants

Constants::ACCESS_TOKEN, Constants::CREATE_PR_AGAINST, Constants::MANAGED_MODULES, Constants::NAMESPACE, Constants::PDKSYNC_DIR, Constants::PDKSYNC_LABEL, Constants::PUSH_FILE_DESTINATION

Class Method Summary collapse

Class Method Details

.add_label(client, repo_name, issue_number, label) ⇒ Object

Parameters:

  • client (Octokit::Client)

    The octokit client used to gain access to and manipulate the repository.

  • repo_name (String)

    The name of the repository on which the commit is to be made.

  • issue_number (Integer)

    The id of the issue (i.e. pull request) to add the label to.

  • label (String)

    The label to add.



448
449
450
451
452
453
# File 'lib/pdksync.rb', line 448

def self.add_label(client, repo_name, issue_number, label)
  client.update_issue(repo_name, issue_number, labels: [label])
rescue StandardError => error
  puts "(FAILURE) Adding label to #{repo_name} issue #{issue_number} has failed. #{error}".red
  return false
end

.add_staged_files(git_repo) ⇒ Object

Parameters:

  • git_repo (Git::Base)

    A git object representing the local repository to be staged.



339
340
341
342
343
344
345
346
# File 'lib/pdksync.rb', line 339

def self.add_staged_files(git_repo)
  if git_repo.status.changed != {}
    git_repo.add(all: true)
    puts 'All files have been staged.'
  else
    puts 'Nothing to commit.'
  end
end

.check_for_label(client, repo_name, label) ⇒ Boolean

Returns A boolean stating whether the label was found.

Parameters:

  • client (Octokit::Client)

    The octokit client used to gain access to and manipulate the repository.

  • repo_name (String)

    The name of the repository on which the commit is to be made.

  • label (String)

    The label to check for.

Returns:

  • (Boolean)

    A boolean stating whether the label was found.



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/pdksync.rb', line 418

def self.check_for_label(client, repo_name, label)
  # Get labels from repository
  repo_labels = client.labels(repo_name)

  # Look for label in the repository's labels
  match = false
  repo_labels.each do |repo_label|
    if repo_label.name == label
      match = true
      break
    end
  end

  # Raise error if a match was not found else return true
  (match == false) ? (raise StandardError, "Label '#{label}' not found in #{repo_name}") : (return true)
rescue StandardError => error
  puts "(FAILURE) Retrieving labels for #{repo_name} has failed. #{error}".red
  return false
end

.checkout_branch(git_repo, branch_suffix) ⇒ Object

Parameters:

  • git_repo (Git::Base)

    A git object representing the local repository to be branched.

  • branch_suffix (String)

    The string that is appended on the branch name. eg template_ref or a friendly name



319
320
321
# File 'lib/pdksync.rb', line 319

def self.checkout_branch(git_repo, branch_suffix)
  git_repo.branch("pdksync_#{branch_suffix}").checkout
end

.clean_env(output_path) ⇒ Object

Parameters:

  • output_path (String)

    The repository that is to be deleted.



237
238
239
240
# File 'lib/pdksync.rb', line 237

def self.clean_env(output_path)
  # If a local copy already exists it is removed
  FileUtils.rm_rf(output_path)
end

.clone_directory(namespace, module_name, output_path) ⇒ Git::Base

Returns A git object representing the local repository.

Parameters:

  • namespace (String)

    The namespace the repository is located in.

  • module_name (String)

    The name of the repository.

  • output_path (String)

    The location the repository is to be cloned to.

Returns:

  • (Git::Base)

    A git object representing the local repository.



252
253
254
255
256
# File 'lib/pdksync.rb', line 252

def self.clone_directory(namespace, module_name, output_path)
  Git.clone("https://github.com/#{namespace}/#{module_name}.git", output_path.to_s) # is returned
rescue Git::GitExecuteError => error
  puts "(FAILURE) Cloning #{module_name} has failed. #{error}".red
end

.commit_staged_files(git_repo, template_ref, commit_message = nil) ⇒ Object

Parameters:

  • git_repo (Git::Base)

    A git object representing the local repository against which the commit is to be made.

  • template_ref (String)

    The unique template_ref that is used as part of the commit name.

  • commit_message (String) (defaults to: nil)

    If sepecified it will be the message for the commit.



356
357
358
359
360
361
362
363
# File 'lib/pdksync.rb', line 356

def self.commit_staged_files(git_repo, template_ref, commit_message = nil)
  message = if commit_message.nil?
              "pdksync_#{template_ref}"
            else
              commit_message
            end
  git_repo.commit(message)
end

.create_commit(git_repo, branch_name, commit_message) ⇒ Object



227
228
229
230
231
# File 'lib/pdksync.rb', line 227

def self.create_commit(git_repo, branch_name, commit_message)
  checkout_branch(git_repo, branch_name)
  add_staged_files(git_repo)
  commit_staged_files(git_repo, branch_name, commit_message)
end

.create_filespaceObject



170
171
172
# File 'lib/pdksync.rb', line 170

def self.create_filespace
  FileUtils.mkdir @pdksync_dir unless Dir.exist?(@pdksync_dir)
end

.create_pr(client, repo_name, template_ref, pdk_version, pr_title = nil) ⇒ Object

Parameters:

  • client (Octokit::Client)

    The octokit client used to gain access to and manipulate the repository.

  • repo_name (String)

    The name of the repository on which the commit is to be made.

  • template_ref (String)

    The unique reference that that represents the template the update has ran against.

  • pdk_version (String)

    The current version of the pdk on which the update is run.



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/pdksync.rb', line 389

def self.create_pr(client, repo_name, template_ref, pdk_version, pr_title = nil)
  if pr_title.nil?
    title = "pdksync - Update using #{pdk_version}"
    message = "pdk version: `#{pdk_version}` \n pdk template ref: `#{template_ref}`"
    head = "pdksync_#{template_ref}"
  else
    title = "pdksync - #{pr_title}"
    message = "#{pr_title}\npdk version: `#{pdk_version}` \n"
    head = template_ref
  end
  pr = client.create_pull_request(repo_name, @create_pr_against,
                                  head,
                                  title,
                                  message)
  pr
rescue StandardError => error
  puts "(FAILURE) PR creation for #{repo_name} has failed. #{error}".red
end

.delete_branch(client, repo_name, branch_name) ⇒ Object

Parameters:

  • client (Octokit::Client)

    The octokit client used to gain access to and manipulate the repository.

  • repo_name (String)

    The name of the repository from which the branch is to be deleted.

  • branch_name (String)

    The name of the branch that is to be deleted.



463
464
465
466
467
# File 'lib/pdksync.rb', line 463

def self.delete_branch(client, repo_name, branch_name)
  client.delete_branch(repo_name, branch_name)
rescue StandardError => error
  puts "(FAILURE) Deleting #{branch_name} in #{repo_name} failed. #{error}".red
end

.main(steps: [:clone], args: nil) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/pdksync.rb', line 38

def self.main(steps: [:clone], args: nil)
  create_filespace
  client = setup_client
  module_names = return_modules
  raise "No modules found in '#{@managed_modules}'" if module_names.nil?
  validate_modules_exist(module_names)
  pr_list = []

  # The current directory is saved for cleanup purposes
  main_path = Dir.pwd

  # validation run_a_command
  if steps.include?(:run_a_command)
    raise '"run_a_command" requires an argument to run.' if args.nil?
    puts "Command '#{args}'"
  end
  # validation create_commit
  if steps.include?(:create_commit)
    raise 'Needs a branch_name and commit_message' if args.nil? || args[:commit_message].nil? || args[:branch_name].nil?
    puts "Commit branch_name=#{args[:branch_name]} commit_message=#{args[:commit_message]}"
  end
  # validation push_and_create_pr
  if steps.include?(:push_and_create_pr)
    raise 'Needs a pr_title' if args.nil? || args[:pr_title].nil?
    puts "PR title =#{args[:pr_title]}"
  end
  # validation clean_branches
  if steps.include?(:clean_branches)
    raise 'Needs a branch_name, and the branch name contains the string pdksync' if args.nil? || args[:branch_name].nil? || !args[:branch_name].include?('pdksync')
    puts "Removing branch_name =#{args[:branch_name]}"
  end

  abort "No modules listed in #{@managed_modules}" if module_names.nil?
  module_names.each do |module_name|
    Dir.chdir(main_path) unless Dir.pwd == main_path
    print "#{module_name}, "
    repo_name = "#{@namespace}/#{module_name}"
    output_path = "#{@pdksync_dir}/#{module_name}"
    if steps.include?(:clone)
      clean_env(output_path) if Dir.exist?(output_path)
      print 'delete module directory, '
      @git_repo = clone_directory(@namespace, module_name, output_path)
      print 'cloned, '
      puts "(WARNING) Unable to clone repo for #{module_name}".red if @git_repo.nil?
      Dir.chdir(main_path) unless Dir.pwd == main_path
      next if @git_repo.nil?
    end
    puts '(WARNING) @output_path does not exist, skipping module'.red unless File.directory?(output_path)
    next unless File.directory?(output_path)
    if steps.include?(:pdk_convert)
      exit_status = run_command(output_path, "#{return_pdk_path} convert --force --template-url https://github.com/puppetlabs/pdk-templates")
      print 'converted, '
      next unless exit_status.zero?
    end
    if steps.include?(:pdk_validate)
      Dir.chdir(main_path) unless Dir.pwd == main_path
      exit_status = run_command(output_path, "#{return_pdk_path} validate -a")
      print 'validated, '
      next unless exit_status.zero?
    end
    if steps.include?(:run_a_command)
      Dir.chdir(main_path) unless Dir.pwd == main_path
      print 'run command, '
      exit_status = run_command(output_path, args)
      next unless exit_status.zero?
    end
    if steps.include?(:pdk_update)
      Dir.chdir(main_path) unless Dir.pwd == main_path
      next unless pdk_update(output_path).zero?
      if steps.include?(:use_pdk_ref)
        ref = return_template_ref
        pr_title = args[:additional_title] ? "#{args[:additional_title]} - pdksync_#{ref}" : "pdksync_#{ref}"
        args = { branch_name: "pdksync_#{ref}",
                 commit_message: "pdksync_#{ref}",
                 pr_title: pr_title,
                 pdksync_label: @default_pdksync_label }
      end
      print 'pdk update, '
    end
    if steps.include?(:create_commit)
      Dir.chdir(main_path) unless Dir.pwd == main_path
      git_instance = Git.open(output_path)
      create_commit(git_instance, args[:branch_name], args[:commit_message])
      print 'commit created, '
    end
    if steps.include?(:push_and_create_pr)
      Dir.chdir(main_path) unless Dir.pwd == main_path
      git_instance = Git.open(output_path)
      push_staged_files(git_instance, git_instance.current_branch, repo_name)
      print 'push, '
      pdk_version = return_pdk_version("#{output_path}/metadata.json")

      # If a label is supplied, verify that it is available in the repo
      label = args[:pdksync_label] ? args[:pdksync_label] : args[:label]
      label_valid = (label.is_a?(String) && !label.to_str.empty?) ? check_for_label(client, repo_name, label) : nil

      # Exit current iteration if an error occured retrieving a label
      if label_valid == false
        raise 'Ensure label is valid'
      end

      # Create the PR and add link to pr list
      pr = create_pr(client, repo_name, git_instance.current_branch, pdk_version, args[:pr_title])
      if pr.nil?
        break
      end

      pr_list.push(pr.html_url)
      print 'created pr, '

      # If a valid label is supplied, add this to the PR
      if label_valid == true
        add_label(client, repo_name, pr.number, label)
        print "added label '#{label}' "
      end
    end
    if steps.include?(:clean_branches)
      Dir.chdir(main_path) unless Dir.pwd == main_path
      delete_branch(client, repo_name, args[:branch_name])
      print 'branch deleted, '
    end
    puts 'done.'.green
  end
  return if pr_list.size.zero?
  puts "\nPRs created:\n".blue
  pr_list.each do |pr|
    puts pr
  end
end

.pdk_update(output_path) ⇒ Integer

Returns The status code of the pdk update run.

Parameters:

  • output_path (String)

    The location that the command is to be run from.

Returns:

  • (Integer)

    The status code of the pdk update run.



293
294
295
296
297
298
299
# File 'lib/pdksync.rb', line 293

def self.pdk_update(output_path)
  # Runs the pdk update command
  Dir.chdir(output_path) unless Dir.pwd == output_path
  _stdout, stderr, status = Open3.capture3("#{return_pdk_path} update --force")
  puts "(FAILURE) Unable to run `pdk update`: #{stderr}".red unless status.exitstatus.zero?
  status.exitstatus
end

.push_staged_files(git_repo, current_branch, repo_name) ⇒ Object

Parameters:

  • git_repo (Git::Base)

    A git object representing the local repository againt which the push is to be made.

  • template_ref (String)

    The unique reference that that represents the template the update has ran against.

  • repo_name (String)

    The name of the repository on which the commit is to be made.



373
374
375
376
377
# File 'lib/pdksync.rb', line 373

def self.push_staged_files(git_repo, current_branch, repo_name)
  git_repo.push(@push_file_destination, current_branch)
rescue StandardError => error
  puts "(FAILURE) Pushing to #{@push_file_destination} for #{repo_name} has failed. #{error}".red
end

.return_modulesArray

Returns An array of different module names.

Returns:

  • (Array)

    An array of different module names.



190
191
192
193
# File 'lib/pdksync.rb', line 190

def self.return_modules
  raise "File '#{@managed_modules}' is empty/does not exist" if File.size?(@managed_modules).nil?
  YAML.safe_load(File.open(@managed_modules))
end

.return_pdk_pathObject

Returns String Path to the pdk executable.

Returns:

  • String Path to the pdk executable



216
217
218
219
220
221
222
223
224
225
# File 'lib/pdksync.rb', line 216

def self.return_pdk_path
  full_path = '/opt/puppetlabs/pdk/bin/pdk'
  path = if File.executable?(full_path)
           full_path
         else
           puts "(WARNING) Using pdk on PATH not '#{full_path}'".red
           'pdk'
         end
  path
end

.return_pdk_version(metadata_file = 'metadata.json') ⇒ String

Returns A string value that represents the current pdk version.

Parameters:

  • metadata_file (String) (defaults to: 'metadata.json')

    An optional input that can be used to set the location of the metadata file.

Returns:

  • (String)

    A string value that represents the current pdk version.



329
330
331
332
333
# File 'lib/pdksync.rb', line 329

def self.return_pdk_version( = 'metadata.json')
  file = File.read()
  data_hash = JSON.parse(file)
  data_hash['pdk-version']
end

.return_template_ref(metadata_file = 'metadata.json') ⇒ String

Returns A string value that represents the current pdk template.

Parameters:

  • metadata_file (String) (defaults to: 'metadata.json')

    An optional input that can be used to set the location of the metadata file.

Returns:

  • (String)

    A string value that represents the current pdk template.



307
308
309
310
311
# File 'lib/pdksync.rb', line 307

def self.return_template_ref( = 'metadata.json')
  file = File.read()
  data_hash = JSON.parse(file)
  data_hash['template-ref']
end

.run_command(output_path, command) ⇒ Integer

Returns The status code of the command run.

Parameters:

  • output_path (String)

    The location that the command is to be run from.

  • command (String)

    The command to be run.

Returns:

  • (Integer)

    The status code of the command run.



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/pdksync.rb', line 266

def self.run_command(output_path, command)
  stdout = ''
  stderr = ''
  status = Process::Status

  Dir.chdir(output_path) unless Dir.pwd == output_path

  # Environment cleanup required due to Ruby subshells using current Bundler environment
  if command =~ %r{^bundle}
    Bundler.with_clean_env do
      stdout, stderr, status = Open3.capture3(command)
    end
  else
    stdout, stderr, status = Open3.capture3(command)
  end

  puts "\n#{stdout}\n".yellow
  puts "(FAILURE) Unable to run command '#{command}': #{stderr}".red unless status.exitstatus.zero?
  status.exitstatus
end

.setup_clientOctokit::Client

Returns client The octokit client that has been created.

Returns:

  • (Octokit::Client)

    client The octokit client that has been created.



178
179
180
181
182
183
184
# File 'lib/pdksync.rb', line 178

def self.setup_client
  client = Octokit::Client.new(access_token: @access_token.to_s)
  client.user.
  client
rescue ArgumentError, Octokit::Unauthorized
  raise "Access Token not set up correctly - Use export 'GITHUB_TOKEN=<put your token here>' to set it."
end

.validate_modules_exist(module_names) ⇒ Object

Parameters:

  • module_names (Array)

    String array of the names of GitHub repos



199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/pdksync.rb', line 199

def self.validate_modules_exist(module_names)
  invalid_names = []
  module_names.each do |module_name|
    # If module name is invalid, push it to invalid names array
    unless Octokit.repository?("#{@namespace}/#{module_name}")
      invalid_names.push(module_name)
      next
    end
  end
  # Raise error if any invalid matches were found
  raise "Could not find the following repositories: #{invalid_names}" unless invalid_names.empty?
end