foreman_docker 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -3
  3. data/app/assets/javascripts/foreman_docker/image_step.js +44 -5
  4. data/app/controllers/concerns/foreman_docker/find_container.rb +19 -0
  5. data/app/controllers/containers/steps_controller.rb +25 -39
  6. data/app/controllers/containers_controller.rb +10 -43
  7. data/app/controllers/image_search_controller.rb +88 -0
  8. data/app/controllers/registries_controller.rb +47 -0
  9. data/app/helpers/container_steps_helper.rb +21 -4
  10. data/app/helpers/containers_helper.rb +19 -7
  11. data/app/models/concerns/foreman_docker/parameter_validators.rb +11 -0
  12. data/app/models/container.rb +20 -7
  13. data/app/models/docker_container_wizard_state.rb +30 -0
  14. data/app/models/docker_container_wizard_states/configuration.rb +7 -0
  15. data/app/models/docker_container_wizard_states/environment.rb +21 -0
  16. data/app/models/docker_container_wizard_states/environment_variable.rb +7 -0
  17. data/app/models/docker_container_wizard_states/image.rb +11 -0
  18. data/app/models/docker_container_wizard_states/preliminary.rb +11 -0
  19. data/app/models/docker_registry.rb +28 -0
  20. data/app/models/environment_variable.rb +5 -0
  21. data/app/models/foreman_docker/docker.rb +22 -3
  22. data/app/models/service/containers.rb +38 -0
  23. data/app/models/service/registry_api.rb +26 -0
  24. data/app/views/containers/_list.html.erb +19 -18
  25. data/app/views/containers/index.html.erb +11 -6
  26. data/app/views/containers/show.html.erb +26 -7
  27. data/app/views/containers/steps/_form_buttons.html.erb +1 -1
  28. data/app/views/containers/steps/configuration.html.erb +2 -2
  29. data/app/views/containers/steps/environment.html.erb +19 -6
  30. data/app/views/containers/steps/image.html.erb +58 -39
  31. data/app/views/containers/steps/preliminary.html.erb +21 -4
  32. data/app/views/foreman_docker/common_parameters/_environment_variable.html.erb +18 -0
  33. data/app/views/image_search/_repository_search_results.html.erb +12 -0
  34. data/app/views/registries/_form.html.erb +26 -0
  35. data/app/views/registries/edit.html.erb +3 -0
  36. data/app/views/registries/index.html.erb +28 -0
  37. data/app/views/registries/new.html.erb +3 -0
  38. data/config/routes.rb +11 -2
  39. data/db/migrate/20141024163003_create_docker_registries.rb +22 -0
  40. data/db/migrate/20141120123003_add_user_credentials_to_docker_registries.rb +6 -0
  41. data/db/migrate/20141209182008_remove_docker_tables.rb +72 -0
  42. data/db/migrate/20141222113313_create_wizard_states.rb +42 -0
  43. data/lib/foreman_docker/engine.rb +25 -5
  44. data/lib/foreman_docker/version.rb +1 -1
  45. data/test/factories/containers.rb +2 -0
  46. data/test/factories/docker_registry.rb +16 -0
  47. data/test/functionals/containers_steps_controller_test.rb +7 -40
  48. data/test/integration/container_steps_test.rb +24 -0
  49. data/test/integration/container_test.rb +18 -0
  50. data/test/units/container_test.rb +0 -7
  51. data/test/units/containers_service_test.rb +21 -0
  52. data/test/units/docker_registry_test.rb +24 -0
  53. metadata +45 -22
  54. data/app/models/docker_image.rb +0 -9
  55. data/app/models/docker_tag.rb +0 -8
  56. data/test/factories/docker_image.rb +0 -5
  57. data/test/factories/docker_tag.rb +0 -6
  58. data/test/units/docker_image_test.rb +0 -23
  59. data/test/units/docker_tag_test.rb +0 -35
@@ -2,10 +2,27 @@ module ContainerStepsHelper
2
2
  def container_wizard(step)
3
3
  wizard_header(
4
4
  step,
5
- _('Resource'),
6
- _('Image'),
7
- _('Configuration'),
8
- _('Environment')
5
+ *wizard_steps.map { |s| s.to_s.humanize }
9
6
  )
10
7
  end
8
+
9
+ def select_registry(f)
10
+ registries = DockerRegistry.with_taxonomy_scope_override(@location, @organization)
11
+ .authorized(:view_registries)
12
+ field(f, 'docker_container_wizard_states_image[registry_id]', :label => _("Registry")) do
13
+ collection_select :wizard_states_image, :registry_id,
14
+ registries,
15
+ :id, :name,
16
+ { :prompt => _("Select a registry") },
17
+ :class => "form-control", :disabled => registries.size == 0
18
+ end
19
+ end
20
+
21
+ def last_step?
22
+ step == wizard_steps.last
23
+ end
24
+
25
+ def taxonomy_icon(taxonomy)
26
+ taxonomy == 'locations' ? 'globe' : 'briefcase'
27
+ end
11
28
  end
@@ -1,10 +1,6 @@
1
1
  module ContainersHelper
2
2
  def managed_icon(container, resource)
3
- if managed?(container, resource)
4
- '<span class="glyphicon glyphicon-check"></span>'.html_safe
5
- else
6
- '<span class="glyphicon glyphicon-unchecked"></span>'.html_safe
7
- end
3
+ icon_text(managed?(container, resource) ? 'check' : 'unchecked')
8
4
  end
9
5
 
10
6
  def managed?(container, resource)
@@ -12,7 +8,9 @@ module ContainersHelper
12
8
  end
13
9
 
14
10
  def uuids_in_resource(resource)
15
- Container.where(:compute_resource_id => resource.id).pluck(:uuid)
11
+ @uuids_in_resource ||= {}
12
+ @uuids_in_resource[resource.id] ||= Container.where(:compute_resource_id => resource.id)
13
+ .pluck(:uuid)
16
14
  end
17
15
 
18
16
  def link_to_container(container, resource)
@@ -20,6 +18,12 @@ module ContainersHelper
20
18
  container_link_hash(container, resource)
21
19
  end
22
20
 
21
+ def link_to_taxonomies(taxonomies)
22
+ taxonomies.map do |taxonomy|
23
+ link_to(taxonomy)
24
+ end.join(' ')
25
+ end
26
+
23
27
  def container_link_hash(container, resource)
24
28
  if managed?(container, resource)
25
29
  hash_for_container_path(:id => Container.find_by_uuid(container.identity).id)
@@ -47,8 +51,16 @@ module ContainersHelper
47
51
  )
48
52
  end
49
53
 
50
- def auto_complete_search(name, val, options = {})
54
+ def auto_complete_docker_search(name, val, options = {})
51
55
  addClass options, 'form-control'
52
56
  text_field_tag(name, val, options)
53
57
  end
58
+
59
+ def hub_url(image)
60
+ if image['is_official']
61
+ "https://registry.hub.docker.com/_/#{image['name']}"
62
+ else
63
+ "https://registry.hub.docker.com/u/#{image['name']}"
64
+ end
65
+ end
54
66
  end
@@ -0,0 +1,11 @@
1
+ module ForemanDocker
2
+ module ParameterValidators
3
+ extend ActiveSupport::Concern
4
+ include ::ParameterValidators
5
+
6
+ def parameters_symbol
7
+ return :environment_variables if is_a? Container
8
+ super
9
+ end
10
+ end
11
+ end
@@ -1,22 +1,35 @@
1
1
  class Container < ActiveRecord::Base
2
2
  include Authorizable
3
+ include Taxonomix
3
4
 
4
5
  belongs_to :compute_resource
5
- belongs_to :image, :class_name => 'DockerImage', :foreign_key => 'docker_image_id'
6
- belongs_to :tag, :class_name => 'DockerTag', :foreign_key => 'docker_tag_id'
6
+ belongs_to :registry, :class_name => "DockerRegistry", :foreign_key => :registry_id
7
+ has_many :environment_variables, :dependent => :destroy, :foreign_key => :reference_id,
8
+ :inverse_of => :container,
9
+ :class_name => 'EnvironmentVariable',
10
+ :validate => false
11
+ accepts_nested_attributes_for :environment_variables, :allow_destroy => true
12
+ include ForemanDocker::ParameterValidators
7
13
 
8
- attr_accessible :command, :image, :name, :compute_resource_id, :entrypoint,
9
- :cpu_set, :cpu_shares, :memory, :tty, :attach_stdin,
10
- :attach_stdout, :attach_stderr, :tag, :uuid
14
+ attr_accessible :command, :repository_name, :name, :compute_resource_id, :entrypoint,
15
+ :cpu_set, :cpu_shares, :memory, :tty, :attach_stdin, :registry_id,
16
+ :attach_stdout, :attach_stderr, :tag, :uuid, :environment_variables_attributes
17
+
18
+ def repository_pull_url
19
+ repo = tag.blank? ? repository_name : "#{repository_name}:#{tag}"
20
+ repo = registry.prefixed_url(repo) if registry
21
+ repo
22
+ end
11
23
 
12
24
  def parametrize
13
25
  { 'name' => name, # key has to be lower case to be picked up by the Docker API
14
- 'Image' => tag.tag.blank? ? image.image_id : "#{image.image_id}:#{tag.tag}",
26
+ 'Image' => repository_pull_url,
15
27
  'Tty' => tty, 'Memory' => memory,
16
28
  'Entrypoint' => entrypoint.try(:split), 'Cmd' => command.try(:split),
17
29
  'AttachStdout' => attach_stdout, 'AttachStdin' => attach_stdin,
18
30
  'AttachStderr' => attach_stderr, 'CpuShares' => cpu_shares,
19
- 'Cpuset' => cpu_set }
31
+ 'Cpuset' => cpu_set,
32
+ 'Env' => environment_variables.map { |env| "#{env.name}=#{env.value}" } }
20
33
  end
21
34
 
22
35
  def in_fog
@@ -0,0 +1,30 @@
1
+ class DockerContainerWizardState < ActiveRecord::Base
2
+ has_one :preliminary, :class_name => DockerContainerWizardStates::Preliminary,
3
+ :dependent => :destroy, :validate => true, :autosave => true
4
+ has_one :image, :class_name => DockerContainerWizardStates::Image,
5
+ :dependent => :destroy, :validate => true, :autosave => true
6
+ has_one :configuration, :class_name => DockerContainerWizardStates::Configuration,
7
+ :dependent => :destroy, :validate => true, :autosave => true
8
+ has_one :environment, :class_name => DockerContainerWizardStates::Environment,
9
+ :dependent => :destroy, :validate => true, :autosave => true
10
+
11
+ delegate :compute_resource_id, :to => :preliminary
12
+ delegate :environment_variables, :to => :environment
13
+
14
+ def container_attributes
15
+ { :repository_name => image.repository_name,
16
+ :tag => image.tag,
17
+ :registry_id => image.registry_id,
18
+ :name => configuration.name,
19
+ :compute_resource_id => preliminary.compute_resource_id,
20
+ :tty => environment.tty,
21
+ :memory => configuration.memory,
22
+ :entrypoint => configuration.entrypoint,
23
+ :command => configuration.command,
24
+ :attach_stdout => environment.attach_stdout,
25
+ :attach_stdin => environment.attach_stdin,
26
+ :attach_stderr => environment.attach_stderr,
27
+ :cpu_shares => configuration.cpu_shares,
28
+ :cpu_set => configuration.cpu_set }
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ module DockerContainerWizardStates
2
+ class Configuration < ActiveRecord::Base
3
+ self.table_name_prefix = 'docker_container_wizard_states_'
4
+ belongs_to :wizard_state, :class_name => DockerContainerWizardState,
5
+ :foreign_key => :docker_container_wizard_state_id
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ module DockerContainerWizardStates
2
+ class Environment < ActiveRecord::Base
3
+ self.table_name_prefix = 'docker_container_wizard_states_'
4
+ belongs_to :wizard_state, :class_name => DockerContainerWizardState
5
+ # Fix me:
6
+ # Validations are off on this association as there's a bug in ::Parameter
7
+ # that forces validation of reference_id. This will fail on new records as
8
+ # validations are executed before parent and children records have been persisted.
9
+ has_many :environment_variables, :dependent => :destroy, :foreign_key => :reference_id,
10
+ :inverse_of => :environment,
11
+ :class_name => 'DockerContainerWizardStates::EnvironmentVariable',
12
+ :validate => false
13
+ include ::ParameterValidators
14
+
15
+ accepts_nested_attributes_for :environment_variables, :allow_destroy => true
16
+
17
+ def parameters_symbol
18
+ :environment_variables
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module DockerContainerWizardStates
2
+ class EnvironmentVariable < Parameter
3
+ belongs_to :environment, :foreign_key => :reference_id, :inverse_of => :environment_variables,
4
+ :class_name => 'DockerContainerWizardStates::Environment'
5
+ validates :name, :uniqueness => { :scope => :reference_id }
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module DockerContainerWizardStates
2
+ class Image < ActiveRecord::Base
3
+ self.table_name_prefix = 'docker_container_wizard_states_'
4
+ belongs_to :wizard_state, :class_name => DockerContainerWizardState,
5
+ :foreign_key => :docker_container_wizard_state_id
6
+ delegate :compute_resource_id, :to => :wizard_state
7
+
8
+ validates :tag, :presence => true
9
+ validates :repository_name, :presence => true
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module DockerContainerWizardStates
2
+ class Preliminary < ActiveRecord::Base
3
+ include Taxonomix
4
+
5
+ self.table_name_prefix = 'docker_container_wizard_states_'
6
+ belongs_to :wizard_state, :class_name => DockerContainerWizardState,
7
+ :foreign_key => :docker_container_wizard_state_id
8
+
9
+ validates :compute_resource_id, :presence => true
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ class DockerRegistry < ActiveRecord::Base
2
+ include Authorizable
3
+ include Taxonomix
4
+ include Encryptable
5
+
6
+ has_many :containers, :foreign_key => "registry_id", :dependent => :destroy
7
+ encrypts :password
8
+
9
+ scoped_search :on => :name, :complete_value => true
10
+ scoped_search :on => :url
11
+
12
+ def used_location_ids
13
+ Location.joins(:taxable_taxonomies).where(
14
+ 'taxable_taxonomies.taxable_type' => 'DockerRegistry',
15
+ 'taxable_taxonomies.taxable_id' => id).pluck(:id)
16
+ end
17
+
18
+ def used_organization_ids
19
+ Organization.joins(:taxable_taxonomies).where(
20
+ 'taxable_taxonomies.taxable_type' => 'DockerRegistry',
21
+ 'taxable_taxonomies.taxable_id' => id).pluck(:id)
22
+ end
23
+
24
+ def prefixed_url(image_name)
25
+ uri = URI(url)
26
+ "#{uri.hostname}:#{uri.port}/#{image_name}"
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ class EnvironmentVariable < Parameter
2
+ belongs_to :container, :foreign_key => :reference_id, :inverse_of => :environment_variables
3
+ audited :except => [:priority], :associated_with => :container, :allow_mass_assignment => true
4
+ validates :name, :uniqueness => { :scope => :reference_id }
5
+ end
@@ -28,12 +28,18 @@ module ForemanDocker
28
28
  client.images.all
29
29
  end
30
30
 
31
- def all_images(filter = '')
31
+ def local_images(filter = '')
32
32
  client # initialize Docker-Api
33
- # we are using an older version of docker-api, which differs from the current
34
33
  ::Docker::Image.all('filter' => filter)
35
34
  end
36
35
 
36
+ def tags_for_local_image(image)
37
+ image.info['RepoTags'].map do |image_tag|
38
+ _, tag = image_tag.split(':')
39
+ tag
40
+ end
41
+ end
42
+
37
43
  def exist?(name)
38
44
  ::Docker::Image.exist?(name)
39
45
  end
@@ -42,6 +48,18 @@ module ForemanDocker
42
48
  client.image_get(id)
43
49
  end
44
50
 
51
+ def tags(image_name)
52
+ if exist?(image_name)
53
+ tags_for_local_image(local_images(image_name).first)
54
+ else
55
+ # If image is not found in the compute resource, get the tags from the Hub
56
+ hub_api_url = "https://index.docker.io/v1/repositories/#{image_name}/tags"
57
+ JSON.parse(URI.parse(hub_api_url).read).map do |tag|
58
+ tag['name']
59
+ end
60
+ end
61
+ end
62
+
45
63
  def search(term = '')
46
64
  client.images.image_search(:term => term)
47
65
  end
@@ -77,10 +95,11 @@ module ForemanDocker
77
95
 
78
96
  def test_connection(options = {})
79
97
  super
80
- client
98
+ client.present?
81
99
  # This should only rescue Fog::Errors, but Fog returns all kinds of errors...
82
100
  rescue => e
83
101
  errors[:base] << e.message
102
+ false
84
103
  end
85
104
 
86
105
  protected
@@ -0,0 +1,38 @@
1
+ module Service
2
+ class Containers
3
+ def self.start_container!(wizard_state)
4
+ ActiveRecord::Base.transaction do
5
+ container = Container.new(wizard_state.container_attributes) do |r|
6
+ # eagerly load environment variables
7
+ state = DockerContainerWizardState.includes(:environment => [:environment_variables])
8
+ .find(wizard_state.id)
9
+ state.environment_variables.each do |environment_variable|
10
+ r.environment_variables.build :name => environment_variable.name,
11
+ :value => environment_variable.value,
12
+ :priority => environment_variable.priority
13
+ end
14
+ end
15
+ Taxonomy.enabled_taxonomies.each do |taxonomy|
16
+ container.send(:"#{taxonomy}=", wizard_state.preliminary.send(:"#{taxonomy}"))
17
+ end
18
+
19
+ fail ActiveRecord::Rollback unless start_container(container)
20
+
21
+ container.save!
22
+ destroy_wizard_state(wizard_state)
23
+ container
24
+ end
25
+ end
26
+
27
+ def self.start_container(container)
28
+ started = container.compute_resource.create_container(container.parametrize)
29
+ container.uuid = started.id if started
30
+ started
31
+ end
32
+
33
+ def self.destroy_wizard_state(wizard_state)
34
+ wizard_state.destroy
35
+ DockerContainerWizardState.destroy_all(["updated_at < ?", (Time.now - 24.hours)])
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ module Service
2
+ class RegistryApi
3
+ DEFAULTS = { :url => 'http://localhost:5000' }
4
+ attr_reader :config
5
+
6
+ def initialize(params = {})
7
+ config = DEFAULTS.merge(params)
8
+ uri = URI(config.delete(:url))
9
+ uri.user = config.delete(:user)
10
+ uri.password = config.delete(:password)
11
+ @config = config.merge(:url => uri.to_s)
12
+ end
13
+
14
+ def search(aquery)
15
+ response = RestClient.get(config[:url] + '/v1/search',
16
+ :params => { :q => aquery }, :accept => :json)
17
+ JSON.parse(response.body)
18
+ end
19
+
20
+ def list_repository_tags(arepository)
21
+ response = RestClient.get(config[:url] + "/v1/repositories/#{arepository}/tags",
22
+ :accept => :json)
23
+ JSON.parse(response.body)
24
+ end
25
+ end
26
+ end
@@ -1,25 +1,29 @@
1
1
  <table class="table table-bordered table-striped table-condensed" data-table="inline">
2
- </thead>
3
- <tr>
4
- <th class="text-center"><%= sort :name, :as => _("Name") %></th>
5
- <th class="hidden-tablet hidden-xs text-center"><%= sort :status, :as => _("Status") %></th>
6
- <th class="hidden-tablet hidden-xs text-center"><%= sort :image, :as => _("Image") %></th>
7
- <th class="hidden-tablet hidden-xs text-center"><%= sort :command, :as => _("Command") %></th>
8
- <th class="hidden-tablet hidden-xs text-center"><%= sort :uptime, :as => _("Uptime") %></th>
9
- <th class="hidden-tablet hidden-xs text-center"><%= sort :running_on, :as => _("Running on") %></th>
10
- <th class="hidden-tablet hidden-xs text-center"><%= sort :managed , :as => _("Managed") %></th>
11
- <th></th>
12
- </tr>
2
+ <thead>
3
+ <tr>
4
+ <th class="text-center"><%= _("Name") %></th>
5
+ <th class="hidden-tablet hidden-xs text-center"><%= _("Status") %></th>
6
+ <th class="hidden-tablet hidden-xs text-center"><%= _("Image") %></th>
7
+ <th class="hidden-tablet hidden-xs text-center"><%= _("Command") %></th>
8
+ <th class="hidden-tablet hidden-xs text-center"><%= _("Uptime") %></th>
9
+ <th class="hidden-tablet hidden-xs text-center"><%= _("Running on") %></th>
10
+ <th class="hidden-tablet hidden-xs text-center"><%= _("Managed") %></th>
11
+ <th></th>
12
+ </tr>
13
13
  </thead>
14
14
 
15
+ <tbody>
15
16
  <% containers.each do |container| %>
16
17
  <tr>
17
18
  <td class="ellipsis text-center"><%= link_to_container(container, resource) %></td>
18
- <td class="hidden-tablet hidden-xs text-center"><span <%= vm_power_class(container.ready?) %>><%= vm_state(container) %></span></td>
19
+ <td class="hidden-tablet hidden-xs text-center">
20
+ <span <%= vm_power_class(container.ready?) %>><%= vm_state(container) %></span></td>
19
21
  <td class="hidden-tablet hidden-xs text-center"><%= trunc(container.image_friendly_name) %></td>
20
22
  <td class="hidden-tablet hidden-xs text-center"><%= trunc(container.command) %></td>
21
- <td class="hidden-tablet hidden-xs text-center"><span class="glyphicon glyphicon-time"></span> <%= container.ready? ? time_ago_in_words(container.started_at) : "N/A" %></td>
22
- <td class="hidden-tablet hidden-xs text-center"><%= link_to resource, compute_resource_path(resource)%> </td>
23
+ <td class="hidden-tablet hidden-xs text-center">
24
+ <span class="glyphicon glyphicon-time"></span> <%= container.ready? ? time_ago_in_words(container.started_at) : "N/A" %>
25
+ </td>
26
+ <td class="hidden-tablet hidden-xs text-center"><%= link_to resource, compute_resource_path(resource) %> </td>
23
27
  <td class="hidden-tablet hidden-xs text-center"><%= managed_icon(container, resource) %></td>
24
28
  <% @compute_resource = resource %>
25
29
  <td><%= action_buttons(vm_power_action(container),
@@ -30,8 +34,5 @@
30
34
  :confirm => _("Delete %s?") % container.name)) %></td>
31
35
  </tr>
32
36
  <% end %>
37
+ </tbody>
33
38
  </table>
34
-
35
- <!-- To be replaced by will_paginate_with_info after 1.6 support is deprecated -->
36
- <%= page_entries_info containers %>
37
- <%= will_paginate containers %>