diff --git a/app/controllers/containers/steps_controller.rb b/app/controllers/containers/steps_controller.rb index 01ba585..829ca24 100644 --- a/app/controllers/containers/steps_controller.rb +++ b/app/controllers/containers/steps_controller.rb @@ -42,7 +42,7 @@ def set_form def create_container @state.send(:"create_#{step}", params[:"docker_container_wizard_states_#{step}"]) - container = Service::Containers.start_container!(@state) + container = ForemanDocker::Service::Containers.start_container!(@state) if container.present? process_success(:object => container, :success_redirect => container_path(container)) else diff --git a/app/controllers/image_search_controller.rb b/app/controllers/image_search_controller.rb index 88bb76c..310b426 100644 --- a/app/controllers/image_search_controller.rb +++ b/app/controllers/image_search_controller.rb @@ -34,7 +34,7 @@ def use_hub? end def hub_image_exists?(terms) - @compute_resource.exist?(terms) + ::Docker::Image.exist?(terms) end def hub_auto_complete_image_tags(terms) @@ -61,10 +61,9 @@ def registry_auto_complete_image_tags(terms) end def registry_search_image(terms) - r = ::Service::RegistryApi.new(:url => @registry.url, - :user => @registry.username, - :password => @registry.password).search(terms) - r['results'] + ::Service::RegistryApi.new(:url => @registry.url, + :user => @registry.username, + :password => @registry.password).search(terms)['results'] end def action_permission diff --git a/app/helpers/containers_helper.rb b/app/helpers/containers_helper.rb index 5549c4a..e742889 100644 --- a/app/helpers/containers_helper.rb +++ b/app/helpers/containers_helper.rb @@ -33,22 +33,28 @@ def container_link_hash(container, resource) end end + def container_title(container) + title = container.name.titleize + title += if container.uuid.present? + "- #{container.in_fog.name}" + else + _(" - provisioning ") + image_tag('spinner.gif') + end + title(container.name.titleize, title.html_safe) + end + def container_title_actions(container) @compute_resource = container.compute_resource + container_title(container) title_actions( - button_group( - link_to(_('Commit'), '#commit-modal', :'data-toggle' => 'modal') - ), - button_group(vm_power_action(container.in_fog)), - button_group( - display_delete_if_authorized( - hash_for_container_path(:id => container.id) - .merge(:auth_object => container, - :auth_action => 'destroy', - :authorizer => authorizer), - :confirm => _("Delete %s?") % container.name) - ) - ) + button_group(link_to(_('Commit'), '#commit-modal', :'data-toggle' => 'modal')), + button_group(vm_power_action(container.in_fog)), + button_group(display_delete_if_authorized( + hash_for_container_path(:id => container.id).merge(:auth_object => container, + :auth_action => 'destroy', + :authorizer => authorizer), + :confirm => _("Delete %s?") % container.name)) + ) if container.uuid.present? end def auto_complete_docker_search(name, val, options = {}) @@ -63,4 +69,20 @@ def hub_url(image) "https://registry.hub.docker.com/u/#{image['name']}" end end + + def fog_property(container) + return 'Not available' unless container.uuid.present? + yield + end + + def pair_attributes_table(attributes) + table = "" + attributes.each do |pair| + pair = pair.split("=") + table += "" + end + table += '
#{pair.first} #{pair.second}
' + table.html_safe + end end diff --git a/app/models/concerns/fog_extensions/fogdocker/server.rb b/app/models/concerns/fog_extensions/fogdocker/server.rb index 5ca3d4a..81331d8 100644 --- a/app/models/concerns/fog_extensions/fogdocker/server.rb +++ b/app/models/concerns/fog_extensions/fogdocker/server.rb @@ -27,7 +27,7 @@ def command end def poweroff - service.vm_action(:id => id, :action => :kill) + service.container_action(:id => id, :action => :kill) end def reset diff --git a/app/models/container.rb b/app/models/container.rb index da2a7cc..31f01d2 100644 --- a/app/models/container.rb +++ b/app/models/container.rb @@ -1,6 +1,7 @@ class Container < ActiveRecord::Base include Authorizable include Taxonomix + include ForemanTasks::Concerns::ActionSubject belongs_to :compute_resource belongs_to :registry, :class_name => "DockerRegistry", :foreign_key => :registry_id @@ -35,4 +36,8 @@ def parametrize def in_fog @fog_container ||= compute_resource.vms.get(uuid) end + + def to_hash + parametrize + end end diff --git a/app/models/foreman_docker/docker.rb b/app/models/foreman_docker/docker.rb index fea712a..6464a0b 100644 --- a/app/models/foreman_docker/docker.rb +++ b/app/models/foreman_docker/docker.rb @@ -44,16 +44,12 @@ def tags_for_local_image(image) end end - def exist?(name) - ::Docker::Image.exist?(name) - end - def image(id) client.image_get(id) end def tags(image_name) - if exist?(image_name) + if ::Docker::Image.exist?(image_name) tags_for_local_image(local_images(image_name).first) else # If image is not found in the compute resource, get the tags from the Hub @@ -73,23 +69,19 @@ def provider_friendly_name end def create_container(args = {}) - options = vm_instance_defaults.merge(args) + options = vm_instance_defaults.merge(args) { |_, default, new| new.empty? ? default : new } logger.debug("Creating container with the following options: #{options.inspect}") - docker_command do - ::Docker::Container.create(options) - end + docker_command { ::Docker::Container.create(options) } end def create_image(args = {}) + return true if ::Docker::Image.exist?(args[:fromImage]) logger.debug("Creating docker image with the following options: #{args.inspect}") - docker_command do - ::Docker::Image.create(args) - end + docker_command { ::Docker::Image.create(args) } end def vm_instance_defaults - ActiveSupport::HashWithIndifferentAccess.new('name' => "foreman_#{Time.now.to_i}", - 'Cmd' => ['/bin/bash']) + { 'name' => "foreman_#{Time.now.to_i}", 'Cmd' => ['/bin/bash'] } end def console(uuid) diff --git a/app/models/foreman_docker/service/actions/container/provision.rb b/app/models/foreman_docker/service/actions/container/provision.rb new file mode 100644 index 0000000..4f4d198 --- /dev/null +++ b/app/models/foreman_docker/service/actions/container/provision.rb @@ -0,0 +1,26 @@ +module ForemanDocker + module Service + module Actions + module Container + class Provision < ::Actions::EntryAction + def plan(container) + action_subject(container) + + sequence do + plan_action(Container::Pull, container) + plan_action(Container::Start, container) + end + end + + def humanized_name + _('Provision container') + end + + def finalize + action_logger.info('Finished provisioning of Docker container') + end + end + end + end + end +end diff --git a/app/models/foreman_docker/service/actions/container/pull.rb b/app/models/foreman_docker/service/actions/container/pull.rb new file mode 100644 index 0000000..a71fc5c --- /dev/null +++ b/app/models/foreman_docker/service/actions/container/pull.rb @@ -0,0 +1,44 @@ +module ForemanDocker + module Service + module Actions + module Container + class Pull < ::Actions::EntryAction + def plan(container) + action_subject(container) + plan_self(:container => container.id) + end + + def run(event = nil) + case event + when :done # do nothing and move to next step + else + suspend do |suspended_action| + Thread.new do + begin + container = ::Container.find(input[:container]) + container.compute_resource + .create_image(:fromImage => container.repository_pull_url) + suspended_action << :done + rescue => e + action_logger.error(e.message) + action_logger.debug(e.backtrace.join("\n ")) + error!(e) + end + end + end + end + end + + def humanized_name + _('Pull') + end + + def finalize + container = ::Container.find(input[:container]) + action_logger.info("[Docker] Finished pulling image #{container.repository_pull_url}") + end + end + end + end + end +end diff --git a/app/models/foreman_docker/service/actions/container/start.rb b/app/models/foreman_docker/service/actions/container/start.rb new file mode 100644 index 0000000..2c7d12a --- /dev/null +++ b/app/models/foreman_docker/service/actions/container/start.rb @@ -0,0 +1,30 @@ +module ForemanDocker + module Service + module Actions + module Container + class Start < ::Actions::EntryAction + def plan(container) + action_subject(container) + plan_self(:container => container.id) + end + + def run + container = ::Container.find(input[:container]) + started = container.compute_resource.create_container(container.parametrize) + container.update_attribute(:uuid, started.id) if started + started + end + + def humanized_name + _('Start') + end + + def finalize + container = ::Container.find(input[:container]) + action_logger.info("[Docker] container #{container.name} successfully started") + end + end + end + end + end +end diff --git a/app/models/foreman_docker/service/containers.rb b/app/models/foreman_docker/service/containers.rb new file mode 100644 index 0000000..cf364ad --- /dev/null +++ b/app/models/foreman_docker/service/containers.rb @@ -0,0 +1,34 @@ +module ForemanDocker + module Service + class Containers + def self.start_container!(wizard_state) + container = ActiveRecord::Base.transaction do + container = Container.new(wizard_state.container_attributes) do |r| + # eagerly load environment variables + state = DockerContainerWizardState.includes(:environment => [:environment_variables]) + .find(wizard_state.id) + state.environment_variables.each do |environment_variable| + r.environment_variables.build :name => environment_variable.name, + :value => environment_variable.value, + :priority => environment_variable.priority + end + end + Taxonomy.enabled_taxonomies.each do |taxonomy| + container.send(:"#{taxonomy}=", wizard_state.preliminary.send(:"#{taxonomy}")) + end + container.save! + container + end + + destroy_wizard_state(wizard_state) + ForemanTasks.async_task(Service::Actions::Container::Provision, container) + container + end + + def self.destroy_wizard_state(wizard_state) + wizard_state.destroy + DockerContainerWizardState.destroy_all(["updated_at < ?", (Time.now - 24.hours)]) + end + end + end +end diff --git a/app/models/service/registry_api.rb b/app/models/foreman_docker/service/registry_api.rb similarity index 100% rename from app/models/service/registry_api.rb rename to app/models/foreman_docker/service/registry_api.rb diff --git a/app/models/service/containers.rb b/app/models/service/containers.rb deleted file mode 100644 index 9f8d547..0000000 --- a/app/models/service/containers.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Service - class Containers - def self.start_container!(wizard_state) - ActiveRecord::Base.transaction do - container = Container.new(wizard_state.container_attributes) do |r| - # eagerly load environment variables - state = DockerContainerWizardState.includes(:environment => [:environment_variables]) - .find(wizard_state.id) - state.environment_variables.each do |environment_variable| - r.environment_variables.build :name => environment_variable.name, - :value => environment_variable.value, - :priority => environment_variable.priority - end - end - Taxonomy.enabled_taxonomies.each do |taxonomy| - container.send(:"#{taxonomy}=", wizard_state.preliminary.send(:"#{taxonomy}")) - end - - fail ActiveRecord::Rollback unless pull_image(container) && start_container(container) - - container.save! - destroy_wizard_state(wizard_state) - container - end - end - - def self.pull_image(container) - container.compute_resource.create_image(:fromImage => container.repository_pull_url) - end - - def self.start_container(container) - started = container.compute_resource.create_container(container.parametrize) - container.uuid = started.id if started - started - end - - def self.destroy_wizard_state(wizard_state) - wizard_state.destroy - DockerContainerWizardState.destroy_all(["updated_at < ?", (Time.now - 24.hours)]) - end - end -end diff --git a/app/views/containers/show.html.erb b/app/views/containers/show.html.erb index 2b4daf5..bd15cef 100644 --- a/app/views/containers/show.html.erb +++ b/app/views/containers/show.html.erb @@ -1,4 +1,3 @@ -<% title "#{@container.name.titleize} - #{@container.in_fog.name}" %> <%= stylesheet 'foreman_docker/terminal' %> <%= container_title_actions(@container) %>
@@ -6,10 +5,10 @@
- + - + @@ -21,40 +20,38 @@ - + - + - + - + - + - + <% Taxonomy.enabled_taxonomies.each do |taxonomy| %> @@ -74,49 +71,58 @@
- <% if @container.in_fog.ready? %> - -
-
- <% processes = Docker::Container.get(@container.uuid).top %> -
- <% processes.each do |process| %> - -
-index' class='collapse accordion-body'> -
<%= JSON.pretty_generate(process) %>
+ <% if @container.uuid.present? && @container.in_fog.ready? %> + <% if @container.in_fog.ready? %> + +
+
+ <% processes = Docker::Container.get(@container.uuid).top %> +
+ <% processes.each do |process| %> + +
-index' class='collapse accordion-body'> +
<%= JSON.pretty_generate(process) %>
+
+ <% end %>
- <% end %> +
+
+
<%= Docker::Container.get(@container.uuid).logs(:stdout => true, :tail => 100) %>
-
-
<%= Docker::Container.get(@container.uuid).logs(:stdout => true, :tail => 100) %>
-
-
+ <% else %> +
+

<%= _("Notice") %> +


+ <%= _("Your container is stopped.") %>

+

<%= _("Please turn on your container to see processes running, logs, and more.") %>

+
+ <% end %> <% else %>

<%= _("Notice") %>


- <%= _("Your container is stopped.") %>

-

<%= _("Please turn on your container to see processes running, logs, and more.") %>

+ <%= _("Your container is being provisioned.") %>

+

<%= _("Please wait until your image is pulled to the Docker host. You can track progress at Monitor > Tasks.") %>

<% end %>
diff --git a/foreman_docker.gemspec b/foreman_docker.gemspec index 415f038..d98002f 100644 --- a/foreman_docker.gemspec +++ b/foreman_docker.gemspec @@ -20,5 +20,6 @@ Gem::Specification.new do |s| s.add_dependency 'docker-api', '~> 1.13' s.add_dependency 'wicked', '~> 1.1' + s.add_dependency 'foreman-tasks' s.add_development_dependency 'rubocop', '~> 0.26' end diff --git a/lib/foreman_docker/engine.rb b/lib/foreman_docker/engine.rb index a8380c1..d4bab90 100644 --- a/lib/foreman_docker/engine.rb +++ b/lib/foreman_docker/engine.rb @@ -4,6 +4,7 @@ require 'fog/fogdocker' require 'wicked' require 'docker' +require 'foreman-tasks' module ForemanDocker # Inherit from the Rails module of the parent app (Foreman), not the plugin. @@ -14,6 +15,10 @@ class Engine < ::Rails::Engine config.autoload_paths += Dir["#{config.root}/app/controllers/concerns"] config.autoload_paths += Dir["#{config.root}/app/models/concerns"] + initializer "foreman_docker.require_dynflow", :before => "foreman_tasks.initialize_dynflow" do + ::ForemanTasks.dynflow.require! + end + initializer 'foreman_docker.load_app_instance_data' do |app| app.config.paths['db/migrate'] += ForemanDocker::Engine.paths['db/migrate'].existent end diff --git a/test/functionals/containers_steps_controller_test.rb b/test/functionals/containers_steps_controller_test.rb index a326349..300f233 100644 --- a/test/functionals/containers_steps_controller_test.rb +++ b/test/functionals/containers_steps_controller_test.rb @@ -8,7 +8,8 @@ class StepsControllerTest < ActionController::TestCase test 'wizard finishes with a redirect to the managed container' do state = DockerContainerWizardState.create! - Service::Containers.expects(:start_container!).with(equals(state)).returns(@container) + ForemanDocker::Service::Containers.expects(:start_container!) + .with(equals(state)).returns(@container) put :update, { :wizard_state_id => state.id, :id => :environment, :docker_container_wizard_states_environment => { :tty => false } }, diff --git a/test/units/containers_service_test.rb b/test/units/containers_service_test.rb index e2da74a..57c54c9 100644 --- a/test/units/containers_service_test.rb +++ b/test/units/containers_service_test.rb @@ -19,7 +19,7 @@ class ContainersServiceTest < ActiveSupport::TestCase end ForemanDocker::Docker.any_instance.expects(:create_container) .returns(OpenStruct.new(:uuid => 1)) - Service::Containers.start_container!(@state) + ForemanDocker::Service::Containers.start_container!(@state) assert_equal DockerContainerWizardState.where(:id => @state.id).count, 0 end end
<%= _('Properties') %>
<%= _('Properties') %>
<%= _('Name') %><%= @container.in_fog.name %><%= fog_property(@container) { @container.in_fog.name } %>
<%= _('Image Repository') %>
<%= _('IP Address') %><%= @container.in_fog.ipaddress %> <%= fog_property(@container) { @container.in_fog.ipaddress } %>
<%= _('CPU shares') %><%= @container.in_fog.cores %><%= fog_property(@container) { @container.in_fog.cores } %>
<%= _('UUID') %><%= trunc @container.in_fog.identity %><%= fog_property(@container) { trunc(@container.in_fog.identity) } %>
<%= _('Memory') %><%= number_to_human_size @container.in_fog.memory %> + <%= fog_property(@container) { number_to_human_size(@container.in_fog.memory) } %> +
<%= _('Command') %><%= @container.in_fog.command %> <%= fog_property(@container) { @container.in_fog.command } %>
<%= _('Exposed ports') %><%= @container.in_fog.exposed_ports %> <%= fog_property(@container) { @container.in_fog.exposed_ports } %>
<%= _('Environment Variables') %> - - <% @container.in_fog.attributes['config_env'].each do |environment_variable| %> - <% pair = environment_variable.split("=") %> - - - - - <% end %> -
<%= pair.first %><%= pair.second %>
+ <%= + fog_property(@container) do + pair_attributes_table(@container.in_fog.attributes['config_env']) + end + %>