lib/superhosting/controller/container.rb in superhosting-0.0.1 vs lib/superhosting/controller/container.rb in superhosting-0.0.2

- old
+ new

@@ -1,174 +1,237 @@ module Superhosting module Controller class Container < Base - CONTAINER_NAME_FORMAT = /[a-zA-Z0-9][a-zA-Z0-9_.-]+/ + CONTAINER_NAME_FORMAT = /^[a-zA-Z0-9][a-zA-Z0-9_.-]+$/ + def initialize(**kwargs) + super + self.index + end + def list - docker = @docker_api.container_list.map {|c| c['Names'].first.slice(1..-1) }.to_set - sx = @config.containers._grep_dirs.map {|n| n._name }.to_set - containers = (docker & sx) + # 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) - { data: containers.to_a } + containers = self._list + { data: containers } end - def add(name:, mail: 'model', admin_mail: nil, model: nil) - return { error: :input_error, message: "Invalid container name '#{name}' - only '#{CONTAINER_NAME_FORMAT}' are allowed" } if name !~ CONTAINER_NAME_FORMAT - return { error: :logical_error, message: 'Container already running.' } if @docker_api.container_info(name) - return { error: :input_error, message: 'No model given.' } unless (model_ = model || @config.default_model(default: nil)) + 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 - # config - config_path_ = "#{@config_path}/containers/#{name}" - config_path_mapper = PathMapper.new(config_path_) - FileUtils.mkdir_p config_path_ + 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 - # model - create_conf("#{config_path_}/model", model) unless model.nil? - model_mapper = @config.models.f(:"#{model_}") - - # image - return { error: :input_error, message: "No docker_image specified in model '#{model_}.'" } unless (image = model_mapper.docker_image(default: nil)) - - # mail - unless mail != 'no' - if mail == 'yes' - create_conf("#{config_path_}/mail", mail) - admin_mail_ = admin_mail - create_conf("#{config_path_}/admin_mail", admin_mail_) unless admin_mail_.nil? - return { error: :input_error, message: 'Admin mail required.' } if admin_mail_.nil? - elsif mail == 'model' - if model_mapper.default_mail == 'yes' - admin_mail_ = admin_mail || model_mapper.default_admin_mail(default: nil) - return { error: :input_error, message: 'Admin mail required.' } if admin_mail_.nil? - end - end + def inspect(name:) + if (resp = self.existing_validation(name: name)).net_status_ok? + { data: self._list[name] } + else + resp end + end - # lib - lib_path_ = "#{@lib_path}/containers/#{name}" - lib_path_mapper = PathMapper.new(lib_path_) - FileUtils.rm_rf "#{lib_path_}/configs" - FileUtils.mkdir_p "#{lib_path_}/configs" - FileUtils.mkdir_p "#{lib_path_}/web" + 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 - # user/group - self.command("groupadd #{name}") - self.command("useradd #{name} -g #{name} -d #{lib_path_}/web/") + def delete(name:) + if (resp = self.existing_validation(name: name)).net_status_ok? + lib_mapper = @lib.containers.f(name) - user = Etc.getpwnam(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 } + } - write_if_not_exist("#{lib_path_}/configs/etc-group", "#{name}:x:#{user.gid}:") - write_if_not_exist("#{lib_path_}/configs/etc-passwd", "#{name}:x:#{user.uid}:#{user.gid}::/web/:/usr/sbin/nologin") + self.on_state(state_mapper: lib_mapper, states: states, + name: name) + end + resp + end - # system users - users = [config_path_mapper.system_users, model_mapper.system_users].find {|f| f.is_a? PathMapper::FileNode } - users.lines.each {|u| self.command("useradd #{name}_#{u.strip} -g #{name} -d #{lib_path_}/web/") } unless users.nil? + def change(name:, mail: 'model', admin_mail: nil, model: nil) - # services - cserv = @config.containers.f(name).services._grep(/.*\.erb/).map {|n| [n._name, n]}.to_h - mserv = model_mapper.services._grep(/.*\.erb/).map {|n| [n._name, n]}.to_h - services = mserv.merge!(cserv) + end - supervisor_path = "#{lib_path_}/supervisor" - FileUtils.mkdir_p supervisor_path - services.each do |_name, node| - text = erb(node, model: model_, container: config_path_mapper) - create_conf("#{supervisor_path}/#{_name[/.*[^\.erb]/]}", text) + 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 - # container - unless model_mapper.f('container.rb').nil? - registry_path = lib_path_mapper.registry.f('container')._path - ex = ScriptExecutor::Container.new( - container: config_path_mapper, container_name: name, container_lib: lib_path_mapper, registry_path: registry_path, - model: model_mapper, config: @config, lib: @lib - ) - ex.execute(model_mapper.f('container.rb')) - ex.commands.each {|c| self.command c } - end + 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? - # docker - write_if_not_exist('/etc/security/docker.conf', "@#{name} #{name}") + 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: - # run container - self.command "docker run --detach --name #{name} -v #{lib_path_}/configs/:/.configs:ro - -v #{lib_path_}/web:/web #{image} /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf".split + mapper.rename!(new_etc_mapper.path) + mapper.create! - { error: :error, message: 'Unable to run docker container.' } unless @docker_api.container_info(name) - end + 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) - def delete(name:) - config_path_mapper = @config.containers.f(name) - lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{name}") - model = config_path_mapper.model(default: @config.default_model) - model_mapper = @config.models.f(:"#{model}") + site_controller = self.get_controller(Site) + site_controller.reindex_container_sites(container_name: new_name) + site_controller.reindex_container_sites(container_name: name) - sites = config_path_mapper.sites._grep_dirs.map { |n| n._name } - sites.each do |s| - begin - Site.new(instance_variables_to_hash(self)).delete(s) - rescue NetStatus::Exception => e - raise Error::Controller, e.net_status + 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 - unless (container = lib_path_mapper.registry.f('container')).nil? - FileUtils.rm container.lines - FileUtils.rm container._path + 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 - unless model_mapper.f('container.rb').nil? - registry_path = lib_path_mapper.registry.f('container')._path - ex = ScriptExecutor::Container.new( - container: config_path_mapper, container_name: name, container_lib: lib_path_mapper, - registry_path: registry_path, on_reconfig_only: true, - model: model_mapper, config: @config, lib: @lib - ) - ex.execute(model_mapper.f('container.rb')) - ex.commands.each {|c| self.command c } - end + def _reconfigure(name:, **kwargs) + lib_mapper = @lib.containers.f(name) - @docker_api.container_kill(name) - @docker_api.container_rm(name) - remove_line_from_file('/etc/security/docker.conf', "@#{name} #{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 } + } - begin - gid = Etc.getgrnam(name).gid - Etc.passwd do |user| - self.command("userdel #{user.name}") if user.gid == gid - end - self.command("groupdel #{name}") - rescue ArgumentError => e - # repeated command execution: group name already does not exist - end + self.on_state(state_mapper: lib_mapper, states: states, + name: name, **kwargs) + end - FileUtils.rm_rf "#{@lib_path}/containers/#{name}/configs" + def save(name:, to:) - {} end - def change(name:, mail: 'model', admin_mail: nil, model: nil) + def restore(name:, from:, mail: 'model', admin_mail: nil, model: nil) end - def update(name:) + def admin(name:) + self.get_controller(Admin, name: name) + end + 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 - def reconfig(name:) + def adding_validation(name:) + if (resp = self.base_validation(name: name)).net_status_ok? + resp = self.not_running_validation(name: name) + end + resp + end + def running_validation(name:) + @docker_api.container_running?(name) ? {}: { error: :logical_error, code: :container_is_not_running, data: { name: name } } end - def save(name:, to:) + def not_running_validation(name:) + @docker_api.container_not_running?(name) ? {} : { error: :logical_error, code: :container_is_running, data: { name: name } } + end + def existing_validation(name:) + self.index.include?(name) ? {} : { error: :logical_error, code: :container_does_not_exists, data: { name: name } } end - def restore(name:, from:, mail: 'model', admin_mail: nil, model: nil) + def not_existing_validation(name:) + self.existing_validation(name: name).net_status_ok? ? { error: :logical_error, code: :container_exists, data: { name: name } } : {} + end + 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 - def admin(name:, logger: @logger) - Admin.new(name: name, logger: logger) + def index + @@index ||= self.reindex + end + + def reindex + @config.containers.grep_dirs.each {|mapper| self.reindex_container(name: mapper.name) } + @@index ||= {} + end + + 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 end end end