module SupermarketSync::Sync

> Supermarket Synchronization Logic Controller

Public Instance Methods

run!() click to toggle source
# File lib/supermarket_sync/sync.rb, line 26
def run! # rubocop: disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity
  Config.supermarkets.each do |name, cfg| # rubocop: disable BlockLength
    puts "Synchronizing #{name}"

    # => Set Configuration
    configure(cfg)

    # => Parse the Cookbooks List
    cookbooks = Array(Util.parse_json(Config.cookbooks_file)[:cookbooks])

    cookbooks.each do |cookbook| # rubocop: disable BlockLength
      cookbook = cookbook.keys.first if cookbook.is_a?(Hash)
      puts "Checking #{cookbook}"
      # => Grab Source Metadata
      source_meta = begin
                      src.get("/api/v1/cookbooks/#{cookbook}")
                    rescue Net::HTTPServerException => e
                      raise e unless e.response.code == '404'
                      puts 'Cookbook not available on Source Supermarket'
                      next
                    end
      # => Grab Latest Available Version Number
      latest = ::Gem::Version.new(::File.basename(source_meta['latest_version']))

      # => Grab Destination Metadata
      dest_meta = begin
                    dest.get("/api/v1/cookbooks/#{cookbook}")
                  rescue Net::HTTPServerException => e
                    raise e unless e.response.code == '404'
                    # => Cookbook not found -- Initial Upload
                    { 'latest_version' => '0.0.0' }
                  end
      # => Determine Current Version
      current = ::Gem::Version.new(::File.basename(dest_meta['latest_version']))

      if latest > current
        puts 'Updating...'
        puts "Source: #{latest}"
        puts "Destination: #{current}"

        # => Retrieve the Cookbook
        tgz = src.streaming_request("/api/v1/cookbooks/#{cookbook}/versions/#{latest}/download")

        # => Upload the Cookbook
        upload('other', tgz) unless Config.read_only

        # => Remove the Tempfile
        begin
          retries ||= 2
          ::File.delete(tgz)
        rescue => e # rubocop: disable RescueStandardError
          raise e if (retries -= 1).negative?
          puts "#{e.class}::#{e.message}"
          puts 'Could not delete Tempfile... Retrying'
          sleep 2
          retry
        end
        @notify&.updated&.push(source: source_meta, dest: dest_meta)
      end
      # => Identify Deprecated Cookbooks
      next unless source_meta['deprecated'] && !dest_meta['deprecated']
      @notify&.deprecated&.push(source: source_meta, dest: dest_meta)
    end
  ensure
    # => Send Notifications
    @notify&.send!
  end
end
src(url = nil) click to toggle source

> API Clients

# File lib/supermarket_sync/sync.rb, line 122
def src(url = nil)
  url ||= @src&.url
  raise ArgumentError, 'No URL supplied!' unless url
  return @src if @src&.url == url
  @src = Chef::HTTP::SimpleJSON.new(url)
end

Private Instance Methods

configure(context) click to toggle source

> Configuration Context

# File lib/supermarket_sync/sync.rb, line 99
        def configure(context) # rubocop: disable AbcSize, MethodLength
  Chef::Config.tap do |cfg|
    cfg.chef_server_url = context[:url]
    cfg.node_name       = context[:user] || ENV['SM_USER']
    cfg.client_key      = context[:key]  || ENV['SM_KEY']
    cfg.ssl_verify_mode = :verify_none
  end

  if Config.notification.any?
    @notify = Notifier.new do |cfg|
      cfg.url      = Config.notification[:url]
      cfg.channels = Config.notification[:channels]
      cfg.username = Config.notification[:username]
    end
  end

  src  context[:source] || Config.source
  dest context[:url]
end
dest(url = nil) click to toggle source
# File lib/supermarket_sync/sync.rb, line 129
        def dest(url = nil)
  url ||= @dest&.url
  raise ArgumentError, 'No URL supplied!' unless url
  return @dest if @dest&.url == url
  @dest = Chef::HTTP::SimpleJSON.new(url)
end
upload(category, tarball) click to toggle source
# File lib/supermarket_sync/sync.rb, line 136
        def upload(category, tarball) # rubocop: disable AbcSize, MethodLength
  uri = URI.parse(dest.url)
  uri.path = '/api/v1/cookbooks'
  resp = Chef::Knife::Core::CookbookSiteStreamingUploader.post(
    uri.to_s, Chef::Config[:node_name], Chef::Config[:client_key],
    tarball: ::File.open(tarball),
    cookbook: Chef::JSONCompat.to_json(category: category)
  )
  return if %w[200 201].include?(resp.code)
  msg = Chef::JSONCompat.to_json_pretty(Chef::JSONCompat.parse(resp.body)) rescue resp.body # rubocop: disable RescueModifier, LineLength
  puts resp.inspect
  raise "\nSupermarket Upload Error:\n#{msg}"
end