class Superhosting::Controller::Container

Constants

CONTAINER_NAME_FORMAT

Public Class Methods

new(**kwargs) click to toggle source
Calls superclass method Superhosting::Base::new
# File lib/superhosting/controller/container.rb, line 6
def initialize(**kwargs)
  super
  self.index
end

Public Instance Methods

_collect_docker_options(mapper:, model_or_mux: nil) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 273
def _collect_docker_options(mapper:, model_or_mux: nil)
  model_or_mux ||= mapper.f('model', default: @config.default_model)
  return { error: :input_error, code: :no_docker_image_specified_in_model_or_mux, data: { name: model_or_mux } } if (image = mapper.docker.image).nil?

  all_options = mapper.docker.grep_files.map {|n| [n.name[/(.*(?=\.erb))|(.*)/].to_sym, n.value] }.to_h
  return { error: :logical_error, code: :docker_command_not_found } if (command = all_options[:command]).nil?

  command_options = @docker_api.grab_container_options(all_options)
  volume_opts = []
  mapper.docker.f('volume', overlay: false).each {|v| volume_opts += v.lines unless v.nil? }
  volume_opts.each {|val| command_options << "--volume #{val}" }

  { data: [command_options, image, command] }
end
_config_options(name:, on_reconfig:, on_config:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 212
def _config_options(name:, on_reconfig:, on_config:)
  mapper = self.index[name][:mapper]
  model = mapper.model(default: @config.default_model)
  model_mapper = @config.models.f(:"#{model}")
  registry_mapper = mapper.lib.registry.f('container')
  mux_mapper = self.index[name][:mux_mapper]

  {
      container: mapper,
      mux: mux_mapper,
      model: model_mapper,
      registry_mapper: registry_mapper,
      on_reconfig: on_reconfig,
      on_config: on_config,
      etc: @config,
      lib: @lib,
      docker_api: @docker_api
  }
end
_delete_docker(name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 232
def _delete_docker(name:)
  if @docker_api.container_exists?(name)
    @docker_api.container_unpause!(name) if @docker_api.container_paused?(name)
    @docker_api.container_kill!(name)
    @docker_api.container_rm!(name)
  end
end
_each_site(name:) { |site_controller, site_name, data| ... } click to toggle source
# File lib/superhosting/controller/container/states.rb, line 288
def _each_site(name:)
  site_controller = self.get_controller(Superhosting::Controller::Site)
  site_controller._list(container_name: name).each do |site_name, data|
    yield site_controller, site_name, data[:state]
  end
end
_list() click to toggle source
# File lib/superhosting/controller/container.rb, line 21
def _list
  def data(name)
    mapper = self.index[name][:mapper]
    docker_options = mapper.docker.grep_files.map {|f| [f.name, f.value] }.to_h
    configs = mapper.f('config.rb', overlay: false).reverse.map {|f| f.value }
    { docker: docker_options, configs: configs }
  end

  containers = {}
  @config.containers.grep_dirs.map do |n|
    name = n.name
    user_controller = self.get_controller(User)
    containers[name] = {
        state: self.state(name: name).value,
        users: user_controller._list(container_name: name),
        admins: self.admin(name: name)._list
    }.merge(data(name)) if self.index.key? name and self.index[name][:mapper].lib.state.file?
  end
  containers
end
_reconfigure(name:, **kwargs) click to toggle source
# File lib/superhosting/controller/container.rb, line 139
def _reconfigure(name:, **kwargs)
  lib_mapper = @lib.containers.f(name)

  states = {
      none: { action: :install_data, undo: :uninstall_data, next: :data_installed },
      data_installed: { action: :install_users, undo: :uninstall_users, next: :users_installed },
      users_installed: { action: :run_mux, undo: :stop_mux, next: :mux_runned },
      mux_runned: { action: :configure_with_apply, undo: :unconfigure_with_unapply, next: :configuration_applied },
      configuration_applied: { action: :run, undo: :stop, next: :up }
  }

  self.on_state(state_mapper: lib_mapper, states: states,
                name: name, **kwargs)
end
_recreate_docker(*docker_options, name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 240
def _recreate_docker(*docker_options, name:)
  docker_options ||= self._collect_docker_options(mapper: self.index[name][:mapper]).net_status_ok!
  self._delete_docker(name: name)
  self._run_docker(*docker_options, name: name)
end
_run_docker(*docker_options, name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 246
def _run_docker(*docker_options, name:)
  docker_options = self._collect_docker_options(mapper: self.index[name][:mapper]).net_status_ok![:data] if docker_options.empty?
  @docker_api.container_run(name, *docker_options)
end
_safe_run_docker(*docker_options, name:, restart: false) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 251
def _safe_run_docker(*docker_options, name:, restart: false)
  if restart
    self._recreate_docker(*docker_options, name: name)
  elsif @docker_api.container_exists?(name)
    if @docker_api.container_dead?(name)
      self._recreate_docker(*docker_options, name: name)
    elsif @docker_api.container_exited?(name)
      @docker_api.container_start!(name)
    elsif @docker_api.container_paused?(name)
      @docker_api.container_unpause!(name)
    elsif @docker_api.container_restarting?(name)
      Polling.start 10 do
         break unless @docker_api.container_restarting?(name)
         sleep 2
      end
    end
  else
    self._run_docker(*docker_options, name: name)
  end
  self.running_validation(name: name)
end
add(name:, mail: 'model', admin_mail: nil, model: nil) click to toggle source
# File lib/superhosting/controller/container.rb, line 50
def add(name:, mail: 'model', admin_mail: nil, model: nil)
  resp = self._reconfigure(name: name, mail: mail, admin_mail: admin_mail, model: model) if (resp = self.not_existing_validation(name: name)).net_status_ok?
  resp
end
adding_validation(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 171
def adding_validation(name:)
  if (resp = self.base_validation(name: name)).net_status_ok?
    resp = self.not_running_validation(name: name)
  end
  resp
end
admin(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 162
def admin(name:)
  self.get_controller(Admin, name: name)
end
apply(name:) click to toggle source
Calls superclass method
# File lib/superhosting/controller/container/states.rb, line 147
def apply(name:)
  self._each_site(name: name) do |controller, sname, state|
    controller.apply(name: sname).net_status_ok!
  end
  super
end
available_validation(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 194
def available_validation(name:)
  if (resp = self.existing_validation(name: name)).net_status_ok?
    resp = (self.index[name][:mapper].lib.state.value == 'up') ? {} : { error: :logical_error, code: :container_is_not_available, data: { name: name }  }
  end
  resp
end
base_validation(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 166
def base_validation(name:)
  @docker_api.container_rm_inactive!(name)
  (name !~ CONTAINER_NAME_FORMAT) ? { error: :input_error, code: :invalid_container_name, data: { name: name, regex: CONTAINER_NAME_FORMAT } } : {}
end
change(name:, mail: 'model', admin_mail: nil, model: nil) click to toggle source
# File lib/superhosting/controller/container.rb, line 73
def change(name:, mail: 'model', admin_mail: nil, model: nil)

end
configure(name:) click to toggle source
Calls superclass method
# File lib/superhosting/controller/container/states.rb, line 133
def configure(name:)
  self._each_site(name: name) do |controller, sname, state|
    controller.configure(name: sname).net_status_ok!
  end
  super
end
configure_with_apply(name:) click to toggle source
Calls superclass method
# File lib/superhosting/controller/container/states.rb, line 154
def configure_with_apply(name:)
  self._each_site(name: name) do |controller, sname, state|
    controller.reconfigure(name: sname).net_status_ok!
  end
  super
end
data(name) click to toggle source
# File lib/superhosting/controller/container.rb, line 22
def data(name)
  mapper = self.index[name][:mapper]
  docker_options = mapper.docker.grep_files.map {|f| [f.name, f.value] }.to_h
  configs = mapper.f('config.rb', overlay: false).reverse.map {|f| f.value }
  { docker: docker_options, configs: configs }
end
delete(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 55
def delete(name:)
  if (resp = self.existing_validation(name: name)).net_status_ok?
    lib_mapper = @lib.containers.f(name)

    states = {
        up: { action: :stop, undo: :run, next: :configuration_applied },
        configuration_applied: { action: :unconfigure_with_unapply, undo: :configure_with_apply, next: :mux_runned },
        mux_runned: { action: :stop_mux, undo: :run_mux, next: :users_installed },
        users_installed: { action: :uninstall_users, next: :data_installed },
        data_installed: { action: :uninstall_data }
    }

    self.on_state(state_mapper: lib_mapper, states: states,
                  name: name)
  end
  resp
end
existing_validation(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 186
def existing_validation(name:)
  self.index.include?(name) ? {} : { error: :logical_error, code: :container_does_not_exists, data: { name: name }  }
end
index() click to toggle source
# File lib/superhosting/controller/container.rb, line 201
def index
  @@index ||= self.reindex
end
inspect(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 42
def inspect(name:)
  if (resp = self.existing_validation(name: name)).net_status_ok?
    { data: self._list[name] }
  else
    resp
  end
end
install_data(name:, mail: 'model', admin_mail: nil, model: nil) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 6
def install_data(name:, mail: 'model', admin_mail: nil, model: nil)
  if !(resp = self.adding_validation(name: name)).net_status_ok?
    return resp
  elsif (model_ = model || @config.containers.f(name).f('model', default: @config.default_model)).nil?
    return { error: :input_error, code: :no_model_given }
  end

  # model
  model_mapper = @config.models.f(model_)
  return { error: :input_error, code: :model_does_not_exists, data: { name: model_ } } unless @config.models.f(model_).dir?
  etc_mapper = @config.containers.f(name).create!
  etc_mapper.model.put!(model) unless model.nil?

  # config
  self.reindex_container(name: name)
  mapper = self.index[name][:mapper]

  # mail
  unless mail != 'no'
    if mail == 'yes'
      mapper.mail.put!(mail)
      unless (admin_mail_ = admin_mail).nil?
        mapper.admin_mail.put!(admin_mail_)
      end
    elsif mail == 'model'
      if model_mapper.default_mail == 'yes'
        admin_mail_ = admin_mail || model_mapper.default_admin_mail
      end
    end
    return { error: :input_error, code: :option_admin_mail_required } if defined? admin_mail_ and admin_mail_.nil?
  end

  # lib
  mapper.lib.config.delete!
  mapper.lib.config.create!
  mapper.lib.web.create!

  # web
  PathMapper.new('/web').create!
  safe_link!(mapper.lib.web.path, mapper.web.path)
  {}
end
install_users(name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 69
def install_users(name:)
  mapper = self.index[name][:mapper]

  # user / group
  user_controller = self.get_controller(User)
  user_controller._group_pretty_add(name: name)
  unless (resp = user_controller._pretty_add_custom(name: name, group: name)).net_status_ok?
    return resp
  end
  user = user_controller._get(name: name)

  self.with_dry_run do |dry_run|
    user_gid = if dry_run
      'XXXX' if user.nil?
    else
      user.gid
    end

    mapper.lib.config.f('etc-group').append_line!("#{name}:x:#{user_gid}:") unless user_gid.nil?
  end

  # system users
  current_system_users = user_controller._group_get_system_users(name: name)
  add_users = mapper.system_users.lines - current_system_users
  del_users = current_system_users - mapper.system_users.lines
  add_users.each do |u|
    unless (resp = user_controller._add_system_user(name: u.strip, container_name: name)).net_status_ok?
      return resp
    end
  end
  del_users.each do |u|
    user_name = "#{name}_#{u.strip}"
    user = user_controller._get(name: user_name)
    unless (resp = user_controller._del(name: user_name)).net_status_ok?
      return resp
    end
    mapper.lib.config.f('etc-passwd').remove_line!(/^#{user_name}:.*/)
  end

  # docker
  PathMapper.new('/etc/security/docker.conf').append_line!("@#{name} #{name}")

  # chown
  chown_r!(name, name, mapper.lib.web.path)
  {}
end
list() click to toggle source
# File lib/superhosting/controller/container.rb, line 11
def list
  # TODO
  # docker = @docker_api.container_list.map {|c| c['Names'].first.slice(1..-1) }.to_set
  # sx = @config.containers.grep_dirs.map {|n| n.name }.compact.to_set
  # containers = (docker & sx)

  containers = self._list
  { data: containers }
end
not_existing_validation(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 190
def not_existing_validation(name:)
  self.existing_validation(name: name).net_status_ok? ? { error: :logical_error, code: :container_exists, data: { name: name }  } : {}
end
not_running_validation(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 182
def not_running_validation(name:)
  @docker_api.container_not_running?(name) ? {} : { error: :logical_error, code: :container_is_running, data: { name: name } }
end
reconfigure(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 131
def reconfigure(name:)
  if (resp = self.existing_validation(name: name)).net_status_ok?
    self.set_state(name: name, state: :data_installed)
    resp = self._reconfigure(name: name)
  end
  resp
end
reindex() click to toggle source
# File lib/superhosting/controller/container.rb, line 205
def reindex
  @config.containers.grep_dirs.each {|mapper| self.reindex_container(name: mapper.name) }
  @@index ||= {}
end
reindex_container(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 210
def reindex_container(name:)
  @@index ||= {}
  etc_mapper = @config.containers.f(name)
  web_mapper = PathMapper.new('/web').f(name)
  lib_mapper = @lib.containers.f(name)
  state_mapper = lib_mapper.state

  if etc_mapper.nil?
    @@index.delete(name)
    return
  end

  model_name = etc_mapper.f('model', default: @config.default_model)
  model_mapper = @config.models.f(model_name)
  etc_mapper = MapperInheritance::Model.new(model_mapper).set_inheritors(etc_mapper)

  mapper = CompositeMapper.new(etc_mapper: etc_mapper, lib_mapper: lib_mapper, web_mapper: web_mapper)

  etc_mapper.erb_options = { container: mapper }
  mux_mapper = if (mux_file_mapper = etc_mapper.mux).file?
    MapperInheritance::Mux.new(@config.muxs.f(mux_file_mapper)).set_inheritors
  end

  @@index[name] = { mapper: mapper, mux_mapper: mux_mapper, state_mapper: state_mapper }
end
rename(name:, new_name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 86
def rename(name:, new_name:)
  if (resp = self.available_validation(name: name)).net_status_ok? and
      (resp = self.adding_validation(name: new_name)).net_status_ok?

    mapper = self.index[name][:mapper]
    new_etc_mapper = mapper.etc.parent.f(new_name)
    model = nil if (model = mapper.f('model').value).nil? # TODO: mail:, admin_mail:

    mapper.rename!(new_etc_mapper.path)
    mapper.create!

    begin
      self.stop(name: name).net_status_ok!
      self.unconfigure_with_unapply(name: name).net_status_ok!
      if (resp = self._reconfigure(name: new_name, model: model)).net_status_ok?
        new_mapper = self.index[new_name][:mapper]
        mapper.lib.web.rename!(new_mapper.lib.web.path)
        mapper.lib.sites.rename!(new_mapper.lib.sites.path)
        mapper.lib.registry.sites.rename!(new_mapper.lib.registry.sites.path)

        site_controller = self.get_controller(Site)
        site_controller.reindex_container_sites(container_name: new_name)
        site_controller.reindex_container_sites(container_name: name)

        self.reconfigure(name: new_name).net_status_ok!
        self.delete(name: name).net_status_ok!
      end
    rescue Exception => e
      resp = e.net_status
      raise
    ensure
      unless resp.net_status_ok?
        unless new_mapper.nil?
          new_mapper.lib.web.rename!(mapper.lib.web.path)
          new_mapper.lib.sites.rename!(mapper.lib.sites.path)
          new_etc_mapper.rename!(mapper.path)
          self.reconfigure(name: name)
        end
        self.delete(name: new_name)
      end
    end
  end
  resp
end
restore(name:, from:, mail: 'model', admin_mail: nil, model: nil) click to toggle source
# File lib/superhosting/controller/container.rb, line 158
def restore(name:, from:, mail: 'model', admin_mail: nil, model: nil)

end
run(name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 161
def run(name:)
  mapper = self.index[name][:mapper]

  if (resp = self._collect_docker_options(mapper: mapper)).net_status_ok?
    command_options, image, command = resp[:data]
    dump_command_option = (command_options + [command]).join("\n")
    dummy_signature_md5 = Digest::MD5.new.digest(dump_command_option)

    restart = (!image.compare_with(mapper.lib.image) or (dummy_signature_md5 != mapper.lib.signature.md5))

    if (resp = self._safe_run_docker(command_options, image, command, name: name, restart: restart)).net_status_ok?
      mapper.lib.image.put!(image, logger: false)
      mapper.lib.signature.put!(dump_command_option, logger: false)
    end
  end
  resp
end
run_mux(name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 179
def run_mux(name:)
  resp = {}
  mapper = self.index[name][:mapper]

  if (mux_mapper = mapper.mux).file?
    mux_name = "mux-#{mux_mapper.value}"
    mux_controller = self.get_controller(Mux)
    resp = mux_controller.add(name: mux_name) if mux_controller.not_running_validation(name: mux_name).net_status_ok?
    mux_controller.index_push(mux_name, name)
  end

  resp
end
running_validation(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 178
def running_validation(name:)
  @docker_api.container_running?(name) ? {}: { error: :logical_error, code: :container_is_not_running, data: { name: name } }
end
save(name:, to:) click to toggle source
# File lib/superhosting/controller/container.rb, line 154
def save(name:, to:)

end
stop(name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 206
def stop(name:)
  self._delete_docker(name: name)
  self.get_controller(Mux).reindex
  {}
end
stop_mux(name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 193
def stop_mux(name:)
  mapper = self.index[name][:mapper]

  if (mux_mapper = mapper.mux).file?
    mux_name = "mux-#{mux_mapper.value}"
    mux_controller = self.get_controller(Mux)
    mux_controller.index_pop(mux_name, name)
    self._delete_docker(name: mux_name) unless mux_controller.index.include?(mux_name)
  end

  {}
end
unconfigure(name:) click to toggle source
Calls superclass method
# File lib/superhosting/controller/container/states.rb, line 140
def unconfigure(name:)
  self._each_site(name: name) do |controller, sname, state|
    controller.unconfigure(name: sname).net_status_ok! # TODO: unchanged site status
  end
  super
end
uninstall_data(name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 49
def uninstall_data(name:)
  if self.index.include? name
    mapper = self.index[name][:mapper]

    # lib
    safe_unlink!(mapper.web.path)
    mapper.lib = mapper.lib
    mapper.lib.web.delete!
    mapper.lib.config.delete!
    mapper.lib.delete!

    # config
    mapper.delete!

    self.reindex_container(name: name)
  end

  {}
end
uninstall_users(name:) click to toggle source
# File lib/superhosting/controller/container/states.rb, line 116
def uninstall_users(name:)
  mapper = self.index[name][:mapper]

  user_controller = self.get_controller(User)
  if (user = user_controller._get(name: name))
    mapper.lib.config.f('etc-group').remove_line!("#{name}:x:#{user.gid}:")
  end

  user_controller._group_del_users(name: name)
  user_controller._group_pretty_del(name: name)

  # docker
  PathMapper.new('/etc/security/docker.conf').remove_line!("@#{name} #{name}")

  {}
end
update(name:) click to toggle source
# File lib/superhosting/controller/container.rb, line 77
def update(name:)
  if (resp = self.existing_validation(name: name)).net_status_ok? and @docker_api.container_exists?(name)
    mapper = self.index[name][:mapper]
    image = mapper.lib.image.value
    self._recreate_docker(name: name) unless @docker_api.container_image?(name, image)
  end
  resp
end