kaiser 0.0.0 → 0.6.4

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.
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class DbResetHard < Cli
6
+ def usage
7
+ <<~EOS
8
+ Shuts down the database docker container, deletes the docker volume on which the db was stored, deletes the default database image stored at \`~/.kaiser/<ENV_NAME>/<current_github_branch_name>/default.tar.bz\`, rebuilds the docker volume and the default database image from scratch and then brings the container up again.
9
+
10
+ USAGE: kaiser db_reset_hard
11
+ EOS
12
+ end
13
+
14
+ def execute(_opts)
15
+ ensure_setup
16
+ FileUtils.rm db_image_path('default') if File.exist?(db_image_path('default'))
17
+ setup_db
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class DbSave < Cli
6
+ def usage
7
+ <<~EOS
8
+ Shuts down the database docker container, backs up the database and brings the container back up.
9
+
10
+ The database will be saved as a tarball to \`~/.kaiser/<ENV_NAME>/<current_github_branch_name>/<DB_BACKUP_FILENAME>.tar.bz\`
11
+
12
+ Alternatively you can also save it to your current directory.
13
+
14
+ USAGE: kaiser db_save DB_BACKUP_FILENAME
15
+ kaiser db_save ./my_database.tar.bz
16
+ EOS
17
+ end
18
+
19
+ def execute(_opts)
20
+ ensure_setup
21
+ name = ARGV.shift || 'default'
22
+ save_db(name)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Deinit < Cli
6
+ def usage
7
+ <<~EOS
8
+ Removes the Kaiser environment from \`~/.kaiser/.config.yml\`. This however does not delete the \`~/.kaiser/databases/<ENV_NAME>\` directory.
9
+
10
+ USAGE: kaiser init ENV_NAME
11
+ EOS
12
+ end
13
+
14
+ def execute(_opts)
15
+ down
16
+ Config.config[:envs].delete(envname)
17
+ Config.config[:envnames].delete(Config.work_dir)
18
+ save_config
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Down < Cli
6
+ def usage
7
+ <<~EOS
8
+ Shuts down and *deletes* the containers that were started using \`kaiser up\`.
9
+
10
+ USAGE: kaiser down
11
+ EOS
12
+ end
13
+
14
+ def execute(_opts)
15
+ stop_db
16
+ stop_app
17
+ delete_db_volume
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Init < Cli
6
+ # TODO: Add explanation for the Already initialized error.
7
+ def usage
8
+ <<~EOS
9
+ Initializes a Kaiser environment and assigns ports for it in \`~/.kaiser/.config.yml\`. When running \`kaiser up\` later the directory \`~/.kaiser/databases/<ENV_NAME>\` will get created.
10
+
11
+ USAGE: kaiser init ENV_NAME
12
+ EOS
13
+ end
14
+
15
+ def execute(_opts)
16
+ return Optimist.die "Already initialized as #{envname}" if envname
17
+
18
+ name = ARGV.shift
19
+ return Optimist.die 'Needs environment name' if name.nil?
20
+
21
+ init_config_for_env(name)
22
+ save_config
23
+ end
24
+
25
+ def init_config_for_env(name)
26
+ Config.config[:envnames][Config.work_dir] = name
27
+ Config.config[:envs][name] = {
28
+ app_port: (largest_port + 1).to_s,
29
+ db_port: (largest_port + 2).to_s
30
+ }
31
+ Config.config[:largest_port] = Config.config[:largest_port] + 2
32
+ end
33
+
34
+ def largest_port
35
+ Config.config[:largest_port]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Login < Cli
6
+ def usage
7
+ <<~EOS
8
+ Executes a command on the application docker container. By executing the command \`sh\` you can get a login shell.
9
+
10
+ USAGE: kaiser login COMMAND
11
+ EOS
12
+ end
13
+
14
+ def execute(_opts)
15
+ ensure_setup
16
+ cmd = (ARGV || []).join(' ')
17
+ exec "docker exec -ti #{app_container_name} #{cmd}"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Logs < Cli
6
+ def usage
7
+ <<~EOS
8
+ Continuously monitors the application container's logs.
9
+
10
+ USAGE: kaiser logs
11
+ EOS
12
+ end
13
+
14
+ def execute(_opts)
15
+ exec "docker logs -f #{app_container_name}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Root < Cli
6
+ def usage
7
+ <<~USAGE
8
+ Executes a command on the application docker container as a root user.
9
+
10
+ USAGE: kaiser root COMMAND
11
+ USAGE
12
+ end
13
+
14
+ def execute(_opts)
15
+ ensure_setup
16
+ cmd = (ARGV || []).join(' ')
17
+ exec "docker exec -ti --user root #{app_container_name} #{cmd}"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Set < Cli
6
+ def usage
7
+ <<~EOS
8
+ This command lets you set up special variables that configure kaiser's behavior for you.
9
+
10
+ Available subcommands:
11
+
12
+ http-suffix - Sets the domain suffix for the reverse proxy to use (defaults to lvh.me)
13
+ cert-url - Sets up a URL from which HTTPS certificates can be downloaded.
14
+ cert-folder - Sets up a folder from which HTTPS certificates can be copied.
15
+ help-https - Shows the HTTPS notes.
16
+
17
+ USAGE: kaiser set cert-url
18
+ kaiser set cert-folder
19
+ kaiser set http-suffix
20
+ kaiser set help-https
21
+ EOS
22
+ end
23
+
24
+ def execute(_opts)
25
+ cmd = ARGV.shift
26
+
27
+ case cmd
28
+ when 'cert-url'
29
+ Config.config[:cert_source] = {
30
+ url: ARGV.shift
31
+ }
32
+ when 'cert-folder'
33
+ Config.config[:cert_source] = {
34
+ folder: ARGV.shift
35
+ }
36
+ when 'http-suffix'
37
+ Config.config[:http_suffix] = ARGV.shift
38
+ when 'help-https'
39
+ puts <<~SET_HELP
40
+ Notes on HTTPS:
41
+
42
+ You need to set suffix and either cert-url or cert-folder to enable HTTPS.
43
+
44
+ cert-url and cert-folder are mutually exclusive. If you set one of them the other will be erased.
45
+
46
+ The cert-url and cert-folder must satisfy the following requirements to work:
47
+
48
+ The strings must be the root of certificates named after the suffix. For example,
49
+
50
+ if cert-url is https://mydomain.com/certs and your suffix is local.mydomain.com, the following
51
+ url need to be the certificate files:
52
+
53
+ https://mydomain.com/certs/local.mydomain.com.chain.pem
54
+ https://mydomain.com/certs/local.mydomain.com.crt
55
+ https://mydomain.com/certs/local.mydomain.com.key
56
+
57
+ Another example:
58
+
59
+ If you use suffix of localme.com and cert-folder is /home/me/https, The following files need to exist:
60
+
61
+ /home/me/https/localme.com.chain.pem
62
+ /home/me/https/localme.com.crt
63
+ /home/me/https/localme.com.key
64
+ SET_HELP
65
+ else
66
+ Optimist.die "Unknown subcommand: '#{cmd}'"
67
+ end
68
+
69
+ save_config
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Show < Cli
6
+ def usage
7
+ <<~EOS
8
+ Subcommand that shows information about the environment such as the TCP ports or the certificate used for HTTPS.
9
+
10
+ USAGE: kaiser show ports
11
+ kaiser show cert-source
12
+ kaiser show http-suffix
13
+ EOS
14
+ end
15
+
16
+ def execute(_opts)
17
+ ensure_setup
18
+ cmd = ARGV.shift
19
+ valid_cmds = 'ports cert-source http-suffix'
20
+ return Optimist.die "Available things to show: #{valid_cmds}" unless cmd
21
+
22
+ case cmd
23
+ when 'ports'
24
+ Config.info_out.puts "app: #{app_port}"
25
+ Config.info_out.puts "db: #{db_port}"
26
+ when 'cert-source'
27
+ unless Config.config[:cert_source]
28
+ Optimist.die 'No certificate source set.
29
+ see kaiser set help'
30
+ end
31
+
32
+ source = Config.config[:cert_source][:url] || Config.config[:cert_source][:folder]
33
+ Config.info_out.puts source
34
+ when 'http-suffix'
35
+ Config.info_out.puts http_suffix
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Shutdown < Cli
6
+ def usage
7
+ # TODO: Explain a bit more about what these containers do and what shutting
8
+ # them down really means for an end user.
9
+ <<~EOS
10
+ Shuts down all the containers used internally by Kaiser.
11
+
12
+ USAGE: kaiser shutdown
13
+ EOS
14
+ end
15
+
16
+ def execute(_opts)
17
+ Config.config[:shared_names].each do |_, container_name|
18
+ killrm container_name
19
+ end
20
+
21
+ CommandRunner.run Config.out, "docker network rm #{Config.config[:networkname]}"
22
+ CommandRunner.run Config.out, "docker volume rm #{Config.config[:shared_names][:certs]}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Cmds
5
+ class Up < Cli
6
+ option :attach,
7
+ "Bind mount the current source code directory in the app container (as the \`kaiser attach\` command would)", short: '-a'
8
+
9
+ def usage
10
+ <<~EOS
11
+ Boots up the application in docker as defined in the \`Kaiserfile\` in its source code. Usually this will create two docker containers \`<ENV_NAME>-db\` and \`<ENV_NAME>-app\` running your database and application respectively.
12
+
13
+ A backup of the default database is created and saved to \`~/.kaiser/<ENV_NAME>/<current_github_branch_name>/default.tar.bz\`. This can be restored at any time using the \`db_reset\` command.
14
+
15
+ USAGE: kaiser up
16
+ EOS
17
+ end
18
+
19
+ def execute(opts)
20
+ ensure_setup
21
+ setup_app
22
+ setup_db
23
+
24
+ if opts[:attach]
25
+ attach_app
26
+ else
27
+ start_app
28
+ end
29
+ end
30
+
31
+ def setup_app
32
+ Config.info_out.puts 'Setting up application'
33
+ File.write(tmp_dockerfile_name, docker_file_contents)
34
+ build_args = docker_build_args.map { |k, v| "--build-arg #{k}=#{v}" }
35
+ CommandRunner.run! Config.out, "docker build
36
+ -t kaiser:#{envname}-#{current_branch}
37
+ -f #{tmp_dockerfile_name} #{Config.work_dir}
38
+ #{build_args.join(' ')}"
39
+ FileUtils.rm(tmp_dockerfile_name)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
5
+ # This is the command runner
6
+ module Kaiser
7
+ # Make running easy
8
+ class CommandRunner
9
+ def self.run(out, cmd, &block)
10
+ out.puts "> #{cmd}"
11
+ CommandRunner.new(out, cmd).run_command(&block)
12
+ end
13
+
14
+ def self.run!(out, cmd, &block)
15
+ status = run(out, cmd, &block)
16
+ raise Kaiser::CmdError.new(cmd, status) if status.to_s != '0'
17
+ end
18
+
19
+ def initialize(out, cmd)
20
+ @out = out
21
+ @cmd = cmd.tr "\n", ' '
22
+ end
23
+
24
+ def print_and_return_status(status = 0)
25
+ @out.puts "$? = #{status}"
26
+ @out.flush
27
+ status
28
+ end
29
+
30
+ def print_lines(lines)
31
+ lines.each do |line|
32
+ @out.print line
33
+ @out.flush
34
+ yield line.chomp if block_given?
35
+ end
36
+ rescue Errno::EIO
37
+ # Happens when `lines` stream is closed
38
+ end
39
+
40
+ def run_command(&block)
41
+ PTY.spawn("#{@cmd} 2>&1") do |stdout, _stdin, pid|
42
+ print_lines(stdout, &block)
43
+ Process.wait(pid)
44
+ end
45
+ print_and_return_status $CHILD_STATUS.exitstatus
46
+ rescue PTY::ChildExited => e
47
+ print_and_return_status(e.status)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ class Config
5
+ class << self
6
+ attr_accessor :out,
7
+ :info_out
8
+
9
+ attr_reader :work_dir,
10
+ :config_dir,
11
+ :config_file,
12
+ :kaiserfile,
13
+ :config
14
+
15
+ def load(work_dir)
16
+ @work_dir = work_dir
17
+ @config_dir = "#{ENV['HOME']}/.kaiser"
18
+
19
+ migrate_dotted_config_files
20
+
21
+ FileUtils.mkdir_p @config_dir
22
+ @config_file = "#{@config_dir}/config.yml"
23
+ @kaiserfile = Kaiserfile.new("#{@work_dir}/Kaiserfile")
24
+
25
+ @config = {
26
+ envnames: {},
27
+ envs: {},
28
+ networkname: 'kaiser_net',
29
+ shared_names: {
30
+ redis: 'kaiser-redis',
31
+ nginx: 'kaiser-nginx',
32
+ chrome: 'kaiser-chrome',
33
+ dns: 'kaiser-dns',
34
+ certs: 'kaiser-certs'
35
+ },
36
+ largest_port: 9000,
37
+ always_verbose: false
38
+ }
39
+
40
+ load_config
41
+
42
+ alt_kaiserfile = "#{ENV['HOME']}/kaiserfiles/Kaiserfile.#{@config[:envnames][work_dir]}"
43
+ @kaiserfile = Kaiserfile.new(alt_kaiserfile) if File.exist?(alt_kaiserfile)
44
+
45
+ @config
46
+ end
47
+
48
+ def always_verbose?
49
+ @config[:always_verbose]
50
+ end
51
+
52
+ # Up until version 0.5.1, kaiser used dotfiles for all of it configuration.
53
+ # It makes sense of hide the configuration directory itself but hiding the files
54
+ # inside of it just causes confusion.
55
+ #
56
+ # Kaiser 0.5.2 started using non-dotted files instead. This method renames the old
57
+ # files in case you have just upgraded from an older version.
58
+ def migrate_dotted_config_files
59
+ return unless File.exist?("#{@config_dir}/.config.yml")
60
+
61
+ Dir["#{@config_dir}/**/.*"].each do |x|
62
+ dest = x.sub(%r{/\.([a-z.]+)$}, '/\1')
63
+ FileUtils.mv x, dest
64
+ end
65
+ end
66
+
67
+ def load_config
68
+ loaded = YAML.load_file(@config_file) if File.exist?(@config_file)
69
+
70
+ config_shared_names = @config[:shared_names] if @config
71
+ loaded_shared_names = loaded[:shared_names] if loaded
72
+
73
+ @config = {
74
+ **(@config || {}),
75
+ **(loaded || {}),
76
+ shared_names: { **(config_shared_names || {}), **(loaded_shared_names || {}) }
77
+ }
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Databases
5
+ class Mysql
6
+ def initialize(options)
7
+ @options = options
8
+ end
9
+
10
+ def options_hash
11
+ testpass = @options[:root_password] || 'testpassword'
12
+ parameters = @options[:parameters] || ''
13
+ port = @options[:port] || 3306
14
+
15
+ {
16
+ port: port,
17
+ data_dir: '/var/lib/mysql',
18
+ params: "-e MYSQL_ROOT_PASSWORD=#{testpass}",
19
+ commands: parameters,
20
+ waitscript_params: "
21
+ -e MYSQL_ADDR=<%= db_container_name %>
22
+ -e MYSQL_PORT=#{port}
23
+ -e MYSQL_ROOT_PASSWORD=#{testpass}",
24
+ waitscript: <<~SCRIPT
25
+ #!/bin/bash
26
+
27
+ echo "Waiting for mysql to start."
28
+ until mysql -h"$MYSQL_ADDR" -P"$MYSQL_PORT" -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT 1"
29
+ do
30
+ printf "."
31
+ sleep 1
32
+ done
33
+
34
+ echo -e "\nmysql started."
35
+ SCRIPT
36
+ }
37
+ end
38
+
39
+ def image_name
40
+ version = @options[:version] || '5.6'
41
+ "mysql:#{version}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ module Databases
5
+ class Postgres
6
+ def initialize(options)
7
+ @options = options
8
+ end
9
+
10
+ def options_hash
11
+ testpass = @options[:root_password] || 'testpassword'
12
+ parameters = @options[:parameters] || ''
13
+ port = @options[:port] || 3306
14
+ platform = @options[:platform] || 'linux/amd64'
15
+
16
+ {
17
+ port: port,
18
+ data_dir: '/var/lib/postgresql/data',
19
+ params: "-e POSTGRES_PASSWORD=#{testpass}",
20
+ commands: parameters,
21
+ platform: platform,
22
+ waitscript_params: "
23
+ -e PG_HOST=<%= db_container_name %>
24
+ -e PG_USER=postgres
25
+ -e PGPASSWORD=#{testpass}
26
+ -e PG_DATABASE=postgres",
27
+ waitscript: <<~SCRIPT
28
+ #!/bin/sh
29
+
30
+ RETRIES=5
31
+
32
+ until psql -h $PG_HOST -U $PG_USER -d $PG_DATABASE -c "select 1" > /dev/null 2>&1 || [ $RETRIES -eq 0 ]; do
33
+ echo "Waiting for postgres server, $((RETRIES--)) remaining attempts..."
34
+ sleep 1
35
+ done
36
+ SCRIPT
37
+ }
38
+ end
39
+
40
+ def image_name
41
+ version = @options[:version] || 'alpine'
42
+ "postgres:#{version}"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ # Docker control
5
+ class DockerControl
6
+ def initialize
7
+ @docker
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ # Prints dots for every line printed
5
+ class Dotter
6
+ attr_accessor :dotted
7
+
8
+ def method_missing(name, *value)
9
+ $stderr.print '.'
10
+ @dotted = true
11
+ super unless @dotted
12
+ end
13
+
14
+ # rubocop:disable Lint/UselessMethodDefinition
15
+ # If we remove this method rubocop complains about it not existing instead.
16
+ def respond_to_missing?(name)
17
+ super
18
+ end
19
+ # rubocop:enable Lint/UselessMethodDefinition
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaiser
4
+ # Base class for Kaiser-related errors.
5
+ class Error < StandardError
6
+ end
7
+
8
+ # Raised when a command exits with non-zero exit status.
9
+ class CmdError < Error
10
+ def initialize(cmd, status)
11
+ super "ERROR\n#{cmd}\n- exited with code #{status}"
12
+ end
13
+ end
14
+ end