diff --git a/between-meals/MOVED b/between-meals/MOVED new file mode 100644 index 0000000..b936e42 --- /dev/null +++ b/between-meals/MOVED @@ -0,0 +1 @@ +Moved to http://github.com/facebook/between-meals diff --git a/between-meals/README.md b/between-meals/README.md deleted file mode 100644 index f59deee..0000000 --- a/between-meals/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Between Meals - -## Intro -Ohai! - -Between Meals is the library for calculating what Chef objects where modified -between two revisions in a version control system. It is also the library -that that backs Taste Tester and Grocery Delivery. - -It currently supports SVN and GIT, but plugins can easily be written for -other systems. - -It also includes some wrappers around knife execution and a few other utility -functions. - -## Dependencies - -* Colorize -* JSON -* Mixlib::ShellOut -* Rugged - diff --git a/between-meals/changes/change.rb b/between-meals/changes/change.rb deleted file mode 100644 index 61a6708..0000000 --- a/between-meals/changes/change.rb +++ /dev/null @@ -1,46 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -# rubocop:disable ClassVars - -module BetweenMeals - # A set of classes that represent a given item's change (a cookbook - # that's changed, a role that's changed or a databag item that's changed). - # - # You almost certainly don't want to use this directly, and instead want - # BetweenMeals::Changeset - module Changes - # Common functionality - class Change - @@logger = nil - attr_accessor :name, :status - def to_s - @name - end - - # People who use us through find() can just pass in logger, - # for everyone else, here's a setter - def logger=(log) - @@logger = log - end - - def self.info(msg) - if @@logger - @@logger.info(msg) - end - end - - def self.debug(msg) - if @@logger - @@logger.debug(msg) - end - end - - def info(msg) - BetweenMeals::Changes::Change.info(msg) - end - - def debug(msg) - BetweenMeals::Changes::Change.debug(msg) - end - end - end -end diff --git a/between-meals/changes/cookbook.rb b/between-meals/changes/cookbook.rb deleted file mode 100644 index 1b928d2..0000000 --- a/between-meals/changes/cookbook.rb +++ /dev/null @@ -1,82 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -# rubocop:disable ClassVars - -module BetweenMeals - module Changes - # Changeset aware cookbook - class Cookbook < Change - def self.meaningful_cookbook_file?(path, cookbook_dirs) - cookbook_dirs.each do |dir| - re = %r{^#{dir}/([^/]+)/.*} - m = path.match(re) - debug("[cookbook] #{path} meaningful? [#{re}]: #{m}") - return true if m - end - false - end - - def self.explode_path(path, cookbook_dirs) - cookbook_dirs.each do |dir| - re = %r{^#{dir}/([^/]+)/.*} - debug("[cookbook] Matching #{path} against ^#{re}") - m = path.match(re) - next unless m - info("Cookbook is #{m[1]}") - return { - :cookbook_dir => dir, - :name => m[1] } - end - nil - end - - def initialize(files, cookbook_dirs) - @files = files - @name = self.class.explode_path( - files.sample[:path], - cookbook_dirs - )[:name] - # if metadata.rb is being deleted - # cookbook is marked for deletion - # otherwise it was modified - # and will be re-uploaded - if files. - select { |x| x[:status] == :deleted }. - map { |x| x[:path].match(%{.*metadata\.rb$}) }. - compact. - any? - @status = :deleted - else - @status = :modified - end - end - - # Given a list of changed files - # create a list of Cookbook objects - def self.find(list, cookbook_dirs, logger) - @@logger = logger - return [] if list.nil? || list.empty? - # rubocop:disable MultilineBlockChain - list. - group_by do |x| - # Group by prefix of cookbok_dir + cookbook_name - # so that we treat deletes and modifications across - # two locations separately - g = self.explode_path(x[:path], cookbook_dirs) - g[:cookbook_dir] + '/' + g[:name] if g - end. - map do |_, change| - # Confirm we're dealing with a cookbook - # Changes to OWNERS or other stuff that might end up - # in [core, other, secure] dirs are ignored - is_cookbook = change.select do |c| - self.meaningful_cookbook_file?(c[:path], cookbook_dirs) - end.any? - if is_cookbook - BetweenMeals::Changes::Cookbook.new(change, cookbook_dirs) - end - end.compact - # rubocop:enable MultilineBlockChain - end - end - end -end diff --git a/between-meals/changes/databag.rb b/between-meals/changes/databag.rb deleted file mode 100644 index a26512f..0000000 --- a/between-meals/changes/databag.rb +++ /dev/null @@ -1,36 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -# rubocop:disable ClassVars - -module BetweenMeals - module Changes - # Changeset aware databag - class Databag < Change - attr_accessor :item - def self.name_from_path(path, databag_dir) - re = %r{^#{databag_dir}/([^/]+)/([^/]+)\.json} - debug("[databag] Matching #{path} against #{re}") - m = path.match(re) - if m - info("Databag is #{m[1]} item is #{m[2]}") - return m[1], m[2] - end - nil - end - - def initialize(file, databag_dir) - @status = file[:status] - @name, @item = self.class.name_from_path(file[:path], databag_dir) - end - - def self.find(list, databag_dir, logger) - @@logger = logger - return [] if list.nil? || list.empty? - list. - select { |x| self.name_from_path(x[:path], databag_dir) }. - map do |x| - BetweenMeals::Changes::Databag.new(x, databag_dir) - end - end - end - end -end diff --git a/between-meals/changes/role.rb b/between-meals/changes/role.rb deleted file mode 100644 index 5e0445d..0000000 --- a/between-meals/changes/role.rb +++ /dev/null @@ -1,37 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -# rubocop:disable ClassVars - -module BetweenMeals - module Changes - # Changeset aware role - class Role < Change - def self.name_from_path(path, role_dir) - re = "^#{role_dir}\/(.+)\.rb" - debug("[role] Matching #{path} against #{re}") - m = path.match(re) - if m - info("Name is #{m[1]}") - return m[1] - end - nil - end - - def initialize(file, role_dir) - @status = file[:status] == :deleted ? :deleted : :modified - @name = self.class.name_from_path(file[:path], role_dir) - end - - # Given a list of changed files - # create a list of Role objects - def self.find(list, role_dir, logger) - @@logger = logger - return [] if list.nil? || list.empty? - list. - select { |x| self.name_from_path(x[:path], role_dir) }. - map do |x| - BetweenMeals::Changes::Role.new(x, role_dir) - end - end - end - end -end diff --git a/between-meals/changeset.rb b/between-meals/changeset.rb deleted file mode 100644 index fa27545..0000000 --- a/between-meals/changeset.rb +++ /dev/null @@ -1,48 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -require_relative 'changes/change' -require_relative 'changes/cookbook' -require_relative 'changes/role' -require_relative 'changes/databag' - -module BetweenMeals - # Convenience for dealing with changes - # Represents a list of diffs between two revisions - # as a series of Cookbook and Role objects - # - # Basically, you always want to use BetweenMeals::Changes through this - # helper class. - class Changeset - class ReferenceError < Exception - end - - def initialize(logger, repo, start_ref, end_ref, locations) - @logger = logger - @repo = repo - @cookbook_dirs = locations[:cookbook_dirs].dup - @role_dir = locations[:role_dir] - @databag_dir = locations[:databag_dir] - # Figure out which files changed if refs provided - # or return all files (full upload) otherwise - if start_ref - @files = [] - @repo.changes(start_ref, end_ref).each do |file| - @files << file - end - else - @files = @repo.files - end - end - - def cookbooks - BetweenMeals::Changes::Cookbook.find(@files, @cookbook_dirs, @logger) - end - - def roles - BetweenMeals::Changes::Role.find(@files, @role_dir, @logger) - end - - def databags - BetweenMeals::Changes::Databag.find(@files, @databag_dir, @logger) - end - end -end diff --git a/between-meals/knife.rb b/between-meals/knife.rb deleted file mode 100755 index fb09e6f..0000000 --- a/between-meals/knife.rb +++ /dev/null @@ -1,197 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'json' -require 'fileutils' -require 'digest/md5' -require_relative 'util' - -module BetweenMeals - # Knife does not have a usable API for using it as a lib - # This could be possibly refactored to touch its internals - # instead of shelling out - class Knife - include BetweenMeals::Util - - def initialize(opts = {}) - @logger = opts[:logger] || nil - @user = opts[:user] || ENV['USER'] - @home = opts[:home] || ENV['HOME'] - @host = opts[:host] || 'localhost' - @port = opts[:port] || 4000 - @config = opts[:config] || - "#{@home}/.chef/knife-#{@user}-taste-tester.rb" - @knife = opts[:bin] || 'knife' - @pem = opts[:pem] || - "#{@home}/.chef/#{@user}-taste-tester.pem" - @role_dir = opts[:role_dir] - @cookbook_dirs = opts[:cookbook_dirs] - @databag_dir = opts[:databag_dir] - @checksum_dir = opts[:checksum_dir] - @client_key = - File.expand_path("#{@home}/.chef/#{@user}-taste-tester.pem") - end - - def role_upload_all - roles = File.join(@role_dir, '*.rb') - exec!("#{@knife} role from file #{roles} -c #{@config}", @logger) - end - - def role_upload(roles) - if roles.any? - roles = roles.map { |x| File.join(@role_dir, "#{x.name}.rb") }.join(' ') - exec!("#{@knife} role from file #{roles} -c #{@config}", @logger) - end - end - - def role_delete(roles) - if roles.any? - roles.each do |role| - exec!( - "#{@knife} role delete #{role.name} --yes -c #{@config}", @logger - ) - end - end - end - - def cookbook_upload_all - exec!("#{@knife} cookbook upload -a -c #{@config}", @logger) - end - - def cookbook_upload(cookbooks) - if cookbooks.any? - cookbooks = cookbooks.map { |x| x.name }.join(' ') - exec!("#{@knife} cookbook upload #{cookbooks} -c #{@config}", @logger) - end - end - - def cookbook_delete(cookbooks) - if cookbooks.any? - cookbooks.each do |cookbook| - exec!("#{@knife} cookbook delete #{cookbook.name}" + - " --purge --yes -c #{@config}", @logger) - end - end - end - - def databag_upload_all - glob = File.join(@databag_dir, '*', '*.json') - items = Dir.glob(glob).map do |file| - BetweenMeals::Changes::Databag.new( - { :status => :modified, :path => file }, @databag_dir - ) - end - databag_upload(items) - end - - def databag_upload(databags) - if databags.any? - databags.group_by { |x| x.name }.each do |dbname, dbs| - create_databag_if_missing(dbname) - dbitems = dbs.map do |x| - File.join(@databag_dir, dbname, "#{x.item}.json") - end.join(' ') - exec!("#{@knife} data bag from file #{dbname} #{dbitems}", @logger) - end - end - end - - def databag_delete(databags) - if databags.any? - databags.group_by { |x| x.name }.each do |dbname, dbs| - dbs.each do |db| - exec!("#{@knife} data bag delete #{dbname} #{db.item}" + - " --yes -c #{@config}", @logger) - end - delete_databag_if_empty(dbname) - end - end - end - - def write_user_config - cfg = <<-BLOCK -user = ENV['USER'] -log_level :info -log_location STDOUT -node_name user -chef_server_url "http://#{@host}:#{@port}" -cache_type 'BasicFile' -client_key '#{@client_key}' -cache_options(:path => File.expand_path("#{@checksum_dir}")) -cookbook_path [ -BLOCK - @cookbook_dirs.each do |dir| - cfg << " \"#{dir}\",\n" - end - cfg << "]\n" - unless File.directory?(File.dirname(@config)) - Dir.mkdir(File.dirname(@config), 0755) - end - if !File.exists?(@config) || - ::Digest::MD5.hexdigest(cfg) != - ::Digest::MD5.hexdigest(File.read(@config)) - @logger.info("Generating #{@config}") - File.write(@config, cfg) - end - - # Won't work with shorter keys - pem = <<-BLOCK ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCs4Ih8+R/2hcYS -tccwJHd0cXHcUibC2wGYmRwf1fKxXLADvfuLRVBHOI5Hgd/ZXF70dowC5mDQ03gr -ouk8e7RL72MCKzPuG2V92sh/FnyKhkNsHCOEKaRRiP9lHVbZkS9LEotCKF7eOkL0 -SVkGWx8pVZzrOFmhZgHaOFJ2/2t1irUTRFqTikrRsP2KvnhHdDlnnbUumZWxSuEN -oN6aSAQEOkKbEOLSn/EIMzEb2jtks7L7wkRErajH094jGoZbQvLiwRHDeM0C9uG7 -2sdQ45BG9EQOCdBzy1We5keqtJbXBcpwuBa0d1nQZIsGxnDb88+Kmh9h6k9/WmYN -zEQEeSSdAgMBAAECggEATFWQru4p6ObEwTo2y9EuVeJJzmkP6HZfzAu/WWdVFG/C -4MQgsCxY+DnGyVhViVq6KuO1iwpCsbLOmyYCKszMncMESs7czUSXmezjHwrEzz3d -w3zhSdhBUCdX7kP4N3VeFp4Hk5zT1viO2+MPRjkyF0RQV6S4HwY1xy+baiP6RRnS -dGhUYsdz6fjxSkYEQy3/xHm9VLT6ZDV4pN2aA+LOFeveHKcnOjKFCBy4WzkO6fvj -6H3jghxsHXoL7loCHfi9WX3xKjeXG/NjGbUfTH8P7IldUPha+ru/e8W/P+jjE1os -VkScWt08Vu6iTl1EkYeFxOMtSDZxeXNnDkPI0iDQgQKBgQDUMFYncQcZ4uLIfoZq -B+Lx7WJGlIwulOdobnraTd5lsrBHj1sC1+/f4cBSpJwUij2l3GdmmnOpuFAof5eu -mrBGu++5jy+0eIeT5O2d30O8GOBryJ+oAKI2/BPVCmM8d986wl5Esauycb++O7UO -RhpZFOCKbFvlNjhg+CdlvHSl7QKBgQDQkkvpnE//yWmhCPg27n7w3bTg5QPNrzTO -pF2iwvLK4XjRceTeW3P4f42HONzJNnmt5TexM9NbdE9g/exA5uNt59ZB5FeFiKAu -NmVXbmswPX6R/dlyidqzz1guGrL04e0dZehHZBNDr5Sio8IBjMWrpDIxjDJqEwUa -4qCu4e6jcQKBgQDN0FTAzRFmOnxenNsj3aJzpx27+DpAtI4A7aicNwuQ+VGjF5nf -mDRDpGU3xBLgmXZSewaQrx+hb/XQUnJ+Ge0BrylHg2tyUbav7U3N49F/kWGdKmwy -OOsfCkLyUbEP5fXQuNdXKj6wR0UE8EUeI0FLRsTFf3VjTsRAynLsa295wQKBgAo3 -QDSfDWQP73aNw+qc3+bYVSW20erfLAz7DAMO3WmGha5sj7M8c3+2b64x4M6SNn+H -/KRXT4DpP4IWrd238WfOtTXhA1BtErtwuqH/rIxeVra74kyz59xqyXzond9UuZJ5 -DVmB01e7X+Jfdv8wb/YqQrMelNGRQOzCMPCf7FphAoGAbUh5HzNF2aciQJGA6Qk8 -zvgEHqbS0/QkJGOZ+UifPRanTDuGYQkPdHHOER4UghbM+Kz5rZbBicJ3bCyNOsah -IAMAEpsWX2s2A6phgMCx7kH6wMmoZn3hb7Thh9+PfR8Jtp2/7k+ibCeF4gEWUCs5 -6wX4GR84dwyhG80yd4TP8Qo= ------END PRIVATE KEY----- - BLOCK - - unless File.exists?(@pem) - @logger.info("Generating #{@pem}") - File.write(@pem, pem) - end - end - - private - - def create_databag_if_missing(databag) - s = Mixlib::ShellOut.new("#{@knife} data bag list" + - " --format json -c #{@config}").run_command - s.error! - db = JSON.load(s.stdout) - unless db.include?(databag) - exec!("#{@knife} data bag create #{databag} -c #{@config}", @logger) - end - end - - def delete_databag_if_empty(databag) - s = Mixlib::ShellOut.new("#{@knife} data bag show #{databag}" + - " --format json -c #{@config}").run_command - s.error! - db = JSON.load(s.stdout) - if db.empty? - exec!("#{@knife} data bag delete #{databag} --yes -c #{@config}", - @logger) - end - end - end -end diff --git a/between-meals/repo.rb b/between-meals/repo.rb deleted file mode 100755 index 65d995e..0000000 --- a/between-meals/repo.rb +++ /dev/null @@ -1,113 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'mixlib/shellout' - -module BetweenMeals - # Local checkout wrapper - class Repo - attr_reader :repo_path - attr_writer :bin - - def initialize(repo_path, logger) - @repo_path = repo_path - @logger = logger - @repo = nil - @bin = nil - setup - rescue - @logger.warn("Unable to read repo from #{File.expand_path(repo_path)}") - exit(1) - end - - def self.get(type, repo_path, logger) - case type - when 'svn' - require_relative 'repo/svn' - BetweenMeals::Repo::Svn.new(repo_path, logger) - when 'git' - require_relative 'repo/git' - BetweenMeals::Repo::Git.new(repo_path, logger) - else - fail "Do not know repo type #{type}" - end - end - - def exists? - fail 'Not implemented' - end - - def status - fail 'Not implemented' - end - - def setup - fail 'Not implemented' - end - - def head_rev - fail 'Not implemented' - end - - def head_msg - fail 'Not implemented' - end - - def head_msg= - fail 'Not implemented' - end - - def head_parents - fail 'Not implemented' - end - - def latest_revision - fail 'Not implemented' - end - - def create(_url) - fail 'Not implemented' - end - - # Return files changed between two revisions - def changes(_start_ref, _end_ref) - fail 'Not implemented' - end - - def update - fail 'Not implemented' - end - - # Return all files - def files - fail 'Not implemented' - end - - def latest_revision - fail 'Not implemented' - end - - def head - fail 'Not implemented' - end - - def checkout - fail 'Not implemented' - end - - def update - fail 'Not implemented' - end - - def last_author - fail 'Not implemented' - end - - def last_msg - fail 'Not implemented' - end - - def last_msg= - fail 'Not implemented' - end - end -end diff --git a/between-meals/repo/git.rb b/between-meals/repo/git.rb deleted file mode 100755 index 535e700..0000000 --- a/between-meals/repo/git.rb +++ /dev/null @@ -1,204 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'rugged' -require 'mixlib/shellout' -require_relative '../changeset' - -module BetweenMeals - # Local checkout wrapper - class Repo - # Git provider - class Git < BetweenMeals::Repo - def setup - if File.exists?(File.expand_path(@repo_path)) - @repo = Rugged::Repository.new(File.expand_path(@repo_path)) - else - @repo = nil - end - @bin = 'git' - end - - def exists? - @repo && !@repo.empty? - end - - def head_rev - @repo.head.target.oid - end - - def last_msg - @repo.head.target.message - end - - def last_msg=(msg) - @repo.head.target.amend( - { - :message => msg, - :update_ref => 'HEAD', - } - ) - end - - def last_author - @repo.head.target.to_hash[:author] - end - - def head_parents - @repo.head.target.parents - end - - def checkout(url) - s = Mixlib::ShellOut.new( - "#{@bin} clone #{url} #{@repo} #{@repo_path}" - ).run_command - s.error! - @repo = Rugged::Repository.new(File.expand_path(@repo_path)) - end - - # Return files changed between two revisions - def changes(start_ref, end_ref) - check_refs(start_ref, end_ref) - s = Mixlib::ShellOut.new( - "#{@bin} diff --name-status #{start_ref} #{end_ref}", - :cwd => File.expand_path(@repo_path) - ) - s.run_command.error! - begin - parse_status(s.stdout).compact - rescue => e - # We've seen some weird non-reproducible failures here - @logger.error( - 'Something went wrong. Please please report this output.' - ) - @logger.error(e) - s.stdout.lines.each do |line| - @logger.error(line.strip) - end - exit(1) - end - end - - def update - cmd = Mixlib::ShellOut.new( - "#{@bin} pull --rebase", :cwd => File.expand_path(@repo_path) - ) - cmd.run_command - if cmd.exitstatus != 0 - @logger.error('Something went wrong with git!') - @logger.error(cmd.stdout) - fail - end - cmd.stdout - end - - # Return all files - def files - @repo.index.map { |x| { :path => x[:path], :status => :created } } - end - - def status - cmd = Mixlib::ShellOut.new( - "#{@bin} status --porcelain 2>&1", - :cwd => File.expand_path(@repo_path) - ) - cmd.run_command - if cmd.exitstatus != 0 - @logger.error('Something went wrong with git!') - @logger.error(cmd.stdout) - fail - end - cmd.stdout - end - - private - - def check_refs(start_ref, end_ref) - unless @repo.exists?(start_ref) - fail Changeset::ReferenceError - end - unless end_ref.nil? - unless @repo.exists?(end_ref) - fail Changeset::ReferenceError - end - end - end - - def parse_status(changes) - # man git-diff-files - # Possible status letters are: - # - # A: addition of a file - # C: copy of a file into a new one - # D: deletion of a file - # M: modification of the contents or mode of a file - # R: renaming of a file - # T: change in the type of the file - # U: file is unmerged (you must complete the merge before it can - # be committed) - # X: "unknown" change type (most probably a bug, please report it) - - # rubocop:disable MultilineBlockChain - changes.lines.map do |line| - case line - when /^A\s+(\S+)$/ - # A path - { - :status => :modified, - :path => Regexp.last_match(1) - } - when /^C(?:\d*)\s+(\S+)\s+(\S+)/ - # C path1 path2 - { - :status => :modified, - :path => Regexp.last_match(2) - } - when /^D\s+(\S+)$/ - # D path - { - :status => :deleted, - :path => Regexp.last_match(1) - } - when /^M(?:\d*)\s+(\S+)$/ - # M path - { - :status => :modified, - :path => Regexp.last_match(1) - } - when /^R(?:\d*)\s+(\S+)\s+(\S+)/ - # R path1 path2 - [ - { - :status => :deleted, - :path => Regexp.last_match(1) - }, - { - :status => :modified, - :path => Regexp.last_match(2) - } - ] - when /^T\s+(\S+)$/ - # T path - [ - { - :status => :deleted, - :path => Regexp.last_match(1) - }, - { - :status => :modified, - :path => Regexp.last_match(1) - } - ] - else - fail 'No match' - end - end.flatten.map do |x| - { - :status => x[:status], - :path => x[:path].sub("#{@repo_path}/", '') - } - end - # rubocop:enable MultilineBlockChain - end - end - end -end diff --git a/between-meals/repo/svn.rb b/between-meals/repo/svn.rb deleted file mode 100755 index 303b265..0000000 --- a/between-meals/repo/svn.rb +++ /dev/null @@ -1,121 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require_relative '../repo' -require_relative '../changeset' -require 'mixlib/shellout' - -module BetweenMeals - # Local checkout wrapper - class Repo - # SVN implementation - class Svn < BetweenMeals::Repo - def setup - @bin = 'svn' - end - - def exists? - # this shuold be better - Dir.exists?(@repo_path) - end - - def head_rev - s = Mixlib::ShellOut.new("#{@bin} info #{@repo_path}").run_command - s.error! - s.stdout.each_line do |line| - m = line.match(/Last Changed Rev: (\d+)$/) - return m[1] if m - end - end - - def latest_revision - s = Mixlib::ShellOut.new("#{@bin} info #{@repo_path}").run_command - s.error! - s.stdout.each do |line| - m = line.match(/Revision: (\d+)$/) - return m[1] if m - end - end - - def checkout(url) - s = Mixlib::ShellOut.new( - "#{@bin} co --ignore-externals #{url} #{@repo_path}").run_command - s.error! - end - - # Return files changed between two revisions - def changes(start_ref, end_ref) - check_refs(start_ref, end_ref) - s = Mixlib::ShellOut.new( - "#{@bin} diff -r #{start_ref}:#{end_ref} --summarize #{@repo_path}") - s.run_command.error! - @logger.info("Diff between #{start_ref} and #{end_ref}") - s.stdout.lines.map do |line| - m = line.match(/^(\w)\w?\s+(\S+)$/) - fail "Could not parse line: #{line}" unless m - - { - :status => m[1] == 'D' ? :deleted : :modified, - :path => m[2].sub("#{@repo_path}/", '') - } - end - end - - def update - cleanup - revert - up - end - - # Return all files - def files - s = Mixlib::ShellOut.new("#{@bin} ls --depth infinity #{@repo_path}") - s.run_command - s.error! - s.stdout.split("\n").map do |x| - { :path => x, :status => :created } - end - end - - private - - def run(cmd) - Mixlib::ShellOut.new(cmd).run_command.error! - end - - def revert - run("#{@bin} revert -R #{@repo_path}") - end - - def up - run("#{@bin} update #{@repo_path}") - end - - def cleanup - run("#{@bin} cleanup #{@repo_path}") - end - - def first_revision - 0 - end - - private - - def check_refs(start_ref, end_ref) - s = Mixlib::ShellOut.new( - "#{@bin} info -r #{start_ref}", - :cwd => @repo_path - ).run_command - s.error! - if end_ref - s = Mixlib::ShellOut.new( - "#{@bin} info -r #{end_ref}", - :cwd => @repo_path - ).run_command - s.error! - end - rescue - raise Changeset::ReferenceError - end - end - end -end diff --git a/between-meals/util.rb b/between-meals/util.rb deleted file mode 100644 index fa051e7..0000000 --- a/between-meals/util.rb +++ /dev/null @@ -1,74 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'colorize' -require 'socket' -require 'timeout' - -module BetweenMeals - # A set of simple utility functions used throughout BetweenMeals - # - # Feel freeo to use... note that if you pass in a logger once - # you don't need to again, but be safe and always pass one in. :) - - # Util classes need class vars :) - # rubocop:disable ClassVars - module Util - @@logger = nil - - def time(logger = nil) - @@logger = logger if logger - t0 = Time.now - yield - info("Executed in #{format('%.2f', Time.now - t0)}s") - end - - def exec!(command, logger = nil) - @@logger = logger if logger - c = execute(command) - c.error! - return c.status.exitstatus, c.stdout - end - - def exec(command, logger = nil) - @@logger = logger if logger - c = execute(command) - return c.status.exitstatus, c.stdout - end - - private - - def info(msg) - @@logger.info(msg) if @@logger - end - - def execute(command) - info("Running: #{command}") - c = Mixlib::ShellOut.new(command) - c.run_command - c.stdout.lines.each do |line| - info("STDOUT: #{line.strip}") - end - c.stderr.lines.each do |line| - info("STDERR: #{line.strip.red}") - end - return c - end - - def port_open?(port) - begin - Timeout.timeout(1) do - begin - s = TCPSocket.new('localhost', port) - s.close - return true - rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH - return false - end - end - rescue Timeout::Error - return false - end - return false - end - end -end diff --git a/grocery-delivery/MOVED b/grocery-delivery/MOVED new file mode 100644 index 0000000..cdc0096 --- /dev/null +++ b/grocery-delivery/MOVED @@ -0,0 +1 @@ +Moved to http://github.com/facebook/grocery-delivery diff --git a/grocery-delivery/README.md b/grocery-delivery/README.md deleted file mode 100644 index e473ad0..0000000 --- a/grocery-delivery/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# Grocery Delivery - -## Intro -Ohai! - -Welcome to Grocery Delivery, software to keep cookbooks, roles, and databags in -sync between a VCS repo and a chef server. The idea is that if you have -multiple, distinct Chef server instances that should all be identical, they can -all run this script in cron. The script uses proper locking, so you should be -able to run it every minute. - -However, there are several things to know: -* It assumes you don't leverage versions or environments. -* It assumes you want anything committed to HEAD to be uploaded immediately. - -Grocery Delivery is pretty customizable. Many things can be tuned from a simple -config file, and it's pluggable so you can extend it as well. - -## Prerequisites - -Grocery Delivery is a particular way of managing your Chef infrastructure, -and it assumes you follow that model consistently. Here are the basic -principals: - -* Checkins are live immediately (which implies code review before merge) -* Versions are meaningless (ideally, never change them) -* You want all your chef-servers in sync -* Everything you care about comes from version control. - -We recommend using the whitelist_node_attrs -(https://github.com/opscode/whitelist-node-attrs) cookbook to prevent node -attributes being saved back to the server. Or in recent versions of Chef 11, -this feature is built-in: - -http://docs.getchef.com/essentials_node_object.html#whitelist-attributes - -## Dependencies - -* Mixlib::Config -* BetweenMeals - -## Config file - -The default config file is `/etc/gd-config.rb` but you may use -c to specify -another. The config file works the same as client.rb does for Chef - there -are a series of keywords that take an arguement and anything else is just -standard Ruby. - -All command-line options are available in the config file: -* dry_run (bool, default: false) -* debug (bool, default: false) -* timestamp (bool, default: false) -* config_file (string, default: `/etc/gd-config.rb`) -* lockfile (string, default: `/var/lock/subsys/grocery_delivery`) -* pidfile (string, default: `/var/run/grocery_delivery.pid`) - -In addition the following are also available: -* master_path - The top-level path for Grocery Delivery's work. Most other - paths are relative to this. Default: `/var/chef/grocery_delivery_work` -* repo_url - The URL to clone/checkout if it doesn't exist. Default: `nil` -* reponame - The relative directory to check the repo out to, inside of - `master_path`. Default: `ops` -* cookbook_paths - An array of directories that contain cookbooks relative to - `reponame`. Default: `['chef/cookbooks']` -* role_path - A directory to find roles in relative to `reponame`. Default: - `['chef/roles']` -* databag_path - A directory to find databags in relative to `reponame`. - Default: `['chef/databags']` -* rev_checkpoint - Name of the file to store the last-uploaded revision, - relative to `reponame`. Default: `gd_revision` -* knife_config - Knife config to use for uploads. Default: - `/root/.chef/knife.rb` -* knife_bin - Path to knife. Default: `/opt/chef/bin/knife` -* vcs_type - Git or SVN? Default: `svn` -* vcs_path - Path to git or svn binary. If not given, just uses 'git' or 'svn'. - Default: `nil` -* plugin_path - Path to plugin file. Default: `/etc/gd-plugin.rb` - -## Plugin - -The plugin should be a ruby file which defines several class methods. It is -class_eval()d into a Hooks class. - -The following functions can optionally be defined: - -* self.preflight_checks(dryrun) - -This code will run once we've read our config and loaded our plugins but before -*anything* else. We don't even have a lock yet. `Dryrun` is a bool which -indicates if we are in dryrun mode. - -* self.prerun(dryrun) - -This is run after we've gotten a lock, written a pidfile and initialized our -repo object (but not touched the repo yet) - -* self.post_repo_up(dryrun) - -This is code to run after we've updated the repo, but before we've done any work -to parse it. - -* self.postrun(dryrun, success, msg) - -After we've parsed the updates to the repo and uploaded/deleted the relevent -items from the local server. `Success` is a bool for whether we succeeded, and -`msg` is the status message - either the revision we sync'd or an error. - -* self.atexit(dryrun, success, msg) - -Same as postrun, but is registered as an atexit function so it happens even -if we crash. diff --git a/grocery-delivery/grocery-delivery.rb b/grocery-delivery/grocery-delivery.rb deleted file mode 100755 index dcaa684..0000000 --- a/grocery-delivery/grocery-delivery.rb +++ /dev/null @@ -1,259 +0,0 @@ -#!/opt/chef/embedded/bin/ruby -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require_relative '../between-meals/util' -require_relative '../between-meals/knife' -require_relative '../between-meals/repo/svn' -require_relative '../between-meals/repo/git' -require_relative '../between-meals/changeset' -require_relative 'grocery-delivery/config' -require_relative 'grocery-delivery/logging' -require_relative 'grocery-delivery/hooks' -require 'optparse' -require 'logger' - -# rubocop:disable GlobalVars -$success = false -$status_msg = 'NO WORK DONE' -$lockfileh = nil - -def action(msg) - if GroceryDelivery::Config.dry_run - GroceryDelivery::Log.warn("[DRYRUN] Would do: #{msg}") - else - GroceryDelivery::Log.warn(msg) - end -end - -def get_lock - GroceryDelivery::Log.warn('Attempting to acquire lock') - $lockfileh = File.open(GroceryDelivery::Config.lockfile, - File::RDWR | File::CREAT, 0600) - $lockfileh.flock(File::LOCK_EX) - GroceryDelivery::Log.warn('Lock acquired') -end - -def write_pidfile - File.write(GroceryDelivery::Config.pidfile, Process.pid) -end - -def checkpoint_path - File.join(GroceryDelivery::Config.master_path, - GroceryDelivery::Config.rev_checkpoint) -end - -def write_checkpoint(rev) - File.write(checkpoint_path, rev) unless GroceryDelivery::Config.dry_run -end - -def read_checkpoint - GroceryDelivery::Log.debug("Reading #{checkpoint_path}") - File.exists?(checkpoint_path) ? File.read(checkpoint_path).strip : nil -end - -def full_upload(knife) - GroceryDelivery::Log.warn('Uploading all cookbooks') - knife.cookbook_upload_all - GroceryDelivery::Log.warn('Uploading all roles') - knife.role_upload_all - GroceryDelivery::Log.warn('Uploading all databags') - knife.databag_upload_all -end - -def partial_upload(knife, repo, checkpoint, local_head) - GroceryDelivery::Log.warn( - "Determing changes... from #{checkpoint} to #{local_head}" - ) - - begin - changeset = BetweenMeals::Changeset.new( - GroceryDelivery::Log, - repo, - checkpoint, - local_head, - { - :cookbook_dirs => - GroceryDelivery::Config.cookbook_paths, - :role_dir => - GroceryDelivery::Config.role_path, - :databag_dir => - GroceryDelivery::Config.databag_path, - }, - ) - rescue BetweenMeals::Changeset::ReferenceError - GroceryDelivery::Log.error('Repo error, invalid revision, exiting') - exit(2) - end - - deleted_cookbooks = changeset.cookbooks.select { |x| x.status == :deleted } - added_cookbooks = changeset.cookbooks.select { |x| x.status == :modified } - deleted_roles = changeset.roles.select { |x| x.status == :deleted } - added_roles = changeset.roles.select { |x| x.status == :modified } - deleted_databags = changeset.databags.select { |x| x.status == :deleted } - added_databags = changeset.databags.select { |x| x.status == :modified } - - { - 'Added cookbooks' => added_cookbooks, - 'Deleted cookbooks' => deleted_cookbooks, - 'Added roles' => added_roles, - 'Deleted roles' => deleted_roles, - 'Added databags' => added_databags, - 'Deleted databags' => deleted_databags, - }.each do |msg, list| - if list - GroceryDelivery::Log.warn("#{msg}: #{list}") - end - end - - knife.cookbook_delete(deleted_cookbooks) if deleted_cookbooks - knife.cookbook_upload(added_cookbooks) if added_cookbooks - knife.role_delete(deleted_roles) if deleted_roles - knife.role_upload(added_roles) if added_roles - knife.databag_delete(deleted_databags) if deleted_databags - knife.databag_upload(added_databags) if added_databags -end - -def upload_changed(repo, checkpoint) - local_head = repo.head_rev - base_dir = File.join(GroceryDelivery::Config.master_path, - GroceryDelivery::Config.reponame) - - knife = BetweenMeals::Knife.new( - { - :logger => GroceryDelivery::Log, - :config => GroceryDelivery::Config.knife_config, - :bin => GroceryDelivery::Config.knife_bin, - :role_dir => File.join(base_dir, GroceryDelivery::Config.role_path), - :cookbook_dirs => GroceryDelivery::Config.cookbook_paths.map do |x| - File.join(base_dir, x) - end, - :databag_dir => File.join(base_dir, GroceryDelivery::Config.databag_path), - } - ) - - if checkpoint - partial_upload(knife, repo, checkpoint, local_head) - else - full_upload(knife) - end - return local_head -end - -def setup_config - options = {} - OptionParser.new do |opts| - options[:config_file] = GroceryDelivery::Config.config_file - opts.on('-n', '--dry-run', 'Dryrun mode') do |s| - options[:dry_run] = s - end - opts.on('-v', '--verbosity', 'Verbosity level. Twice for debug.') do - # If -vv is supplied this block is executed twice - if options[:verbosity] - options[:verbosity] = ::Logger::DEBUG - else - options[:verbosity] = ::Logger::INFO - end - end - opts.on('-T', '--timestamp', 'Timestamp output') do |s| - options[:timestamp] = s - end - opts.on('-c', '--config-file FILE', 'config file') do |s| - unless File.exists?(File.expand_path(s)) - GroceryDelivery::Log.error("Config file #{s} not found.") - exit(2) - end - options[:config_file] = s - end - opts.on('-l', '--lockfile FILE', 'lockfile') do |s| - options[:lockfile] = s - end - opts.on('-p', '--pidfile FILE', 'pidfile') do |s| - options[:pidfile] = s - end - end.parse! - if File.exists?(File.expand_path(options[:config_file])) - GroceryDelivery::Config.from_file(options[:config_file]) - end - GroceryDelivery::Config.merge!(options) - GroceryDelivery::Log.verbosity = GroceryDelivery::Config.verbosity - if GroceryDelivery::Config.dry_run - GroceryDelivery::Log.warn('Dryrun mode activated, no changes will be made.') - end - GroceryDelivery::Hooks.get(GroceryDelivery::Config.plugin_path) - at_exit do - GroceryDelivery::Hooks.atexit(GroceryDelivery::Config.dry_run, - $success, $status_msg) - end -end - -def get_repo - repo_path = File.join(GroceryDelivery::Config.master_path, - GroceryDelivery::Config.reponame) - r = BetweenMeals::Repo.get(GroceryDelivery::Config.vcs_type, repo_path, - GroceryDelivery::Log) - if GroceryDelivery::Config.vcs_path - r.bin = GroceryDelivery::Config.vcs_path - end - r -end - -setup_config - -GroceryDelivery::Hooks.preflight_checks(GroceryDelivery::Config.dry_run) - -get_lock -write_pidfile -repo = get_repo - -GroceryDelivery::Hooks.prerun(GroceryDelivery::Config.dry_run) - -if repo.exists? - action('Updating repo') - repo.update unless GroceryDelivery::Config.dry_run -else - unless GroceryDelivery::Config.repo_url - GroceryDeliver::Log.error( - 'No repo URL was specified, and no repo is checked out' - ) - exit(1) - end - action('Cloning repo') - unless GroceryDelivery::Config.dry_run - repo.checkout(GroceryDelivery::Config.repo_url) - end -end - -GroceryDelivery::Hooks.post_repo_up(GroceryDelivery::Config.dry_run) - -if GroceryDelivery::Config.dry_run && !repo.exists? - GroceryDelivery::Log.warn( - 'In dryrun mode, with no repo, there\'s not much I can dryrun' - ) - GroceryDelivery::Hooks.postrun(GroceryDelivery::Config.dry_run, true, - 'dryrun mode') - exit -end - -checkpoint = read_checkpoint -if repo.exists? && repo.head_rev == checkpoint - GroceryDelivery::Log.warn('Repo has not changed, nothing to do...') - $success = true - $status_msg = "Success at #{checkpoint}" -else - begin - ver = upload_changed(repo, checkpoint) - write_checkpoint(ver) - $success = true - $status_msg = "Success at #{ver}" - rescue => e - $status_msg = e.message - e.backtrace.each do |line| - GroceryDelivery::Log.error(line) - end - end -end - -GroceryDelivery::Log.warn($status_msg) -GroceryDelivery::Hooks.postrun(GroceryDelivery::Config.dry_run, $success, - $status_msg) -# rubocop:enable GlobalVars diff --git a/grocery-delivery/grocery-delivery/config.rb b/grocery-delivery/grocery-delivery/config.rb deleted file mode 100644 index 3163ea8..0000000 --- a/grocery-delivery/grocery-delivery/config.rb +++ /dev/null @@ -1,32 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'mixlib/config' -require 'logger' - -module GroceryDelivery - # Config file parser and config object - # Uses Mixlib::Config v1 syntax so it works in Chef10 omnibus... - # it's compatible with v2, so it should work in 11 too. - class Config - extend Mixlib::Config - - dry_run false - verbosity Logger::WARN - timestamp false - config_file '/etc/gd-config.rb' - pidfile '/var/run/grocery_delivery.pid' - lockfile '/var/lock/subsys/grocery_delivery' - master_path '/var/chef/grocery_delivery_work' - repo_url nil - reponame 'ops' - cookbook_paths ['chef/cookbooks'] - role_path 'chef/roles' - databag_path 'chef/databags' - rev_checkpoint 'gd_revision' - knife_config '/root/.chef/knife.rb' - knife_bin '/opt/chef/bin/knife' - vcs_type 'svn' - vcs_path nil - plugin_path '/etc/gd-plugin.rb' - end -end diff --git a/grocery-delivery/grocery-delivery/hooks.rb b/grocery-delivery/grocery-delivery/hooks.rb deleted file mode 100644 index 7f24e4d..0000000 --- a/grocery-delivery/grocery-delivery/hooks.rb +++ /dev/null @@ -1,34 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -module GroceryDelivery - # Hook class for GD - class Hooks - # This code will run once we've read our config and loaded our plugins - # but before *anything* else. We don't even have a lock yet. - def self.preflight_checks(_dryrun) - end - - # This is run after we've gotten a lock, written a pidfile and initialized - # our repo object (but not touched the repo yet) - def self.prerun(_dryrun) - end - - # This is code to run after we've updated the repo, but before we've done - # any work to parse it. - def self.post_repo_up(_dryrun) - end - - # After we parse the updates to the repo and uploaded/deleted the relevent - # items from the local server. - def self.postrun(_dryrun, _success, _msg) - end - - # exit hooks. - def self.atexit(_dryrun, _success, _msg) - end - - def self.get(file) - class_eval(File.read(file), __FILE__, __LINE__) if File.exists?(file) - end - end -end diff --git a/grocery-delivery/grocery-delivery/logging.rb b/grocery-delivery/grocery-delivery/logging.rb deleted file mode 100755 index c045eff..0000000 --- a/grocery-delivery/grocery-delivery/logging.rb +++ /dev/null @@ -1,56 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'syslog' -require 'logger' - -module GroceryDelivery - # Logging wrapper - # rubocop:disable ClassVars - module Log - @@init = false - @@level = Logger::WARN - - def self.init - Syslog.open(File.basename($PROGRAM_NAME, '.rb')) - @@init = true - end - - def self.verbosity=(val) - @@level = val - end - - def self.logit(level, msg) - init unless @@init - # You can't do `Syslog.log(level, msg)` because if there is a - # `%` in `msg` then ruby will interpret it as a printf string and - # expect more arguments to log(). - Syslog.log(level, '%s', msg) - puts msg if $stdout.tty? - end - - def self.debug(msg) - if @@level == Logger::DEBUG - msg.prepend('DEBUG: ') - logit(Syslog::LOG_DEBUG, msg) - end - end - - def self.info(msg) - if @@level == Logger::INFO - msg.prepend('INFO: ') - logit(Syslog::LOG_INFO, msg) - end - end - - def self.warn(msg) - msg.prepend('WARN: ') - logit(Syslog::LOG_WARNING, msg) - end - - def self.error(msg) - msg.prepend('ERROR: ') - logit(Syslog::LOG_ERR, msg) - end - end - # rubocop:enable ClassVars -end diff --git a/spec/between-meals/cookbook_spec.rb b/spec/between-meals/cookbook_spec.rb deleted file mode 100644 index 2627a5a..0000000 --- a/spec/between-meals/cookbook_spec.rb +++ /dev/null @@ -1,172 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -require_relative '../../between-meals/changes/change' -require_relative '../../between-meals/changes/cookbook' -require_relative '../../between-meals/changeset' -require 'logger' - -describe BetweenMeals::Changes::Cookbook do - let(:logger) do - Logger.new('/dev/null') - end - let(:cookbook_dirs) do - ['cookbooks/one', 'cookbooks/two'] - end - - FIXTURES = [ - { - :name => 'empty filelists', - :files => [], - :result => [], - }, - { - :name => 'modifying of a cookbook', - :files => [ - { - :status => :modified, - :path => 'cookbooks/two/cb_one/recipes/test.rb' - }, - { - :status => :modified, - :path => 'cookbooks/two/cb_one/metadata.rb' - }, - ], - :result => [ - ['cb_one', :modified], - ], - }, - { - :name => 'a mix of in-place modifications and deletes', - :files => [ - { - :status => :modified, - :path => 'cookbooks/one/cb_one/recipes/test.rb' - }, - { - :status => :deleted, - :path => 'cookbooks/one/cb_one/recipes/test2.rb' - }, - { - :status => :modified, - :path => 'cookbooks/one/cb_one/recipes/test3.rb' - }, - ], - :result => [ - ['cb_one', :modified], - ], - }, - { - :name => 'removing metadata.rb - invalid cookbook, delete it', - :files => [ - { - :status => :modified, - :path => 'cookbooks/one/cb_one/recipes/test.rb' - }, - { - :status => :deleted, - :path => 'cookbooks/one/cb_one/metadata.rb' - }, - ], - :result => [ - ['cb_one', :deleted], - ], - }, - { - :name => 'changing cookbook location', - :files => [ - { - :status => :deleted, - :path => 'cookbooks/one/cb_one/recipes/test.rb' - }, - { - :status => :deleted, - :path => 'cookbooks/one/cb_one/metadata.rb' - }, - { - :status => :modified, - :path => 'cookbooks/two/cb_one/recipes/test.rb' - }, - { - :status => :modified, - :path => 'cookbooks/two/cb_one/recipes/test2.rb' - }, - { - :status => :modified, - :path => 'cookbooks/two/cb_one/metadata.rb' - }, - ], - :result => [ - ['cb_one', :deleted], - ['cb_one', :modified], - ], - }, - { - :name => 'modifying metadata only', - :files => [ - { - :status => :modified, - :path => 'cookbooks/two/cb_one/metadata.rb' - }, - ], - :result => [ - ['cb_one', :modified], - ], - }, - { - :name => 'modifying README only', - :files => [ - { - :status => :modified, - :path => 'cookbooks/two/cb_one/README.md' - }, - ], - :result => [ - ['cb_one', :modified], - ], - }, - { - :name => 'modifying recipe only', - :files => [ - { - :status => :modified, - :path => 'cookbooks/two/cb_one/recipe/default.rb' - }, - ], - :result => [ - ['cb_one', :modified], - ], - }, - { - :name => 'skipping non-cookbook files', - :files => [ - { - :status => :modified, - :path => 'cookbooks/two/OWNERS' - }, - { - :status => :modified, - :path => 'cookbooks/OWNERS' - }, - { - :status => :modified, - :path => 'OWNERS' - }, - ], - :result => [ - ], - }, - ] - - FIXTURES.each do |fixture| - it "should handle #{fixture[:name]}" do - BetweenMeals::Changes::Cookbook.find( - fixture[:files], - cookbook_dirs, - logger - ).map do |cb| - [cb.name, cb.status] - end. - should eq(fixture[:result]) - end - end - -end diff --git a/spec/between-meals/databag_spec.rb b/spec/between-meals/databag_spec.rb deleted file mode 100644 index 9651ee2..0000000 --- a/spec/between-meals/databag_spec.rb +++ /dev/null @@ -1,75 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -require_relative '../../between-meals/changes/change' -require_relative '../../between-meals/changes/databag' -require 'logger' - -describe BetweenMeals::Changes::Databag do - let(:logger) do - Logger.new('/dev/null') - end - let(:roles_dir) do - 'databags' - end - - FIXTURES = [ - { - :name => 'empty filelists', - :files => [], - :result => [], - }, - { - :name => 'delete databag', - :files => [ - { - :status => :deleted, - :path => 'databags/test/databag1.json' - }, - { - :status => :deleted, - :path => 'databags/test1/test2/databag2.json' - }, - { - :status => :modified, - :path => 'cookbooks/two/cb_one/metadata.rb' - }, - ], - :result => [ - ['test', :deleted], - ], - }, - { - :name => 'add/modify a databag', - :files => [ - { - :status => :modified, - :path => 'databags/one/databag1.json' - }, - { - :status => :deleted, - :path => 'databags/test/databag2.rb' # wrong extension - }, - { - :status => :deleted, - :path => 'databags/two/databag3.json' - }, - ], - :result => [ - ['one', :modified], ['two', :deleted] - ], - }, - ] - - FIXTURES.each do |fixture| - it "should handle #{fixture[:name]}" do - BetweenMeals::Changes::Databag.find( - fixture[:files], - roles_dir, - logger - ).map do |cb| - [cb.name, cb.status] - end. - should eq(fixture[:result]) - end - end - -end diff --git a/spec/between-meals/git_spec.rb b/spec/between-meals/git_spec.rb deleted file mode 100644 index 955ec96..0000000 --- a/spec/between-meals/git_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -require_relative '../../between-meals/repo/git' -require_relative '../../between-meals/repo.rb' -require 'logger' - -describe BetweenMeals::Repo::Git do - let(:logger) do - Logger.new('/dev/null') - end - - FIXTURES = [ - { - :name => 'empty filelists', - :changes => '', - :result => [] - }, - { - :name => 'handle renames', - :changes => 'R050 foo/bar/baz foo/bang/bong', - :result => [ - { :status => :deleted, :path => 'bar/baz' }, - { :status => :modified, :path => 'bang/bong' }, - ], - }, - { - :name => 'handle type changes', - :changes => 'T foo/bar/baz', - :result => [ - { :status => :deleted, :path => 'bar/baz' }, - { :status => :modified, :path => 'bar/baz' }, - ], - }, - { - :name => 'handle additions', - :changes => 'A foo/bar/baz', - :result => [ - { :status => :modified, :path => 'bar/baz' }, - ], - }, - { - :name => 'handle deletes', - :changes => 'D foo/bar/baz', - :result => [ - { :status => :deleted, :path => 'bar/baz' }, - ], - }, - { - :name => 'handle modifications', - :changes => 'M004 foo/bar/baz', - :result => [ - { :status => :modified, :path => 'bar/baz' }, - ], - }, - { - :name => 'handle misc', - :changes => < [ - { :status => :deleted, :path => 'bar/baz' }, - { :status => :modified, :path => 'bang/bong' }, - { :status => :deleted, :path => 'bar/baz' }, - { :status => :modified, :path => 'bang/bong' }, - ], - }, - ] - - FIXTURES.each do |fixture| - it "should handle #{fixture[:name]}" do - BetweenMeals::Repo::Git.any_instance.stub(:setup).and_return(true) - git = BetweenMeals::Repo::Git.new('foo', logger) - git.send(:parse_status, fixture[:changes]). - should eq(fixture[:result]) - end - end - -end diff --git a/spec/between-meals/repo_spec.rb b/spec/between-meals/repo_spec.rb deleted file mode 100644 index 0195582..0000000 --- a/spec/between-meals/repo_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -require_relative '../../between-meals/repo' -require_relative '../../between-meals/repo/git' -require_relative '../../between-meals/repo/svn' - -describe 'BetweenMeals::Repo' do - - let (:class_interface) { BetweenMeals::Repo.public_methods.sort } - let (:instance_interface) { BetweenMeals::Repo.instance_methods.sort } - - # Misc Repos should not expose anything more than parent class, - # which default to 'Not implemented' - [BetweenMeals::Repo::Git, BetweenMeals::Repo::Svn].each do |klass| - it "#{klass} should conform to BetweenMeals::Repo class interface" do - klass.public_methods.sort.should eq(class_interface) - end - it "#{klass} should conform to BetweenMeals::Repo instance interface" do - klass.instance_methods.sort.should eq(instance_interface) - end - end -end diff --git a/spec/between-meals/role_spec.rb b/spec/between-meals/role_spec.rb deleted file mode 100644 index 27fb84c..0000000 --- a/spec/between-meals/role_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -require_relative '../../between-meals/changes/change' -require_relative '../../between-meals/changes/role' -require 'logger' - -describe BetweenMeals::Changes::Role do - let(:logger) do - Logger.new('/dev/null') - end - let(:roles_dir) do - 'roles' - end - - FIXTURES = [ - { - :name => 'empty filelists', - :files => [], - :result => [], - }, - { - :name => 'delete role', - :files => [ - { - :status => :deleted, - :path => 'roles/test.rb' - }, - { - :status => :modified, - :path => 'cookbooks/two/cb_one/metadata.rb' - }, - ], - :result => [ - ['test', :deleted], - ], - }, - { - :name => 'add/modify a role', - :files => [ - { - :status => :modified, - :path => 'cookbooks/one/cb_one/recipes/test.rb' - }, - { - :status => :modified, - :path => 'roles/test.rb' - }, - { - :status => :modified, - :path => 'cookbooks/one/cb_one/recipes/test3.rb' - }, - ], - :result => [ - ['test', :modified], - ], - }, - ] - - FIXTURES.each do |fixture| - it "should handle #{fixture[:name]}" do - BetweenMeals::Changes::Role.find( - fixture[:files], - roles_dir, - logger - ).map do |cb| - [cb.name, cb.status] - end. - should eq(fixture[:result]) - end - end - -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 7d9868d..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'logger' - -module TasteTester - # Null logger - module Logging - def logger - Logging.logger - end - def self.logger - @logger ||= Logger.new('/dev/null') - end - end -end diff --git a/taste-tester/MOVED b/taste-tester/MOVED new file mode 100644 index 0000000..3584f28 --- /dev/null +++ b/taste-tester/MOVED @@ -0,0 +1 @@ +Moved to http://github.com/facebook/taste-tester diff --git a/taste-tester/README.md b/taste-tester/README.md deleted file mode 100644 index 04c0504..0000000 --- a/taste-tester/README.md +++ /dev/null @@ -1,145 +0,0 @@ -# Taste Tester - -## Intro -Ohai! - -Welcome to Taste Tester, software to manage a chef-zero instance and use it to -test changes on production servers. - -At its core, Taste Tester starts up a chef-zero server on localhost, uploads a -repository to it, ssh's to a remote server and points its configs to your new -chef-zero instance. - -Further, it keeps track of where in git you were when that happened so future -uploads will do the right thing, even as you switch branches. - -Taste Tester can be controlled via a variety of config-file options, and can be -further customized by writing a plugin. - -## Synopsis - -Typical usage is: - -```text -vi cookbooks/... # Make your changes and commit locally -taste-tester test -s [host] # Put host in Taste Tester mode -ssh root@[host] # Log in to host - # Run chef and watch it break -vi cookbooks/... # Fix your cookbooks -taste-tester upload # Upload the diff -ssh root@[host] - # Run chef and watch it succeed - -taste-tester untest [host] # Put host back in production - # (optional - will revert itself after 1 hour) -``` - -See the help for futher information. - -## Prerequisites - -* Taste Tester assumes that /etc/chef/client.rb on your servers is a symlink and -that your real config is /etc/chef/client-prod.rb - -* Taste Tester assumes that it's generally safe to "go back" to production. I.e. -We set things up so you can set a cronjob to un-taste-test a server after the -desired amount of time, which means it must be (relatively) safe to revert -back. - -* Taste Tester assumes you use a setup similar to grocery-delivery in -production. Specifically that you don't use versions or environments. - -## Dependencies - -* Mixlib::Config -* Colorize -* BetweenMeals - -## Automatic Untesting - -Taste Tester touches `/etc/chef/test_timestamp` on the remote server as far into -the future as the user wants to test (default is 1h). You should have a cronjob -to check the timestamp of this file, and if it is old, remove it and put the -symlinks for `/etc/chef/client.rb` back to where they belong. - -A small shell script to do this is included called `taste-untester`. We -recommend running this at least every 15 minutes. - -If you let Taste Tester setup reverse-SSH tunnels, make sure your untester -is also killing the ssh tunnel whose PID is in `/etc/chef/test_timestamp` -(taste-untester will do this for you). - -## Config file - -The default config file is `/etc/taste-tester-config.rb` but you may use -c to -specify another. The config file works the same as client.rb does for Chef - -there are a series of keywords that take an arguement and anything else is just -standard Ruby. - -All command-line options are available in the config file: -* debug (bool, default: `false`) -* timestamp (bool, default: `false`) -* config_file (string, default: `/etc/gd-config.rb`) -* plugin_path (string, default: `/etc/taste-tester-plugin.rb`) -* repo (string, default: `#{ENV['HOME']}/ops`) -* testing_time (int, default: `3600`) -* chef_client_command (strng, default: `chef-client`) -* skip_repo_checks (bool, default: `false`) -* skip_pre_upload_hook (bool, default: `false`) -* skip_post_upload_hook (bool, default: `false`) -* skip_pre_test_hook (bool, default: `false`) -* skip_post_test_hook (bool, default: `false`) -* skip_repo_checks_hook (bool, default: `false`) - -The following options are also available: -* base_dir - The directory in the repo under which to find chef configs. - Default: `chef` -* cookbook_dirs - An array of cookbook directories relative to base_dir. - Default: `['cookbooks'] -* role_dir - A directory of roles, relative to base_dir. Default: `roles` -* databag_dir - A directory of databags, relative to base_dir. - Default: `databags` -* ref_file - The file to store the last git revision we uploaded in. Default: - `#{ENV['HOME']}/.chef/taste-tester-ref.txt` -* checksum_dir - The checksum directory to put in knife.conf for users. Default: - `#{ENV['HOME']}/.chef/checksums` - -## Plugin - -The plugin should be a ruby file which defines several class methods. It is -class_eval()d into a Hooks class. - -The following functions can optionally be defined: - -* self.pre_upload(dryrun, repo, last_ref, cur_ref) - -Stuff to do before we upload anything to chef-zero. `Repo` is a BetweenMeals::Repo -object. `last_ref` is the last git ref we uploaded and `cur_ref` is the git ref -the repo is currently at, - -* self.post_upload(dryrun, repo, last_ref, cur_ref) - -Stuff to do after we upload to chef-zero. - -* self.pre_test(dryrun, repo, hosts) - -Stuff to do before we put machines in test mode. `hosts` is an array of -hostnames. - -* self.test_remote_cmds(dryrun, hostname) - -Additional commands to run on the remote host when putting it in test mode. -Should return an array of strings. `hostname` is the hostname. - -* self.test_remote_client_rb_extra_code(hostname) - -Should return a string of additional code to include in the remote `client.rb`. -Example uses: defining json_attribs - -* self.post_test(dryrun, repo, hosts) - -Stuff to do after putting all hosts in test mode. - -* self.repo_checks(dryrun, repo) - -Additional checks you want to do on the repo as sanity checks. diff --git a/taste-tester/taste-tester.rb b/taste-tester/taste-tester.rb deleted file mode 100755 index 892fd9d..0000000 --- a/taste-tester/taste-tester.rb +++ /dev/null @@ -1,354 +0,0 @@ -#!/opt/chef/embedded/bin/ruby -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -# rubocop:disable UnusedBlockArgument, AlignParameters - -if ENV['MY_RUBY_HOME'] && ENV['MY_RUBY_HOME'].include?('rvm') - puts 'Please disable RVM before running taste-tester' - exit(1) -end - -require 'rubygems' -require 'time' -require 'optparse' -require 'colorize' - -require_relative 'taste-tester/logging' -require_relative 'taste-tester/config' -require_relative 'taste-tester/commands' -require_relative 'taste-tester/hooks' - -include TasteTester::Logging - -if ENV['USER'] == 'root' - logger.warn('You should not be running as root') - exit(1) -end - -# Command line parsing and param descriptions -module TasteTester - verify = 'Verify your changes were actually applied as intended!'.red - cmd = TasteTester::Config.chef_client_command - description = <<-EOF -Welcome to taste-tester! - -Usage: taste-tester [] - -TLDR; Most common usage is: - vi cookbooks/... # Make your changes and commit locally - taste-tester test -s [host] # Put host in test mode - ssh root@[host] # Log on host - #{format('%-28s', " #{cmd}")} # Run chef and watch it break - vi cookbooks/... # Fix your cookbooks - taste-tester upload # Upload the diff - ssh root@[host] # Log on host - #{format('%-28s', " #{cmd}")} # Run chef and watch it succeed - <#{verify}> - taste-tester untest [host] # Put host back in production - # (optional - will revert itself after 1 hour) - -And you're done! See the above wiki page for more details. - -MODES: - test - Sync your local repo to your virtual Chef server (same as 'upload'), and - point some production server specified by -s to your virtual chef server for - testing. If you have you a plugin that uses the hookpoint, it'll may amend - your commit message to denote the server you tested. - - upload - Sync your local repo to your virtual Chef server (i.e. just the first step - of 'test'). By defailt, it intelligently uploads whatever has changed since - the last time you ran upload (or test), but tracking git changes (even - across branch changes). You may specify -f to force a full upload of all - cookbooks and roles. It also does a fair amount of sanity checking on - your repo and you may specify --skip-repo-checks to bypass this. - - keeptesting - Extend the testing time on server specified by -s by 1 hour unless - otherwise specified by -t. - - untest - Return the server specified in -s to production. - - status - Print out the state of the world. - - run - Run #{cmd} on the machine specified by '-s' over SSH and print the output. - NOTE!! This is #{'NOT'.red} a sufficient test, you must log onto the remote - machine and verify the changes you are trying to make are actually present. - - stop - You probably don't want this. It will shutdown the chef-zero server on - your localhost. - - start - You probably don't want this. It will start up the chef-zero server on - your localhost. taste-tester dynamically starts this if it's down, so there - should be no need to do this manually. - - restart - You probably don't want this. It will restart up the chef-zero server on - your localhost. taste-tester dynamically starts this if it's down, so there - should be no need to do this manually. - EOF - - mode = ARGV.shift unless ARGV.size > 0 && ARGV[0].start_with?('-') - - unless mode - mode = 'help' - puts "ERROR: No mode specified\n\n" - end - - options = { :config_file => TasteTester::Config.config_file } - parser = OptionParser.new do |opts| - opts.banner = description - - opts.separator '' - opts.separator 'Global options:'.upcase - - opts.on('-c', '--config FILE', 'Config file') do |file| - unless File.exists?(File.expand_path(file)) - logger.error("Sorry, cannot find #{file}") - exit(1) - end - options[:config_file] = file - end - - opts.on('-v', '--verbose', 'Verbosity, provide twice for all debug') do - # If -vv is supplied this block is executed twice - if options[:verbosity] - options[:verbosity] = Logger::DEBUG - else - options[:verbosity] = Logger::INFO - end - end - - opts.on('-p', '--plugin-path FILE', String, 'Plugin file') do |file| - unless File.exists?(File.expand_path(file)) - logger.error("Sorry, cannot find #{file}") - exit(1) - end - options[:plugin_path] = file - end - - opts.on('-h', '--help', 'Print help message.') do - print opts - exit - end - - opts.on('-T', '--timestamp', 'Time-stamped log style output') do - options[:timestamp] = true - end - - opts.separator '' - opts.separator 'Sub-command options:'.upcase - - opts.on( - '-C', '--cookbooks COOKBOOK[,COOKBOOK]', Array, - 'Specific cookbooks to upload. Intended mostly for debugging,' + - ' not recommended. Works on upload and test. Not yet implemented.' - ) do |cbs| - options[:cookbooks] = cbs - end - - opts.on( - '-D', '--databag DATABAG/ITEM[,DATABAG/ITEM]', Array, - 'Specific cookbooks to upload. Intended mostly for debugging,' + - ' not recommended. Works on upload and test. Not yet implemented.' - ) do |cbs| - options[:databags] = cbs - end - - opts.on( - '-f', '--force-upload', - 'Force upload everything. Works on upload and test.' - ) do - options[:force_upload] = true - end - - opts.on( - '--chef-port-range PORT1,PORT2', Array, - 'Port range for chef-zero' - ) do |ports| - unless ports.count == 2 - logger.error("Invalid port range: #{ports}") - exit 1 - end - options[:chef_port_range] = ports - end - - opts.on( - '--tunnel-port PORT', 'Port for ssh tunnel' - ) do |port| - options[:user_tunnel_port] = port - end - - opts.on( - '-l', '--linkonly', 'Only setup the remote server, skip uploading.' - ) do - options[:linkonly] = true - end - - opts.on( - '-t', '--testing-timestamp TIME', - 'Until when should the host remain in testing.' + - ' Anything parsable is ok, such as "5/18 4:35" or "16/9/13".' - ) do |time| - begin - options[:testing_until] = Time.parse(time) - rescue - logger.error("Invalid date: #{time}") - exit 1 - end - end - - opts.on( - '-t', '--testing-time TIME', - 'How long should the host remain in testing.' + - ' Takes a simple relative time string, such as "45m", "4h" or "2d".' - ) do |time| - m = time.match(/^(\d+)([d|h|m]+)$/) - if m - exp = { - :d => 60 * 60 * 24, - :h => 60 * 60, - :m => 60, - }[m[2].to_sym] - delta = m[1].to_i * exp - options[:testing_until] = Time.now + delta.to_i - else - logger.error("Invalid testing-time: #{time}") - exit 1 - end - end - - opts.on( - '-r', '--repo DIR', - "Custom repo location, current deafult is #{TasteTester::Config.repo}." + - ' Works on upload and test.' - ) do |dir| - options[:repo] = dir - end - - opts.on( - '-R', '--roles ROLE[,ROLE]', Array, - 'Specific roles to upload. Intended mostly for debugging,' + - ' not recommended. Works on upload and test. Not yet implemented.' - ) do |roles| - options[:roles] = roles - end - - opts.on('--really', 'Really do link-only. DANGEROUS!') do |r| - options[:really] = r - end - - opts.on( - '-s', '--servers SERVER[,SERVER]', Array, - 'Server to test/untest/keeptesting.' - ) do |s| - options[:servers] = s - end - - opts.on( - '--user USER', 'Custom username for SSH, defaults to "root".' + - ' If custom user is specified, we will use sudo for all commands.' - ) do |user| - options[:user] = user - end - - opts.on( - '-S', '--[no-]use-ssh-tunnels', 'Protect Chef traffic with SSH tunnels' - ) do |s| - options[:use_ssh_tunnels] = s - end - - opts.on('--skip-repo-checks', 'Skip repository sanity checks') do - options[:skip_repo_checks] = true - end - - opts.on('-y', '--yes', 'Do not prompt before testing.') do - options[:yes] = true - end - - opts.separator '' - opts.separator 'Control local hook behavior with these options:' - - opts.on( - '--skip-pre-upload-hook', 'Skip pre-upload hook. Works on upload, test.' - ) do - options[:skip_pre_upload_hook] = true - end - - opts.on( - '--skip-post-upload-hook', 'Skip post-upload hook. Works on upload, test.' - ) do - options[:skip_post_upload_hook] = true - end - - opts.on( - '--skip-pre-test-hook', 'Skip pre-test hook. Works on test.' - ) do - options[:skip_pre_test_hook] = true - end - - opts.on( - '--skip-post-test-hook', 'Skip post-test hook. Works on test.' - ) do - options[:skip_post_test_hook] = true - end - - opts.on( - '--skip-repo-checks-hook', 'Skip repo-checks hook. Works on upload, test.' - ) do - options[:skip_post_test_hook] = true - end - end - - if mode == 'help' - puts parser - exit - end - - parser.parse! - - if File.exists?(File.expand_path(options[:config_file])) - TasteTester::Config.from_file(File.expand_path(options[:config_file])) - end - TasteTester::Config.merge!(options) - TasteTester::Logging.verbosity = TasteTester::Config.verbosity - TasteTester::Logging.use_log_formatter = TasteTester::Config.timestamp - - if File.exists?(File.expand_path(TasteTester::Config.plugin_path)) - TasteTester::Hooks.get(File.expand_path(TasteTester::Config[:plugin_path])) - end - - case mode.to_sym - when :start - TasteTester::Commands.start - when :stop - TasteTester::Commands.stop - when :restart - TasteTester::Commands.restart - when :keeptesting - TasteTester::Commands.keeptesting - when :status - TasteTester::Commands.status - when :test - TasteTester::Commands.test - when :untest - TasteTester::Commands.untest - when :run - TasteTester::Commands.runchef - when :upload - TasteTester::Commands.upload - else - logger.error("Invalid mode: #{mode}") - puts parser - exit(1) - end -end - -if __FILE__ == $PROGRAM_NAME - include TasteTester -end diff --git a/taste-tester/taste-tester/client.rb b/taste-tester/taste-tester/client.rb deleted file mode 100755 index 50d11c5..0000000 --- a/taste-tester/taste-tester/client.rb +++ /dev/null @@ -1,169 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require_relative 'logging' -require_relative '../../between-meals/repo' -require_relative '../../between-meals/knife' -require_relative '../../between-meals/changeset' - -module TasteTester - # Client side upload functionality - # Ties together Repo/Changeset diff logic - # and Server/Knife uploads - class Client - include TasteTester::Logging - include BetweenMeals::Util - - attr_accessor :force, :skip_checks - - def initialize(server) - @server = server - @knife = BetweenMeals::Knife.new( - :logger => logger, - :user => @server.user, - :host => @server.host, - :port => @server.port, - :role_dir => TasteTester::Config.roles, - :cookbook_dirs => TasteTester::Config.cookbooks, - :databag_dir => TasteTester::Config.databags, - :checksum_dir => TasteTester::Config.checksum_dir, - ) - @knife.write_user_config - @repo = BetweenMeals::Repo.get(TasteTester::Config.repo_type, - TasteTester::Config.repo, logger) - fail 'Could not read repo' unless @repo - end - - def checks - unless @skip_checks - TasteTester::Hooks.repo_checks(TasteTester::Config.dryrun, @repo) - end - end - - def upload - checks unless @skip_checks - - logger.warn("Using #{TasteTester::Config.repo}") - logger.info("Last commit: #{@repo.head_rev} " + - "'#{@repo.last_msg.split("\n").first}'" + - " by #{@repo.last_author[:email]}") - - if @force || !@server.latest_uploaded_ref - logger.info('Full upload forced') if @force - unless TasteTester::Config.skip_pre_upload_hook - TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun, - @repo, - nil, - @repo.head_rev) - end - time(logger) { full } - unless TasteTester::Config.skip_post_upload_hook - TasteTester::Hooks.post_upload(TasteTester::Config.dryrun, - @repo, - nil, - @repo.head_rev) - end - else - # Since we also upload the index, we always need to run the - # diff even if the version we're on is the same as the last - # revision - unless TasteTester::Config.skip_pre_upload_hook - TasteTester::Hooks.pre_upload(TasteTester::Config.dryrun, - @repo, - @server.latest_uploaded_ref, - @repo.head_rev) - end - begin - time(logger) { partial } - rescue BetweenMeals::Changeset::ReferenceError - logger.warn('Something changed with your repo, doing full upload') - time(logger) { full } - end - unless TasteTester::Config.skip_post_upload_hook - TasteTester::Hooks.post_upload(TasteTester::Config.dryrun, - @repo, - @server.latest_uploaded_ref, - @repo.head_rev) - end - end - - @server.latest_uploaded_ref = @repo.head_rev - end - - private - - def full - logger.warn('Doing full upload') - @knife.cookbook_upload_all - @knife.role_upload_all - @knife.databag_upload_all - end - - def partial - logger.info('Doing differential upload from ' + - @server.latest_uploaded_ref) - changeset = BetweenMeals::Changeset.new( - logger, - @repo, - @server.latest_uploaded_ref, - nil, - { - :cookbook_dirs => - TasteTester::Config.relative_cookbook_dirs, - :role_dir => - TasteTester::Config.relative_role_dir, - :databag_dir => - TasteTester::Config.relative_databag_dir, - }, - ) - - cbs = changeset.cookbooks - deleted_cookbooks = cbs.select { |x| x.status == :deleted } - modified_cookbooks = cbs.select { |x| x.status == :modified } - roles = changeset.roles - deleted_roles = roles.select { |x| x.status == :deleted } - modified_roles = roles.select { |x| x.status == :modified } - databags = changeset.databags - deleted_databags = databags.select { |x| x.status == :deleted } - modified_databags = databags.select { |x| x.status == :modified } - - didsomething = false - unless deleted_cookbooks.empty? - didsomething = true - logger.warn("Deleting cookbooks: [#{deleted_cookbooks.join(' ')}]") - @knife.cookbook_delete(deleted_cookbooks) - end - - unless modified_cookbooks.empty? - didsomething = true - logger.warn("Uploading cookbooks: [#{modified_cookbooks.join(' ')}]") - @knife.cookbook_upload(modified_cookbooks) - end - - unless deleted_roles.empty? - didsomething = true - logger.warn("Deleting roles: [#{deleted_roles.join(' ')}]") - @knife.role_delete(deleted_roles) - end - - unless modified_roles.empty? - didsomething = true - logger.warn("Uploading roles: [#{modified_roles.join(' ')}]") - @knife.role_upload(modified_roles) - end - - unless deleted_databags.empty? - didsomething = true - logger.warn("Deleting databags: [#{deleted_databags.join(' ')}]") - @knife.databag_delete(deleted_databags) - end - - unless modified_databags.empty? - didsomething = true - logger.warn("Uploading databags: [#{modified_databags.join(' ')}]") - @knife.databag_upload(modified_databags) - end - - logger.warn('Nothing to upload!') unless didsomething - end - end -end diff --git a/taste-tester/taste-tester/commands.rb b/taste-tester/taste-tester/commands.rb deleted file mode 100755 index 58dfeb0..0000000 --- a/taste-tester/taste-tester/commands.rb +++ /dev/null @@ -1,144 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -# rubocop:disable UnusedBlockArgument, UnusedMethodArgument - -require_relative 'server' -require_relative 'host' -require_relative 'config' -require_relative 'client' -require_relative 'logging' - -module TasteTester - # Functionality dispatch - module Commands - extend TasteTester::Logging - - def self.start - server = TasteTester::Server.new - return if TasteTester::Server.running? - server.start - end - - def self.restart - server = TasteTester::Server.new - server.stop if TasteTester::Server.running? - server.start - end - - def self.stop - server = TasteTester::Server.new - server.stop - end - - def self.status - server = TasteTester::Server.new - if TasteTester::Server.running? - logger.warn("Local taste-tester server running on port #{server.port}") - if server.latest_uploaded_ref - logger.warn('Latest uploaded revision is ' + - server.latest_uploaded_ref) - else - logger.warn('No cookbooks/roles uploads found') - end - else - logger.warn('Local taste-tester server not running') - end - end - - def self.test - hosts = TasteTester::Config.servers - unless hosts - logger.warn('You must provide a hostname') - exit(1) - end - unless TasteTester::Config.yes - printf("Set #{TasteTester::Config.servers} to test mode? [y/N] ") - ans = STDIN.gets.chomp - exit(1) unless ans =~ /^[yY](es)?$/ - end - if TasteTester::Config.linkonly && TasteTester::Config.really - logger.warn('Skipping upload at user request... potentially dangerous!') - else - if TasteTester::Config.linkonly - logger.warn('Ignoring --linkonly because --really not set') - end - upload - end - server = TasteTester::Server.new - repo = BetweenMeals::Repo.get(TasteTester::Config.repo_type, - TasteTester::Config.repo, logger) - unless TasteTester::Config.skip_pre_test_hook - TasteTester::Hooks.pre_test(TasteTester::Config.dryrun, repo, hosts) - end - tested_hosts = [] - hosts.each do |hostname| - host = TasteTester::Host.new(hostname, server) - if host.in_test? - username = host.who_is_testing - logger.error("User #{username} is already testing on #{hostname}") - else - host.test - tested_hosts << hostname - end - end - unless TasteTester::Config.skip_post_test_hook - TasteTester::Hooks.post_test(TasteTester::Config.dryrun, repo, - tested_hosts) - end - end - - def self.untest - hosts = TasteTester::Config.servers - unless hosts - logger.warn('You must provide a hostname') - exit(1) - end - server = TasteTester::Server.new - hosts.each do |hostname| - host = TasteTester::Host.new(hostname, server) - host.untest - end - end - - def self.runchef - hosts = TasteTester::Config.servers - unless hosts - logger.warn('You must provide a hostname') - exit(1) - end - server = TasteTester::Server.new - hosts.each do |hostname| - host = TasteTester::Host.new(hostname, server) - host.run - end - end - - def self.keeptesting - hosts = TasteTester::Config.servers - unless hosts - logger.warn('You must provide a hostname') - exit(1) - end - server = TasteTester::Server.new - hosts.each do |hostname| - host = TasteTester::Host.new(hostname, server) - host.keeptesting - end - end - - def self.upload - server = TasteTester::Server.new - # On a fore-upload rather than try to clean up whatever's on the server - # we'll restart chef-zero which will clear everything and do a full - # upload - if TasteTester::Config.force_upload - server.restart - else - server.start unless TasteTester::Server.running? - end - client = TasteTester::Client.new(server) - client.skip_checks = true if TasteTester::Config.skip_checks - client.force = true if TasteTester::Config.force_upload - client.upload - end - end -end diff --git a/taste-tester/taste-tester/config.rb b/taste-tester/taste-tester/config.rb deleted file mode 100644 index d913162..0000000 --- a/taste-tester/taste-tester/config.rb +++ /dev/null @@ -1,92 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'mixlib/config' -require_relative 'logging' -require_relative '../../between-meals/util' - -module TasteTester - # Config file parser and config object - # Uses Mixlib::Config v1 syntax so it works in Chef10 omnibus... - # it's compatible with v2, so it should work in 11 too. - module Config - extend Mixlib::Config - extend TasteTester::Logging - extend BetweenMeals::Util - - repo "#{ENV['HOME']}/ops" - repo_type 'git' - base_dir 'chef' - cookbook_dirs ['cookbooks'] - role_dir 'roles' - databag_dir 'databags' - config_file '/etc/taste-tester-config.rb' - plugin_path '/etc/taste-tester-plugin.rb' - chef_zero_path '/opt/chef/embedded/bin/chef-zero' - verbosity Logger::WARN - timestamp false - user 'root' - ref_file "#{ENV['HOME']}/.chef/taste-tester-ref.json" - checksum_dir "#{ENV['HOME']}/.chef/checksums" - skip_repo_checks false - chef_client_command 'chef-client' - testing_time 3600 - chef_port_range [5000, 5500] - tunnel_port 4001 - timestamp_file '/etc/chef/test_timestamp' - use_ssh_tunnels true - - skip_pre_upload_hook false - skip_post_upload_hook false - skip_pre_test_hook false - skip_post_test_hook false - skip_repo_checks_hook false - - def self.cookbooks - cookbook_dirs.map do |x| - File.join(repo, base_dir, x) - end - end - - def self.relative_cookbook_dirs - cookbook_dirs.map do |x| - File.join(base_dir, x) - end - end - - def self.roles - File.join(repo, base_dir, role_dir) - end - - def self.relative_role_dir - File.join(base_dir, role_dir) - end - - def self.databags - File.join(repo, base_dir, databag_dir) - end - - def self.relative_databag_dir - File.join(base_dir, databag_dir) - end - - def self.chef_port - range = chef_port_range.first.to_i..chef_port_range.last.to_i - range.to_a.shuffle.each do |port| - unless port_open?(port) - return port - end - end - logger.error 'Could not find a free port in range' + - " [#{chef_port_range.first}, #{chef_port_range.last}]" - exit 1 - end - - def self.testing_end_time - if TasteTester::Config.testing_until - TasteTester::Config.testing_until.strftime('%y%m%d%H%M.%S') - else - (Time.now + TasteTester::Config.testing_time).strftime('%y%m%d%H%M.%S') - end - end - end -end diff --git a/taste-tester/taste-tester/hooks.rb b/taste-tester/taste-tester/hooks.rb deleted file mode 100644 index 66111ab..0000000 --- a/taste-tester/taste-tester/hooks.rb +++ /dev/null @@ -1,52 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require_relative 'logging' -require_relative '../../between-meals/util' - -module TasteTester - # Hooks placeholders - class Hooks - extend TasteTester::Logging - extend BetweenMeals::Util - - # Do stuff before we upload to chef-zero - def self.pre_upload(_dryrun, _repo, _last_ref, _cur_ref) - end - - # Do stuff after we upload to chef-zero - def self.post_upload(_dryrun, _repo, _last_ref, _cur_ref) - end - - # Do stuff before we put hosts in test mode - def self.pre_test(_dryrun, _repo, _hosts) - end - - # This should return an array of commands to execute on - # remote systems. - def self.test_remote_cmds(_dryrun, _hostname) - end - - # Should return a string with extra stuff to shove - # in the remote client.rb - def self.test_remote_client_rb_extra_code(_hostname) - end - - # Do stuff after we put hosts in test mode - def self.post_test(_dryrun, _repo, _hosts) - end - - # Additional checks you want to do on the repo - def self.repo_checks(_dryrun, _repo) - end - - def self.get(file) - path = File.expand_path(file) - logger.warn("Loading plugin at #{path}") - unless File.exists?(path) - logger.error('Plugin file not found') - exit(1) - end - class_eval(File.read(path), __FILE__, __LINE__) - end - end -end diff --git a/taste-tester/taste-tester/host.rb b/taste-tester/taste-tester/host.rb deleted file mode 100755 index 517d7f9..0000000 --- a/taste-tester/taste-tester/host.rb +++ /dev/null @@ -1,187 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'fileutils' -require 'base64' -require 'open3' -require 'colorize' - -require_relative 'ssh' -require_relative 'tunnel' - -module TasteTester - # Manage state of the remote node - class Host - include TasteTester::Logging - - attr_reader :name - - def initialize(name, server) - @name = name - @user = ENV['USER'] - @server = server - @tunnel = TasteTester::Tunnel.new(@name, @server) - end - - def runchef - logger.warn("Running '#{TasteTester::Config.command}' on #{@name}") - cmd = "ssh #{TasteTester::Config.user}@#{@name} " - if TasteTester::Config.user != 'root' - cc = Base64.encode64(cmds).gsub(/\n/, '') - cmd += "\"echo '#{cc}' | base64 --decode | sudo bash -x\"" - else - cmd += "\"#{cmds}\"" - end - status = IO.popen( - cmd - ) do |io| - # rubocop:disable AssignmentInCondition - while line = io.gets - puts line.chomp! - end - # rubocop:enable AssignmentInCondition - io.close - $CHILD_STATUS.to_i - end - logger.warn("Finished #{TasteTester::Config.command}" + - " on #{@name} with status #{status}") - if status == 0 - msg = "#{TasteTester::Config.command} was successful" + - ' - please log to the host and confirm all the intended' + - ' changes were made' - logger.error msg.upcase - end - end - - def test - logger.warn("Taste-testing on #{@name}") - - # Nuke any existing tunnels that may be there - TasteTester::Tunnel.kill(@name) - - # Then setup the tunnel - @tunnel.run - @serialized_config = Base64.encode64(config).gsub(/\n/, '') - - # Then setup the testing - ssh = TasteTester::SSH.new(@name) - ssh << 'logger -t taste-tester Moving server into taste-tester' + - " for #{@user}" - ssh << "touch -t #{TasteTester::Config.testing_end_time}" + - " #{TasteTester::Config.timestamp_file}" - ssh << "echo -n '#{@serialized_config}' | base64 --decode" + - ' > /etc/chef/client-taste-tester.rb' - ssh << 'rm -vf /etc/chef/client.rb' - ssh << '( ln -vs /etc/chef/client-taste-tester.rb' + - ' /etc/chef/client.rb; true )' - ssh.run! - - # Then run any other stuff they wanted - cmds = TasteTester::Hooks.test_remote_cmds( - TasteTester::Config.dryrun, - @name - ) - - if cmds && cmds.any? - ssh = TasteTester::SSH.new(@name) - cmds.each { |c| ssh << c } - ssh.run! - end - end - - def untest - logger.warn("Removing #{@name} from taste-tester") - ssh = TasteTester::SSH.new(@name) - TasteTester::Tunnel.kill(@name) - ssh << 'rm -vf /etc/chef/client.rb' - ssh << 'rm -vf /etc/chef/client-taste-tester.rb' - ssh << 'ln -vs /etc/chef/client-prod.rb /etc/chef/client.rb' - ssh << 'rm -vf /etc/chef/client.pem' - ssh << 'ln -vs /etc/chef/client-prod.pem /etc/chef/client.pem' - ssh << "rm -vf #{TasteTester::Config.timestamp_file}" - ssh << 'logger -t taste-tester Returning server to production' - ssh.run! - end - - def who_is_testing - ssh = TasteTester::SSH.new(@name) - ssh << 'grep "^# TasteTester by" /etc/chef/client.rb' - output = ssh.run - if output.first == 0 - user = output.last.match(/# TasteTester by (.*)$/) - if user - return user[1] - end - end - - # Legacy FB stuff, remove after migration. Safe for everyone else. - ssh = TasteTester::SSH.new(@name) - ssh << 'file /etc/chef/client.rb' - output = ssh.run - if output.first == 0 - user = output.last.match(/client-(.*)-(taste-tester|test).rb/) - if user - return user[1] - end - end - - return nil - end - - def in_test? - ssh = TasteTester::SSH.new(@name) - ssh << "test -f #{TasteTester::Config.timestamp_file}" - if ssh.run.first == 0 && who_is_testing && who_is_testing != ENV['USER'] - true - else - false - end - end - - def keeptesting - logger.warn("Renewing taste-tester on #{@name} until" + - " #{TasteTester::Config.testing_end_time}") - TasteTester::Tunnel.kill(@name) - @tunnel = TasteTester::Tunnel.new(@name, @server) - @tunnel.run - end - - private - - def config - if TasteTester::Config.use_ssh_tunnels - url = "http://localhost:#{@tunnel.port}" - else - url = "http://#{@server.host}:#{TasteTester::State.port}" - end - ttconfig = <<-eos -# TasteTester by #{@user} -# Prevent people from screwing up their permissions -if Process.euid != 0 - puts 'Please run chef as root!' - Process.exit! -end - -log_level :info -log_location STDOUT -chef_server_url '#{url}' -Ohai::Config[:plugin_path] << '/etc/chef/ohai_plugins' - - eos - - extra = TasteTester::Hooks.test_remote_client_rb_extra_code(@name) - if extra - ttconfig += <<-eos -# Begin user-hook specified code - #{extra} -# End user-hook secified code - - eos - end - - ttconfig += <<-eos -puts 'INFO: Running on #{@name} in taste-tester by #{@user}' - eos - return ttconfig - end - end -end diff --git a/taste-tester/taste-tester/logging.rb b/taste-tester/taste-tester/logging.rb deleted file mode 100755 index 824a2b6..0000000 --- a/taste-tester/taste-tester/logging.rb +++ /dev/null @@ -1,55 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 -# rubocop:disable ClassVars, UnusedMethodArgument, UnusedBlockArgument -require 'logger' - -module TasteTester - # Logging wrapper - module Logging - @@use_log_formatter = false - @@level = Logger::WARN - @@formatter_proc = nil - - def logger - logger = Logging.logger - logger.formatter = formatter - logger.level = @@level - logger - end - - def self.logger - @logger ||= Logger.new(STDOUT) - end - - def self.formatterproc=(p) - @@formatter_proc = p - end - - def self.use_log_formatter=(use_log_formatter) - @@use_log_formatter = use_log_formatter - end - - def self.verbosity=(level) - @@level = level - end - - def formatter - return @@formatter_proc if @@formatter_proc - if @@use_log_formatter - proc do |severity, datetime, progname, msg| - if severity == 'ERROR' - msg = msg.red - end - "[#{datetime.strftime('%Y-%m-%dT%H:%M:%S%:z')}] #{severity}: #{msg}\n" - end - else - proc do |severity, datetime, progname, msg| - msg.to_s.prepend("#{severity}: ") unless severity == 'WARN' - if severity == 'ERROR' - msg = msg.to_s.red - end - "#{msg}\n" - end - end - end - end -end diff --git a/taste-tester/taste-tester/server.rb b/taste-tester/taste-tester/server.rb deleted file mode 100755 index 0abfd11..0000000 --- a/taste-tester/taste-tester/server.rb +++ /dev/null @@ -1,122 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'fileutils' -require 'socket' -require 'timeout' - -require_relative '../../between-meals/util' -require_relative 'config' -require_relative 'state' - -module TasteTester - # Stateless chef-zero server management - class Server - include TasteTester::Config - include TasteTester::Logging - extend ::BetweenMeals::Util - - attr_accessor :user, :host - - def initialize - @state = TasteTester::State.new - @ref_file = TasteTester::Config.ref_file - ref_dir = File.dirname(File.expand_path(@ref_file)) - @zero_path = TasteTester::Config.chef_zero_path - unless File.directory?(ref_dir) - begin - FileUtils.mkpath(ref_dir) - rescue => e - logger.warn("Chef temp dir #{ref_dir} missing and can't be created") - logger.warn(e) - end - end - - @user = ENV['USER'] - - # If we are using SSH tunneling listen on localhost, otherwise listen - # on all addresses - both v4 and v6. Note that on localhost, ::1 is - # v6-only, so we default to 127.0.0.1 instead. - @addr = TasteTester::Config.use_ssh_tunnels ? '127.0.0.1' : '::' - @host = 'localhost' - end - - def start - return if TasteTester::Server.running? - @state.wipe - logger.warn('Starting taste-tester server') - write_config - start_chef_zero - end - - def stop - logger.warn('Stopping taste-tester server') - stop_chef_zero - end - - def restart - logger.warn('Restarting taste-tester server') - if TasteTester::Server.running? - stop_chef_zero - end - write_config - start_chef_zero - end - - def port - @state.port - end - - def port=(port) - @state.port = port - end - - def latest_uploaded_ref - @state.ref - end - - def latest_uploaded_ref=(ref) - @state.ref = ref - end - - def self.running? - if TasteTester::State.port - return port_open?(TasteTester::State.port) - end - false - end - - private - - def write_config - knife = BetweenMeals::Knife.new( - :logger => logger, - :user => @user, - :host => @host, - :port => port, - :role_dir => TasteTester::Config.roles, - :cookbook_dirs => TasteTester::Config.cookbooks, - :checksum_dir => TasteTester::Config.checksum_dir, - ) - knife.write_user_config - end - - def start_chef_zero - @state.wipe - @state.port = TasteTester::Config.chef_port - logger.info("Starting chef-zero of port #{@state.port}") - Mixlib::ShellOut.new( - "/opt/chef/embedded/bin/chef-zero --host #{@addr}" + - " --port #{@state.port} -d" - ).run_command.error! - end - - def stop_chef_zero - @state.wipe - logger.info('Killing your chef-zero instances') - s = Mixlib::ShellOut.new("pkill -9 -u #{ENV['USER']} -f bin/chef-zero") - s.run_command - # You have to give it a moment to stop or the stat fails - sleep(1) - end - end -end diff --git a/taste-tester/taste-tester/ssh.rb b/taste-tester/taste-tester/ssh.rb deleted file mode 100755 index e45cbc3..0000000 --- a/taste-tester/taste-tester/ssh.rb +++ /dev/null @@ -1,60 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -module TasteTester - # Thin ssh wrapper - class SSH - include TasteTester::Logging - include BetweenMeals::Util - - attr_reader :output - - def initialize(host, timeout = 5, tunnel = false) - @host = host - @timeout = timeout - @tunnel = tunnel - @cmds = [] - end - - def add(string) - @cmds << string - end - - alias_method :<<, :add - - def run - @status, @output = exec(cmd, logger) - end - - def run! - @status, @output = exec!(cmd, logger) - rescue => e - # rubocop:disable LineLength - error = <<-MSG -SSH returned error while connecting to #{TasteTester::Config.user}@#{@host} -The host might be broken or your SSH access is not working properly -Try doing 'ssh -v #{TasteTester::Config.user}@#{@host}' and come back once that works -MSG - # rubocop:enable LineLength - error.lines.each { |x| logger.error x.strip } - logger.error(e.message) - end - - private - - def cmd - @cmds.each do |cmd| - logger.info("Will run: '#{cmd}' on #{@host}") - end - cmds = @cmds.join(' && ') - cmd = "ssh -T -o BatchMode=yes -o ConnectTimeout=#{@timeout} " - cmd += "#{TasteTester::Config.user}@#{@host} " - if TasteTester::Config.user != 'root' - cc = Base64.encode64(cmds).gsub(/\n/, '') - cmd += "\"echo '#{cc}' | base64 --decode | sudo bash -x\"" - else - cmd += "\'#{cmds}\'" - end - cmd - end - end -end diff --git a/taste-tester/taste-tester/state.rb b/taste-tester/taste-tester/state.rb deleted file mode 100644 index c379741..0000000 --- a/taste-tester/taste-tester/state.rb +++ /dev/null @@ -1,87 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -require 'fileutils' -require 'socket' -require 'timeout' - -require_relative '../../between-meals/util' -require_relative 'config' - -module TasteTester - # State of taste-tester processes - class State - include TasteTester::Config - include TasteTester::Logging - include ::BetweenMeals::Util - - def initialize - ref_dir = File.dirname(File.expand_path( - TasteTester::Config.ref_file - )) - unless File.directory?(ref_dir) - begin - FileUtils.mkpath(ref_dir) - rescue => e - logger.error("Chef temp dir #{ref_dir} missing and can't be created") - logger.error(e) - exit(1) - end - end - end - - def port - TasteTester::State.read(:port) - end - - def port=(port) - write(:port, port) - end - - def ref - TasteTester::State.read(:ref) - end - - def ref=(ref) - write(:ref, ref) - end - - def self.port - TasteTester::State.read(:port) - end - - def wipe - if TasteTester::Config.ref_file && - File.exists?(TasteTester::Config.ref_file) - File.delete(TasteTester::Config.ref_file) - end - end - - private - - def write(key, value) - begin - state = JSON.parse(File.read(TasteTester::Config.ref_file)) - rescue - state = {} - end - state[key.to_s] = value - ff = File.open( - TasteTester::Config.ref_file, - 'w' - ) - ff.write(state.to_json) - ff.close - rescue => e - logger.error('Unable to write the reffile') - logger.debug(e) - exit 0 - end - - def self.read(key) - JSON.parse(File.read(TasteTester::Config.ref_file))[key.to_s] - rescue => e - logger.debug(e) - nil - end - end -end diff --git a/taste-tester/taste-tester/tunnel.rb b/taste-tester/taste-tester/tunnel.rb deleted file mode 100755 index 62d4664..0000000 --- a/taste-tester/taste-tester/tunnel.rb +++ /dev/null @@ -1,53 +0,0 @@ -# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 - -module TasteTester - # Thin ssh tunnel wrapper - class Tunnel - include TasteTester::Logging - include BetweenMeals::Util - - attr_reader :port - - def initialize(host, server, timeout = 5) - @host = host - @server = server - @timeout = timeout - if TasteTester::Config.testing_until - @delta_secs = TasteTester::Config.testing_until.strftime('%s').to_i - - Time.now.strftime('%s').to_i - else - @delta_secs = TasteTester::Config.testing_time - end - end - - def run - @port = TasteTester::Config.tunnel_port - logger.info("Setting up tunnel on port #{@port}") - @status, @output = exec!(cmd, logger) - rescue - logger.error 'Failed bringing up ssh tunnel' - exit(1) - end - - def cmd - cmds = "echo \\\$\\\$ > #{TasteTester::Config.timestamp_file} &&" + - " touch -t #{TasteTester::Config.testing_end_time}" + - " #{TasteTester::Config.timestamp_file} && sleep #{@delta_secs}" - cmd = "ssh -T -o BatchMode=yes -o ConnectTimeout=#{@timeout} " + - "-o ExitOnForwardFailure=yes -f -R #{@port}:localhost:" + - "#{@server.port} root@#{@host} \"#{cmds}\"" - cmd - end - - def self.kill(name) - ssh = TasteTester::SSH.new(name) - # Since commands are &&'d together, and we're using &&, we need to - # surround this in paryns, and make sure as a whole it evaluates - # to true so it doesn't mess up other things... even though this is - # the only thing we're currently executing in this SSH. - ssh << "( [ -s #{TasteTester::Config.timestamp_file} ]" + - " && kill -- -\$(cat #{TasteTester::Config.timestamp_file}); true )" - ssh.run! - end - end -end diff --git a/taste-tester/taste-untester b/taste-tester/taste-untester deleted file mode 100755 index 11cd567..0000000 --- a/taste-tester/taste-untester +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash - -CONFLINK='/etc/chef/client.rb' -PRODCONF='/etc/chef/client-prod.rb' -CERTLINK='/etc/chef/client.pem' -PRODCERT='/etc/chef/client-prod.pem' -STAMPFILE='/etc/chef/test_timestamp' -MYSELF=$0 -DRYRUN=0 -DEBUG=0 - -debug() { - [ "$DEBUG" -eq 1 ] && echo $* -} - -set_server_to_prod() { - ME=$(basename $MYSELF) - if [ -s $STAMPFILE ]; then - kill -- -$(cat $STAMPFILE) - fi - rm -f $CONFLINK - ln -s $PRODCONF $CONFLINK - # Legacy FB stuff, will go away - if [ -h $CERTLINK ]; then - rm $CERTLINK - ln -s $PRODCERT $CERTLINK - fi - rm -f $STAMPFILE - logger -p user.warning -t $ME Reverted to production Chef. - if [ -e '/usr/bin/wall' ]; then - echo "Reverted $(hostname) to production Chef." | wall - fi -} - -check_server() { - if [ ! -h $CONFLINK ]; then - return - fi - current_config=$(readlink $CONFLINK) - if [ "$current_config" = $PRODCONF ]; then - if [ -f "$STAMPFILE" ]; then - rm -f $STAMPFILE - fi - return - fi - - now=$(date +%s) - if [ "$(uname)" = 'Darwin' ]; then - stamp_time=$(stat -f "%m" -t "%z" $STAMPFILE) - stamp=$(date -r $stamp_time) - else - stamp=$(stat -c %y $STAMPFILE) - stamp_time=$(date +%s -d "$stamp") - fi - - debug "$now vs $stamp_time" - if [ "$now" -gt "$stamp_time" ]; then - if [ $DRYRUN -eq 0 ]; then - set_server_to_prod - else - echo "DRYRUN: Would return server to prod" - fi - fi -} - -while getopts 'dn' opt; do - case "$opt" in - d) - DEBUG=1 - ;; - n) - DRYRUN=1 - ;; - esac -done - -check_server