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