theforeman/foreman-docker

View on GitHub
app/models/foreman_docker/docker.rb

Summary

Maintainability
A
3 hrs
Test Coverage
require 'uri'

module ForemanDocker
  class Docker < ::ComputeResource
    validates :url, :format => { :with => URI.regexp }, :presence => true
    validates :email, :format => { :with => /.+@.+\..+/i }, :allow_blank => true

    def self.model_name
      ComputeResource.model_name
    end

    def self.get_container(container)
      conn = container.compute_resource.docker_connection
      ::Docker::Container.get(container.uuid, {}, conn)
    end

    def capabilities
      []
    end

    def supports_update?
      false
    end

    def provided_attributes
      super.merge(:mac => :mac)
    end

    def max_memory
      16 * 1024 * 1024 * 1024
    end

    def max_cpu_count
      ::Docker.info['NCPU'] || 1
    end

    def available_images
      ::Docker::Image.all({}, docker_connection)
    end

    def local_images(filter = '')
      ::Docker::Image.all({ 'filter' => filter }, docker_connection)
    end

    def tags_for_local_image(image, tag = nil)
      result = image.info['RepoTags'].map do |image_tag|
        _, tag = image_tag.split(':')
        tag
      end
      result = filter_tags(result, tag) if tag
      result
    end

    def exist?(name)
      ::Docker::Image.exist?(name, {}, docker_connection)
    end

    def image(id)
      ::Docker::Image.get(id, {}, docker_connection)
    end

    def tags(image_name)
      if exist?(image_name)
        tags_for_local_image(image(image_name))
      else
        Service::RegistryApi.docker_hub.tags(image_name).map { |tag| tag['name'] }
      end
    end

    def search(term = '')
      client.images.image_search(:term => term)
    end

    def provider_friendly_name
      'Docker'
    end

    def create_container(args = {})
      options = vm_instance_defaults.merge(args)
      logger.debug("Creating container with the following options: #{options.inspect}")
      docker_command do
        ::Docker::Container.create(options, docker_connection)
      end
    end

    def create_image(args = {})
      logger.debug("Creating docker image with the following options: #{args.inspect}")
      docker_command do
        ::Docker::Image.create(args, credentials, docker_connection)
      end
    end

    def vm_instance_defaults
      ActiveSupport::HashWithIndifferentAccess.new('name' => "foreman_#{Time.now.to_i}",
                                                   'Cmd' => ['/bin/bash'])
    end

    def console(uuid)
      test_connection
      container = ::Docker::Container.get(uuid, {}, docker_connection)
      {
        :name       => container.info['Name'],
        'timestamp' => Time.now.utc,
        'output'    => container.logs(:stdout => true, :tail => 100)
      }
    end

    def api_version
      ::Docker.version(docker_connection)
    end

    def authenticate!
      ::Docker.authenticate!(credentials, docker_connection)
    end

    def test_connection(options = {})
      super
      api_version
      credentials.empty? ? true : authenticate!
    # This should only rescue Fog::Errors, but Fog returns all kinds of errors...
    rescue => e
      errors[:base] << e.message
      false
    end

    def docker_connection
      @docker_connection ||= ::Docker::Connection.new(url, credentials)
    end

    protected

    def filter_tags(result, query)
      result.select do |tag_name|
        tag_name['name'] =~ /^#{query}/
      end
    end

    def docker_command
      yield
    rescue Excon::Errors::Error, ::Docker::Error::DockerError => e
      logger.debug "Fog error: #{e.message}\n " + e.backtrace.join("\n ")
      errors.add(:base,
                 _("Error creating communicating with Docker. Check the Foreman logs: %s") %
                 e.message.to_s)
      false
    end

    def bootstrap(args)
      client.servers.bootstrap vm_instance_defaults.merge(args.to_hash)
    rescue Fog::Errors::Error => e
      errors.add(:base, e.to_s)
      false
    end

    def client
      opts = {
        :provider => 'fogdocker',
        :docker_url => url
      }
      opts[:docker_username] = user if user.present?
      opts[:docker_password] = password if password.present?
      opts[:docker_email] = email if email.present?
      @client ||= ::Fog::Compute.new(opts)
    end

    def credentials
      @credentials ||= {}.tap do |options|
        options[:username] = user if user.present?
        options[:password] = password if password.present?
        options[:email] = email if email.present?
      end
    end
  end
end