class KnifeGithubDeploy::GithubDeploy

Implements the knife github deploy function

@author

Ian Southam (<isoutham@schubergphilis.com>)

Copyright

Copyright © 2013 Ian Southam.

This code is specific to our company workflow

Overview

All modes presume you have used github download to download a cookbook or are creating a new cookbook

Examples

Deploy a development version of cookbook to your chef server

knife github deploy cookbook_name

Deploy a release version of cookbook to your chef server

knife github deploy cookbook_name -f

Options

-f Operate in final release mode -p Update the patch component of the version -m Update the minor component of the version -M Update the major component of the version

Operation Modes

Development (default)

This will take a cookbook name Do some basic version checks (if the current cookbook is frozen) and upload it

If the cookbook is frozen it will force you to choose a new version and update the metadata accordingly

Release (-f)

You will be forced to select a new version. You can choose via the options whether to increment the Major/minor or patch revision numbers The version will be tagged Uploaded to the Chef server and frozen

Version numbers

You can choose a specific version number by specifying it on the command line.

If you do not specify a version, the version will be the version in your cookbook's metadata

A warning is issued if the version is lower than the version in github

Public Instance Methods

checkout_tag(version) click to toggle source

If a tag is available in github check it out Potentially quite dangerous as it could cause code to get rolled back @param version [String] Version

# File lib/chef/knife/github_deploy.rb, line 276
def checkout_tag(version)
    ui.info "Checking out tag #{version}"
    cookbook_path = get_cookbook_path(@cookbook_name)
    Dir.chdir(cookbook_path);
            `git checkout -b #{version}`
            if !$?.exitstatus == 0
               ui.error("Failed to checkout branch #{version} of #{@cookbook_name}")
               exit 1
    end
end
choose_version(version) click to toggle source

Increment the current version according to the config options config config config Method will exit if the user chooses not to increment the version

@param version [String] Version @return [String] New version number

# File lib/chef/knife/github_deploy.rb, line 233
def choose_version(version)
    if version =~ /(\d+)\.(\d+)\.(\d+)/
       major = $1
       minor = $2
       patch = $3
       if config[:major]
         major = major.to_i + 1 
         minor = 0 
         patch = 0 
       end
       if config[:minor] 
        minor = minor.to_i + 1 
        patch = 0 
       end
       patch = patch.to_i + 1 if config[:patch]
       version = "#{major}.#{minor}.#{patch}"
       Chef::Log.debug("New version is #{version}")
    else
       Chef::Log.error("Version is in a format I cannot auto-update")
       exit 1
    end
    version
end
cookbook_upload() click to toggle source

Upload the cookbook to chef server If mode is final, freeze the cookbook

# File lib/chef/knife/github_deploy.rb, line 259
def cookbook_upload() 
    # Git meuk should not be uploaded use chefignore file instead
    # FileUtils.remove_entry("#{@github_tmp}/git/#{@cookbook_name}/.git")
            args = ['cookbook', 'upload',  @cookbook_name ]
    if config[:final]
        args.push "--freeze"
    end
    upload = Chef::Knife::CookbookUpload.new(args)
    # upload.config[:cookbook_path] = "#{@github_tmp}/git"
    # plugin will throw its own errors
    upload.run
end
do_commit(version, push) click to toggle source

Commit changes in git @param version [String] cookbook version @param push [Bool] true is the cookbook should also be pushed

# File lib/chef/knife/github_deploy.rb, line 299
def do_commit(version, push)
    cookbook_path = get_cookbook_path(@cookbook_path)
    Dir.chdir("#{cookbook_path}")
    output = `git commit -a -m "Deploy #{version}" 2>&1`
    if $?.exitstatus != 0
       if output !~ /nothing to commit/
          Chef::Log.error("Could not commit #{@cookbook_name}")
          puts output
          exit 1
       end
    end
    if push
        output = `git push --tags 2>&1`
        if $?.exitstatus != 0
           Chef::Log.error("Could not push tag for: #{@cookbook_name}")
           exit 1
        end
        output = `git push 2>&1`
    end
end
get_cookbook_chef_versions() click to toggle source

Get a sorted array of version for the cookbook

# File lib/chef/knife/github_deploy.rb, line 288
def get_cookbook_chef_versions ()
    cookbooks = rest.get_rest("/cookbooks/#{@cookbook_name}?num_version=all")
    cookbooks[@cookbook_name]['versions'].each do |v|
        @versions.push v['version']
    end
end
run() click to toggle source
# File lib/chef/knife/github_deploy.rb, line 93
def run
  # Main run entry point for the class

  validate_base_options      

  display_debug_info

  # Gather all repo information from github.
  get_all_repos = get_all_repos(@github_organizations.reverse)

  # Get all chef cookbooks and versions (hopefully chef does the error handeling).
  cookbooks = rest.get_rest("/cookbooks?num_version=1")

  @versions = []
  cookbook_version = nil
  @cookbook_name = name_args.first unless name_args.empty?
  cookbook_version = name_args[1] unless name_args[1].nil?

  if @cookbook_name
    repo = get_all_repos.select { |k,v| v["name"] == @cookbook_name }
  else
    Chef::Log.error("Cookbook not in git.  You must add it to git to use deploy")
    exit 1
  end

  if repo.empty?
    Chef::Log.error("Cookbook not in git.  You must add it to git to use deploy")
    exit 1
  end

  # is the cookbook in the cookbook_path?
  if get_cookbook_path(@cookbook_name).nil?
    Chef::Log.error("Cookbook is not in cookbook path")
    ui.info("HINT: knife github clone #{@cookbook_name}")
    exit 1
  end

  # ----------------------------- #
  # The version can come
  # 1.  From the command line
  # 2.  From the cookbook
  # ----------------------------- #
  if cookbook_version.nil?
     cookbook_version = get_cookbook_version()
  end
  # Next check to see if the version in git is way ahead
  if ! get_all_repos[@cookbook_name]['latest_tag'].nil?
      cb1 = Mixlib::Versioning.parse(cookbook_version)
      cb2 = Mixlib::Versioning.parse(get_all_repos[@cookbook_name]['latest_tag'])
      if(cb2 > cb1)
          ui.confirm "Version in github #{cb2} is greater than the version you want to deploy #{cb1} - Continue"
      end
  end

  inChef = true
  isFrozen = false
  if (config[:major] || config[:minor])
      config[:patch] = false
  end
  if (config[:major] && config[:minor])
      config[:minor] = false
  end

  begin
      isFrozen = rest.get_rest("cookbooks/#{@cookbook_name}/#{cookbook_version}").frozen_version?
  rescue
      ui.warn "#{@cookbook_name} is not yet in chef"
      inChef = false
  end

  
  if config[:final]
      ui.info "Using Final mode"

  else
      ui.info "Using Development mode"
  end
  ui.info "Cookbook is frozen" if isFrozen

  # Might be first upload so need to catch that cookbook does not exist!
  get_cookbook_chef_versions()  unless ! inChef

  if config[:final]
      cookbook_version = up_version(cookbook_version)

      if repo[@cookbook_name]['tags'].select { |k| k['name'] == cookbook_version }.empty?
          ui.info("Cookbook #{cookbook_version} has no tag in Git")
          ui.confirm("Shall I add a tag for you?")
          set_cookbook_version(cookbook_version)
          add_tag(cookbook_version)
      else
          ui.confirm("Tag #{cookbook_version} exists - did you make this for this release?")
          checkout_tag(cookbook_version)
          set_cookbook_version(cookbook_version)
      end

      do_commit(cookbook_version, true)
  end

  # In Dev mode the version of the cookbook does not need to change
  # If however the cookbook is frozen, then the version has to change
  if ! config[:final] && isFrozen
      cookbook_version = up_version(cookbook_version)
      set_cookbook_version(cookbook_version)
      do_commit(cookbook_version, false)
  end

  # If we have gotten this far we can just upload the cookbook
  cookbook_upload()

end
set_cookbook_version(version) click to toggle source

Set the version in metadata.rb @param version [String] cookbook version

# File lib/chef/knife/github_deploy.rb, line 323
def set_cookbook_version(version)
    return  unless get_cookbook_version() != version
    contents = ''
    cookbook_path = get_cookbook_path(@cookbook_name)
    File.foreach("#{cookbook_path}/metadata.rb") do |line|
        line.gsub!(/(version[\t\s]+)(.*)/i,"\\1 \"#{version}\"\n")
        contents = contents << line
    end
    File.open("#{cookbook_path}/metadata.rb", 'w') {|f| f.write(contents) }
    return true
end
up_version(version) click to toggle source

Ask user to increment current/desired version number Method will exit if the user chooses not to increment the version

@param version [String] Version @return [String] New version number

# File lib/chef/knife/github_deploy.rb, line 211
def up_version(version)
    while true do
          ui.info("Trying to deploy version #{version}")
          if @versions.include?(version)
             ui.info("Version #{version} is already in chef")
             vt = choose_version(version)
             ui.confirm("Shall I bump the version to #{vt} (No to Cancel)")
             version = choose_version(version)
          else
             break
          end
    end
    version
end