superhosting 0.0.1

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 (70) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +117 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +23 -0
  9. data/Rakefile +6 -0
  10. data/Vagrantfile +10 -0
  11. data/bin/sx +10 -0
  12. data/bootstrap.sh +31 -0
  13. data/lib/superhosting.rb +40 -0
  14. data/lib/superhosting/base.rb +37 -0
  15. data/lib/superhosting/cli/base.rb +227 -0
  16. data/lib/superhosting/cli/cmd/admin_add.rb +11 -0
  17. data/lib/superhosting/cli/cmd/admin_container_add.rb +16 -0
  18. data/lib/superhosting/cli/cmd/admin_container_delete.rb +16 -0
  19. data/lib/superhosting/cli/cmd/admin_container_list.rb +12 -0
  20. data/lib/superhosting/cli/cmd/admin_delete.rb +11 -0
  21. data/lib/superhosting/cli/cmd/admin_passwd.rb +16 -0
  22. data/lib/superhosting/cli/cmd/container_add.rb +21 -0
  23. data/lib/superhosting/cli/cmd/container_admin_add.rb +16 -0
  24. data/lib/superhosting/cli/cmd/container_admin_delete.rb +16 -0
  25. data/lib/superhosting/cli/cmd/container_admin_list.rb +12 -0
  26. data/lib/superhosting/cli/cmd/container_change.rb +21 -0
  27. data/lib/superhosting/cli/cmd/container_delete.rb +11 -0
  28. data/lib/superhosting/cli/cmd/container_list.rb +9 -0
  29. data/lib/superhosting/cli/cmd/container_reconfig.rb +11 -0
  30. data/lib/superhosting/cli/cmd/container_restore.rb +24 -0
  31. data/lib/superhosting/cli/cmd/container_save.rb +14 -0
  32. data/lib/superhosting/cli/cmd/container_update.rb +11 -0
  33. data/lib/superhosting/cli/cmd/mysql_db_add.rb +9 -0
  34. data/lib/superhosting/cli/cmd/mysql_db_delete.rb +9 -0
  35. data/lib/superhosting/cli/cmd/mysql_db_dump.rb +9 -0
  36. data/lib/superhosting/cli/cmd/mysql_db_sql.rb +9 -0
  37. data/lib/superhosting/cli/cmd/mysql_grant.rb +9 -0
  38. data/lib/superhosting/cli/cmd/mysql_user_add.rb +12 -0
  39. data/lib/superhosting/cli/cmd/mysql_user_delete.rb +9 -0
  40. data/lib/superhosting/cli/cmd/site_add.rb +16 -0
  41. data/lib/superhosting/cli/cmd/site_alias_add.rb +16 -0
  42. data/lib/superhosting/cli/cmd/site_alias_delete.rb +16 -0
  43. data/lib/superhosting/cli/cmd/site_delete.rb +11 -0
  44. data/lib/superhosting/cli/cmd/site_rename.rb +16 -0
  45. data/lib/superhosting/cli/cmd/user_add.rb +24 -0
  46. data/lib/superhosting/cli/cmd/user_change.rb +24 -0
  47. data/lib/superhosting/cli/cmd/user_delete.rb +16 -0
  48. data/lib/superhosting/cli/cmd/user_list.rb +9 -0
  49. data/lib/superhosting/cli/cmd/user_passwd.rb +16 -0
  50. data/lib/superhosting/cli/error/ambiguous_command.rb +11 -0
  51. data/lib/superhosting/cli/error/base.rb +8 -0
  52. data/lib/superhosting/cli/error/controller.rb +8 -0
  53. data/lib/superhosting/controller/admin.rb +21 -0
  54. data/lib/superhosting/controller/admin/container.rb +24 -0
  55. data/lib/superhosting/controller/container.rb +174 -0
  56. data/lib/superhosting/controller/container/admin.rb +24 -0
  57. data/lib/superhosting/controller/mysql.rb +17 -0
  58. data/lib/superhosting/controller/mysql/db.rb +23 -0
  59. data/lib/superhosting/controller/mysql/user.rb +15 -0
  60. data/lib/superhosting/controller/site.rb +106 -0
  61. data/lib/superhosting/controller/site/alias.rb +21 -0
  62. data/lib/superhosting/controller/user.rb +25 -0
  63. data/lib/superhosting/docker_api.rb +31 -0
  64. data/lib/superhosting/helpers.rb +30 -0
  65. data/lib/superhosting/script_executor/base.rb +26 -0
  66. data/lib/superhosting/script_executor/container.rb +36 -0
  67. data/lib/superhosting/script_executor/site.rb +13 -0
  68. data/lib/superhosting/version.rb +3 -0
  69. data/superhosting.gemspec +32 -0
  70. metadata +341 -0
@@ -0,0 +1,24 @@
1
+ module Superhosting
2
+ module Cli
3
+ module Cmd
4
+ class UserAdd < Base
5
+ option :no_ssh,
6
+ :long => '--no-ssh',
7
+ :boolean => true
8
+
9
+ option :no_ftp,
10
+ :long => '--no-ftp',
11
+ :boolean => true
12
+
13
+ option :container_name,
14
+ :short => '-c NAME',
15
+ :long => '--container NAME',
16
+ :required => true
17
+
18
+ def self.has_required_param?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module Superhosting
2
+ module Cli
3
+ module Cmd
4
+ class UserChange < Base
5
+ option :no_ssh,
6
+ :long => '--no-ssh',
7
+ :boolean => true
8
+
9
+ option :no_ftp,
10
+ :long => '--no-ftp',
11
+ :boolean => true
12
+
13
+ option :container_name,
14
+ :short => '-c NAME',
15
+ :long => '--container NAME',
16
+ :required => true
17
+
18
+ def self.has_required_param?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module Superhosting
2
+ module Cli
3
+ module Cmd
4
+ class UserDelete < Base
5
+ option :container_name,
6
+ :short => '-c NAME',
7
+ :long => '--container NAME',
8
+ :required => true
9
+
10
+ def self.has_required_param?
11
+ true
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Superhosting
2
+ module Cli
3
+ module Cmd
4
+ class UserList < Base
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Superhosting
2
+ module Cli
3
+ module Cmd
4
+ class UserPasswd < Base
5
+ option :generate,
6
+ :short => '-g',
7
+ :long => '--generate',
8
+ :boolean => true
9
+
10
+ def self.has_required_param?
11
+ true
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Superhosting
2
+ module Cli
3
+ module Error
4
+ class AmbiguousCommand < Base
5
+ def initialize(msg: 'Ambiguous command', commands:, path: '')
6
+ super(error: "#{msg}: #{path.join(' ')} (#{commands.join('|')})")
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module Superhosting
2
+ module Cli
3
+ module Error
4
+ class Base < NetStatus::Exception
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Superhosting
2
+ module Cli
3
+ module Error
4
+ class Controller < Base
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ module Superhosting
2
+ module Controller
3
+ class Admin < Base
4
+ def add(name:)
5
+
6
+ end
7
+
8
+ def delete(name:)
9
+
10
+ end
11
+
12
+ def passwd(name:, generate: nil)
13
+
14
+ end
15
+
16
+ def container(name:, logger: @logger)
17
+ Container.new(name: name, logger: logger)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ module Superhosting
2
+ module Controller
3
+ class Admin
4
+ class Container < Base
5
+ def initialize(name:, **kvargs)
6
+ @admin_name = name
7
+ super(kvargs)
8
+ end
9
+
10
+ def list
11
+
12
+ end
13
+
14
+ def add(name:)
15
+
16
+ end
17
+
18
+ def delete(name:)
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,174 @@
1
+ module Superhosting
2
+ module Controller
3
+ class Container < Base
4
+ CONTAINER_NAME_FORMAT = /[a-zA-Z0-9][a-zA-Z0-9_.-]+/
5
+
6
+ def list
7
+ docker = @docker_api.container_list.map {|c| c['Names'].first.slice(1..-1) }.to_set
8
+ sx = @config.containers._grep_dirs.map {|n| n._name }.to_set
9
+ containers = (docker & sx)
10
+
11
+ { data: containers.to_a }
12
+ end
13
+
14
+ def add(name:, mail: 'model', admin_mail: nil, model: nil)
15
+ return { error: :input_error, message: "Invalid container name '#{name}' - only '#{CONTAINER_NAME_FORMAT}' are allowed" } if name !~ CONTAINER_NAME_FORMAT
16
+ return { error: :logical_error, message: 'Container already running.' } if @docker_api.container_info(name)
17
+ return { error: :input_error, message: 'No model given.' } unless (model_ = model || @config.default_model(default: nil))
18
+
19
+ # config
20
+ config_path_ = "#{@config_path}/containers/#{name}"
21
+ config_path_mapper = PathMapper.new(config_path_)
22
+ FileUtils.mkdir_p config_path_
23
+
24
+ # model
25
+ create_conf("#{config_path_}/model", model) unless model.nil?
26
+ model_mapper = @config.models.f(:"#{model_}")
27
+
28
+ # image
29
+ return { error: :input_error, message: "No docker_image specified in model '#{model_}.'" } unless (image = model_mapper.docker_image(default: nil))
30
+
31
+ # mail
32
+ unless mail != 'no'
33
+ if mail == 'yes'
34
+ create_conf("#{config_path_}/mail", mail)
35
+ admin_mail_ = admin_mail
36
+ create_conf("#{config_path_}/admin_mail", admin_mail_) unless admin_mail_.nil?
37
+ return { error: :input_error, message: 'Admin mail required.' } if admin_mail_.nil?
38
+ elsif mail == 'model'
39
+ if model_mapper.default_mail == 'yes'
40
+ admin_mail_ = admin_mail || model_mapper.default_admin_mail(default: nil)
41
+ return { error: :input_error, message: 'Admin mail required.' } if admin_mail_.nil?
42
+ end
43
+ end
44
+ end
45
+
46
+ # lib
47
+ lib_path_ = "#{@lib_path}/containers/#{name}"
48
+ lib_path_mapper = PathMapper.new(lib_path_)
49
+ FileUtils.rm_rf "#{lib_path_}/configs"
50
+ FileUtils.mkdir_p "#{lib_path_}/configs"
51
+ FileUtils.mkdir_p "#{lib_path_}/web"
52
+
53
+ # user/group
54
+ self.command("groupadd #{name}")
55
+ self.command("useradd #{name} -g #{name} -d #{lib_path_}/web/")
56
+
57
+ user = Etc.getpwnam(name)
58
+
59
+ write_if_not_exist("#{lib_path_}/configs/etc-group", "#{name}:x:#{user.gid}:")
60
+ write_if_not_exist("#{lib_path_}/configs/etc-passwd", "#{name}:x:#{user.uid}:#{user.gid}::/web/:/usr/sbin/nologin")
61
+
62
+ # system users
63
+ users = [config_path_mapper.system_users, model_mapper.system_users].find {|f| f.is_a? PathMapper::FileNode }
64
+ users.lines.each {|u| self.command("useradd #{name}_#{u.strip} -g #{name} -d #{lib_path_}/web/") } unless users.nil?
65
+
66
+ # services
67
+ cserv = @config.containers.f(name).services._grep(/.*\.erb/).map {|n| [n._name, n]}.to_h
68
+ mserv = model_mapper.services._grep(/.*\.erb/).map {|n| [n._name, n]}.to_h
69
+ services = mserv.merge!(cserv)
70
+
71
+ supervisor_path = "#{lib_path_}/supervisor"
72
+ FileUtils.mkdir_p supervisor_path
73
+ services.each do |_name, node|
74
+ text = erb(node, model: model_, container: config_path_mapper)
75
+ create_conf("#{supervisor_path}/#{_name[/.*[^\.erb]/]}", text)
76
+ end
77
+
78
+ # container
79
+ unless model_mapper.f('container.rb').nil?
80
+ registry_path = lib_path_mapper.registry.f('container')._path
81
+ ex = ScriptExecutor::Container.new(
82
+ container: config_path_mapper, container_name: name, container_lib: lib_path_mapper, registry_path: registry_path,
83
+ model: model_mapper, config: @config, lib: @lib
84
+ )
85
+ ex.execute(model_mapper.f('container.rb'))
86
+ ex.commands.each {|c| self.command c }
87
+ end
88
+
89
+ # docker
90
+ write_if_not_exist('/etc/security/docker.conf', "@#{name} #{name}")
91
+
92
+ # run container
93
+ self.command "docker run --detach --name #{name} -v #{lib_path_}/configs/:/.configs:ro
94
+ -v #{lib_path_}/web:/web #{image} /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf".split
95
+
96
+ { error: :error, message: 'Unable to run docker container.' } unless @docker_api.container_info(name)
97
+ end
98
+
99
+ def delete(name:)
100
+ config_path_mapper = @config.containers.f(name)
101
+ lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{name}")
102
+ model = config_path_mapper.model(default: @config.default_model)
103
+ model_mapper = @config.models.f(:"#{model}")
104
+
105
+ sites = config_path_mapper.sites._grep_dirs.map { |n| n._name }
106
+ sites.each do |s|
107
+ begin
108
+ Site.new(instance_variables_to_hash(self)).delete(s)
109
+ rescue NetStatus::Exception => e
110
+ raise Error::Controller, e.net_status
111
+ end
112
+ end
113
+
114
+ unless (container = lib_path_mapper.registry.f('container')).nil?
115
+ FileUtils.rm container.lines
116
+ FileUtils.rm container._path
117
+ end
118
+
119
+ unless model_mapper.f('container.rb').nil?
120
+ registry_path = lib_path_mapper.registry.f('container')._path
121
+ ex = ScriptExecutor::Container.new(
122
+ container: config_path_mapper, container_name: name, container_lib: lib_path_mapper,
123
+ registry_path: registry_path, on_reconfig_only: true,
124
+ model: model_mapper, config: @config, lib: @lib
125
+ )
126
+ ex.execute(model_mapper.f('container.rb'))
127
+ ex.commands.each {|c| self.command c }
128
+ end
129
+
130
+ @docker_api.container_kill(name)
131
+ @docker_api.container_rm(name)
132
+ remove_line_from_file('/etc/security/docker.conf', "@#{name} #{name}")
133
+
134
+ begin
135
+ gid = Etc.getgrnam(name).gid
136
+ Etc.passwd do |user|
137
+ self.command("userdel #{user.name}") if user.gid == gid
138
+ end
139
+ self.command("groupdel #{name}")
140
+ rescue ArgumentError => e
141
+ # repeated command execution: group name already does not exist
142
+ end
143
+
144
+ FileUtils.rm_rf "#{@lib_path}/containers/#{name}/configs"
145
+
146
+ {}
147
+ end
148
+
149
+ def change(name:, mail: 'model', admin_mail: nil, model: nil)
150
+
151
+ end
152
+
153
+ def update(name:)
154
+
155
+ end
156
+
157
+ def reconfig(name:)
158
+
159
+ end
160
+
161
+ def save(name:, to:)
162
+
163
+ end
164
+
165
+ def restore(name:, from:, mail: 'model', admin_mail: nil, model: nil)
166
+
167
+ end
168
+
169
+ def admin(name:, logger: @logger)
170
+ Admin.new(name: name, logger: logger)
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,24 @@
1
+ module Superhosting
2
+ module Controller
3
+ class Container
4
+ class Admin < Base
5
+ def initialize(name:, **kvargs)
6
+ @container_name = name
7
+ super(kvargs)
8
+ end
9
+
10
+ def list
11
+
12
+ end
13
+
14
+ def add(name:)
15
+
16
+ end
17
+
18
+ def delete(name:)
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Superhosting
2
+ module Controller
3
+ class Mysql < Base
4
+ def db
5
+ Db.new
6
+ end
7
+
8
+ def user
9
+ User.new
10
+ end
11
+
12
+ def grant
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Superhosting
2
+ module Controller
3
+ class Mysql
4
+ class Db < Base
5
+ def add
6
+
7
+ end
8
+
9
+ def delete
10
+
11
+ end
12
+
13
+ def dump
14
+
15
+ end
16
+
17
+ def sql
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Superhosting
2
+ module Controller
3
+ class Mysql
4
+ class User < Base
5
+ def add(generate:)
6
+
7
+ end
8
+
9
+ def delete
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,106 @@
1
+ module Superhosting
2
+ module Controller
3
+ class Site < Base
4
+ attr_reader :site_index
5
+
6
+ DOMAIN_NAME_FORMAT = /^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$/
7
+
8
+ def site_index
9
+ def generate
10
+ @site_index = {}
11
+ @config.containers._grep_dirs.each do |c|
12
+ c.sites._grep_dirs.each do |s|
13
+ names = []
14
+ names << s._name
15
+ s.aliases.lines {|n| names << n.strip } unless s.aliases.nil?
16
+ raise NetStatus::Exception.new(error: :error, message: "Conflict between container_sites: '#{@site_index[s._name][:site]._path}' and '#{s._path}'") if @site_index.key? s._name
17
+ names.each {|n| @site_index[n] = { container: c, site: s } }
18
+ end
19
+ end
20
+ @site_index
21
+ end
22
+
23
+ @site_index ||= self.generate
24
+ end
25
+
26
+ def add(name:, container_name:)
27
+ return { error: :input_error, message: "Invalid site name '#{name}' only '#{DOMAIN_NAME_FORMAT}'" } if name !~ DOMAIN_NAME_FORMAT
28
+ return { error: :logical_error, message: 'Site already exists.' } if self.site_index.include? name
29
+ return { error: :logical_error, message: "Container '#{container_name}' doesn\'t exists." } if (config_path_mapper = @config.containers.f(container_name)).nil?
30
+
31
+ site_mapper = config_path_mapper.sites.f(name)
32
+ FileUtils.mkdir_p site_mapper._path
33
+
34
+ lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{container_name}")
35
+ model = config_path_mapper.model(default: @config.default_model)
36
+ model_mapper = @config.models.f(:"#{model}")
37
+
38
+ lib_sites_path = lib_path_mapper.registry.sites
39
+ unless model_mapper.f('site.rb').nil?
40
+ registry_path = lib_sites_path.f(name)._path
41
+ FileUtils.mkdir_p lib_sites_path._path
42
+ ex = ScriptExecutor::Site.new(
43
+ site: site_mapper, site_name: name,
44
+ container: config_path_mapper, container_name: name, container_lib: lib_path_mapper, registry_path: registry_path,
45
+ model: model_mapper, config: @config, lib: @lib
46
+ )
47
+ ex.execute(model_mapper.f('site.rb'))
48
+ ex.commands.each {|c| self.command c }
49
+ end
50
+
51
+ {}
52
+ end
53
+
54
+ def delete(name:)
55
+ if site = self.site_index[name]
56
+ FileUtils.rm_rf site[:site]._path
57
+ container_sites = site[:container].sites
58
+ FileUtils.rm_rf container_sites._path if container_sites.nil?
59
+
60
+ config_path_mapper = site[:container]
61
+ lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{config_path_mapper._name}")
62
+ model = config_path_mapper.model(default: @config.default_model)
63
+ model_mapper = @config.models.f(:"#{model}")
64
+
65
+ lib_sites_path = lib_path_mapper.registry.sites
66
+
67
+ unless (registry_site = lib_sites_path.f(name)).nil?
68
+ FileUtils.rm registry_site.lines
69
+ FileUtils.rm registry_site._path
70
+ end
71
+
72
+ unless model_mapper.f('site.rb').nil?
73
+ registry_path = lib_sites_path.f(name)._path
74
+ FileUtils.mkdir_p lib_sites_path._path
75
+ ex = ScriptExecutor::Site.new(
76
+ site: site[:site], site_name: name,
77
+ container: config_path_mapper, container_name: name, container_lib: lib_path_mapper,
78
+ registry_path: registry_path, on_reconfig_only: true,
79
+ model: model_mapper, config: @config, lib: @lib
80
+ )
81
+ ex.execute(model_mapper.f('site.rb'))
82
+ ex.commands.each {|c| self.command c }
83
+ end
84
+
85
+ {}
86
+ end
87
+ end
88
+
89
+ def rename(name:, new_name:)
90
+ return { error: :logical_error, message: "Site '#{name}' doesn't exists." } unless site = self.site_index[name]
91
+ return { error: :logical_error, message: "Site '#{new_name}' already exists." } if self.site_index[new_name]
92
+
93
+ FileUtils.mv site[:container].sites.f(name)._path, site[:container].sites.f(new_name)._path
94
+
95
+ lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{site[:container]._name}")
96
+ unless (lib_sites_path = lib_path_mapper.registry.sites).nil?
97
+ FileUtils.mv lib_sites_path.f(name), lib_sites_path.f(new_name)
98
+ end
99
+ end
100
+
101
+ def alias(name:, logger: @logger)
102
+ Alias.new(name: name, logger: logger)
103
+ end
104
+ end
105
+ end
106
+ end