Project

General

Profile

Download (3.18 KB) Statistics
| Branch: | Tag: | Revision:

foreman_docker / app / models / service / registry_api.rb @ f785e5ae

1
module Service
2
  class RegistryApi
3
    DOCKER_HUB = 'https://index.docker.io/'.freeze
4
    DEFAULTS = {
5
      url: 'http://localhost:5000'.freeze,
6
      connection: { omit_default_port: true,
7
                    headers: { "Content-Type" => "application/json" }
8
                  }
9
    }
10

    
11
    attr_accessor :config, :url
12
    delegate :logger, :to => Rails
13

    
14
    def initialize(params = {})
15
      self.config = DEFAULTS.merge(params)
16
      self.url = config[:url]
17

    
18
      Docker.logger = logger if Rails.env.development? || Rails.env.test?
19
    end
20

    
21
    def connection
22
      @connection ||= ::Docker::Connection.new(url, default_connection_options.merge(credentials))
23
    end
24

    
25
    def get(path, params = nil)
26
      response = connection.get('/'.freeze, params,
27
                                connection.options.merge({ path: "#{path}" }))
28
      response = parse_json(response)
29
      response
30
    end
31

    
32
    # Since the Registry API v2 does not support a search the v1 endpoint is used
33
    # Newer registries will fail, the v2 catalog endpoint is used
34
    def search(query)
35
      get('/v1/search'.freeze, { q: query })
36
    rescue => e
37
      logger.warn "API v1 - Search failed #{e.backtrace}"
38
      { 'results' => catalog(query) }
39
    end
40

    
41
    # Some Registries might have this endpoint not implemented/enabled
42
    def catalog(query)
43
      get('/v2/_catalog'.freeze)['repositories'].select do |image|
44
        image =~ /^#{query}/
45
      end.map { |image_name| { 'name' => image_name } }
46
    end
47

    
48
    def tags(image_name, query = nil)
49
      result = get_tags(image_name)
50
      result = result.keys.map { |t| {'name' => t.to_s } } if result.is_a? Hash
51
      result = filter_tags(result, query) if query
52
      result
53
    end
54

    
55
    def ok?
56
      get('/v1/'.freeze).match("Docker Registry API")
57
    rescue => e
58
      logger.warn "API v1 - Ping failed #{e.backtrace}"
59
      get('/v2/'.freeze).is_a? Hash
60
    end
61

    
62
    def self.docker_hub
63
      @@docker_hub ||= new(url: DOCKER_HUB)
64
    end
65

    
66
    private
67

    
68
    def default_connection_options
69
      @default_connection_options ||= DEFAULTS[:connection].tap do |defaults|
70
        defaults[:ssl_verify_peer] = config.fetch(:verify_ssl, true)
71
      end
72
    end
73

    
74
    def parse_json(string)
75
      JSON.parse(string)
76
    rescue => e
77
      logger.warn "JSON parsing failed: #{e.backtrace}"
78
      string
79
    end
80

    
81
    def get_tags(image_name)
82
      get("/v1/repositories/#{image_name}/tags")
83
    rescue => e
84
      Foreman::Logging.exception("API v1 - Repository images request failed", e)
85
      tags_v2(image_name)
86
    end
87

    
88
    def tags_v2(image_name)
89
      get("/v2/#{image_name}/tags/list")['tags'].map { |tag| { 'name' => tag } }
90
    rescue Docker::Error::NotFoundError
91
      []
92
    rescue Excon::Error::Found => e
93
      if e.response.status == 302
94
        new_path = URI.parse(e.response.headers['Location']).path
95
        get(new_path)['tags'].map { |tag| { 'name' => tag } }
96
      else
97
        ::Foreman.exception "Could not load tags using v2", e
98
        []
99
      end
100
    end
101

    
102
    def credentials
103
      { user: config.fetch(:user, nil),
104
        password: config.fetch(:password, nil) }
105
    end
106

    
107
    def filter_tags(result, query)
108
      result.select do |tag_name|
109
        tag_name['name'] =~ /^#{query}/
110
      end
111
    end
112
  end
113
end