foreman_docker 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 %>