heighliner 0.9.0

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,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heighliner
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
+ :steerfile,
13
+ :config
14
+
15
+ def load(work_dir, use_steerfile: true)
16
+ @work_dir = work_dir
17
+ @config_dir = "#{ENV['HOME']}/.heighliner"
18
+
19
+ migrate_dotted_config_files
20
+
21
+ FileUtils.mkdir_p @config_dir
22
+ @config_file = "#{@config_dir}/config.yml"
23
+
24
+ @config = {
25
+ envnames: {},
26
+ envs: {},
27
+ networkname: 'heighliner_net',
28
+ shared_names: {
29
+ nginx: 'heighliner-nginx',
30
+ chrome: 'heighliner-chrome',
31
+ dns: 'heighliner-dns',
32
+ certs: 'heighliner-certs'
33
+ },
34
+ largest_port: 9000,
35
+ always_verbose: false
36
+ }
37
+
38
+ load_config
39
+
40
+ if use_steerfile
41
+ POSSIBLE_STEERFILES.each do |x|
42
+ @fname = x if File.exist?(x)
43
+ end
44
+
45
+ Optimist.die <<~ERROR if @fname.nil?
46
+ No Steerfile in current directory.
47
+ Possible names are #{POSSIBLE_STEERFILES.join(', ')}"
48
+ ERROR
49
+
50
+ @steerfile = Steerfile.new("#{@work_dir}/#{@fname}")
51
+ end
52
+
53
+ @config
54
+ end
55
+
56
+ POSSIBLE_STEERFILES = %w[
57
+ Steerfile
58
+ Heighliner.config
59
+ heighliner.config
60
+ ].freeze
61
+
62
+ def always_verbose?
63
+ @config[:always_verbose]
64
+ end
65
+
66
+ # Up until version 0.5.1, heighliner used dotfiles for all of it configuration.
67
+ # It makes sense of hide the configuration directory itself but hiding the files
68
+ # inside of it just causes confusion.
69
+ #
70
+ # Heighliner 0.5.2 started using non-dotted files instead. This method renames the old
71
+ # files in case you have just upgraded from an older version.
72
+ def migrate_dotted_config_files
73
+ return unless File.exist?("#{@config_dir}/.config.yml")
74
+
75
+ Dir["#{@config_dir}/**/.*"].each do |x|
76
+ dest = x.sub(%r{/\.([a-z.]+)$}, '/\1')
77
+ FileUtils.mv x, dest
78
+ end
79
+ end
80
+
81
+ def load_config
82
+ loaded = YAML.load_file(@config_file) if File.exist?(@config_file)
83
+
84
+ config_shared_names = @config[:shared_names] if @config
85
+ loaded_shared_names = loaded[:shared_names] if loaded
86
+
87
+ @config = {
88
+ **(@config || {}),
89
+ **(loaded || {}),
90
+ shared_names: { **(config_shared_names || {}), **(loaded_shared_names || {}) }
91
+ }
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heighliner
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 Heighliner
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 Heighliner
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 Heighliner
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 Heighliner
4
+ # Base class for Heighliner-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
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heighliner
4
+ # To implement a Heighliner plugin you must inherit from this class.
5
+ # For example,
6
+ #
7
+ # class MyPlugin < Plugin
8
+ # def on_init
9
+ # puts 'My plugin is loaded!'
10
+ # end
11
+ # end
12
+ #
13
+ # Then in your Kasierfile
14
+ #
15
+ # plugin :my_plugin
16
+ #
17
+ # Plugins has access the Steerfile DSL. For example,
18
+ #
19
+ # class Ruby < Plugin
20
+ # def on_init
21
+ # attach_mount 'Gemfile', '/usr/app/Gemfile'
22
+ # attach_mount 'Gemfile.lock', '/usr/app/Gemfile.lock'
23
+ # end
24
+ # end
25
+ #
26
+ class Plugin
27
+ def initialize(steerfile)
28
+ @steerfile = steerfile
29
+ end
30
+
31
+ def self.loaded?(name)
32
+ Plugin.all_plugins.key?(name)
33
+ end
34
+
35
+ def self.inherited(plugin)
36
+ super
37
+
38
+ puts "INHERITED #{plugin}"
39
+
40
+ # underscore class name
41
+ name = plugin.to_s.split('::').last
42
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
43
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
44
+ .gsub(/([a-z])(\d)/, '\1_\2')
45
+ .tr('-', '_').downcase
46
+
47
+ Plugin.all_plugins[name.to_sym] = plugin
48
+ end
49
+
50
+ def self.all_plugins
51
+ @all_plugins ||= {}
52
+ end
53
+
54
+ def on_init
55
+ raise 'Please implement #on_init'
56
+ end
57
+
58
+ def method_missing(method_sym, *arguments, &block) # rubocop:disable all
59
+ @steerfile.send(method_sym, *arguments, &block)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Little stub to initialize the namespace
65
+ module Heighliner
66
+ module Plugins
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module Heighliner
6
+ module Plugins
7
+ class Database < Plugin
8
+ def on_init
9
+ @steerfile.define_singleton_method :def_db do |*args|
10
+ args[0] = { args.first => {} } unless args.first.is_a? Hash
11
+
12
+ option = args.first
13
+
14
+ driver_name = option.keys.first.to_s
15
+
16
+ begin
17
+ require "heighliner/databases/#{driver_name}"
18
+ rescue LoadError
19
+ raise "Unknown database '#{driver_name}'"
20
+ end
21
+
22
+ driver_class = Heighliner::Databases.const_get(driver_name.camelize)
23
+ db_driver = driver_class.new(option.values.first)
24
+
25
+ db(db_driver.image_name, **db_driver.options_hash)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heighliner
4
+ module Plugins
5
+ class GitSubmodule < Plugin
6
+ def on_init
7
+ `git submodule status`.lines.each do |line|
8
+ # The git-submodule man page says uninitialized submodules are prefixed with a -
9
+ # but I found this unreliable. While testing I pressed Control-C in the middle of
10
+ # the update command so some submodule would be initialized and others wouldn't.
11
+ # After that, the status command had removed the - for every submodule.
12
+ # Therefore we just check if there's files in the directory instead.
13
+ dir = line.strip.split(' ')[1]
14
+ if !Dir.exist?(dir) || Dir.empty?(dir) # rubocop:disable Style/Next
15
+ puts "Found uninitialized git submodule '#{dir}'"
16
+ puts "please run 'git submodule update --init --recursive'"
17
+ exit 1
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heighliner
4
+ # This class describes an app-specific service
5
+ class Service
6
+ attr_reader :name
7
+
8
+ def initialize(envname, name, service_info)
9
+ @envname = envname
10
+ @name = name
11
+ @service_info = service_info
12
+ end
13
+
14
+ def shared_name
15
+ "#{@envname}-#{name}"
16
+ end
17
+
18
+ def image
19
+ @service_info[:image]
20
+ end
21
+
22
+ def command
23
+ @service_info[:command].to_s
24
+ end
25
+
26
+ def binds
27
+ @service_info[:binds] || {}
28
+ end
29
+
30
+ def env
31
+ @service_info[:env] || {}
32
+ end
33
+
34
+ def start_docker_command
35
+ envstring = env.map do |k, v|
36
+ "-e #{k}=#{v}"
37
+ end.join(' ')
38
+
39
+ bindstring = binds.map do |k, v|
40
+ "-v #{k}:#{v}"
41
+ end.join(' ')
42
+
43
+ commandstring = command
44
+
45
+ cmd_array = [
46
+ 'docker run -d',
47
+ "--name #{shared_name}",
48
+ "--network #{Config.config[:networkname]}",
49
+ envstring,
50
+ bindstring,
51
+ image,
52
+ commandstring
53
+ ]
54
+
55
+ cmd_array.filter! { |x| !x.empty? }
56
+
57
+ cmd_array.join(' ')
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heighliner
4
+ # This class is responsible for parsing the Steerfile
5
+ class Steerfile
6
+ attr_accessor :docker_file_contents,
7
+ :docker_build_args,
8
+ :database,
9
+ :platform,
10
+ :port,
11
+ :database_reset_command,
12
+ :attach_mounts,
13
+ :server_type,
14
+ :services
15
+
16
+ def initialize(filename)
17
+ Optimist.die 'No Steerfile in current directory' unless File.exist? filename
18
+
19
+ @database = {
20
+ image: 'none',
21
+ platform: '',
22
+ port: 1234,
23
+ data_dir: '/tmp/data',
24
+ params: '',
25
+ commands: 'echo "no db"',
26
+ waitscript: 'echo "no dbwait"',
27
+ waitscript_params: ''
28
+ }
29
+ @attach_mounts = []
30
+ @params_array = []
31
+ @server_type = :unknown
32
+ @database_reset_command = 'echo "no db to reset"'
33
+ @port = 1234
34
+ @services = {}
35
+
36
+ instance_eval File.read(filename), filename
37
+ end
38
+
39
+ def validate!
40
+ raise 'No dockerfile specified.' if @docker_file_contents.nil?
41
+ end
42
+
43
+ def plugin(name)
44
+ require "heighliner/plugins/#{name}"
45
+ raise "Plugin #{name} is not loaded." unless Plugin.loaded?(name)
46
+
47
+ Plugin.all_plugins[name].new(self).on_init
48
+ end
49
+
50
+ def dockerfile(name, options = {})
51
+ @docker_file_contents = File.read(name)
52
+ @docker_build_args = options[:args] || {}
53
+ end
54
+
55
+ def attach_mount(from, to)
56
+ attach_mounts << [from, to]
57
+ end
58
+
59
+ def db(image,
60
+ data_dir:,
61
+ port:,
62
+ platform: '',
63
+ params: '',
64
+ commands: '',
65
+ waitscript: nil,
66
+ waitscript_params: '')
67
+ @database = {
68
+ image: image,
69
+ platform: platform,
70
+ port: port,
71
+ data_dir: data_dir,
72
+ params: params,
73
+ commands: commands,
74
+ waitscript: waitscript,
75
+ waitscript_params: waitscript_params
76
+ }
77
+ end
78
+
79
+ def force_platform(platform_name)
80
+ @platform = platform_name
81
+ end
82
+
83
+ def expose(port)
84
+ @port = port
85
+ end
86
+
87
+ def app_params(value)
88
+ @params_array << value
89
+ end
90
+
91
+ def params
92
+ @params_array.join(' ')
93
+ end
94
+
95
+ def db_reset_command(value)
96
+ @database_reset_command = value
97
+ end
98
+
99
+ def type(value)
100
+ raise 'Valid server types are: [:http]' if value != :http
101
+
102
+ @server_type = value
103
+ end
104
+
105
+ def service(name,
106
+ image: name,
107
+ command: nil,
108
+ binds: {},
109
+ env: {})
110
+ raise "duplicate service #{name.inspect}" if @services.key?(name)
111
+
112
+ @services[name] = {
113
+ image: image,
114
+ command: command,
115
+ binds: binds,
116
+ env: env
117
+ }
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heighliner
4
+ VERSION = '0.9.0'
5
+ end
data/lib/heighliner.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'heighliner/error'
4
+ require 'heighliner/version'
5
+ require 'heighliner/steerfile'
6
+ require 'heighliner/cli_options'
7
+ require 'heighliner/cli'
8
+ require 'heighliner/after_dotter'
9
+ require 'heighliner/dotter'
10
+
11
+ require 'heighliner/config'
12
+
13
+ require 'heighliner/cmds/init'
14
+ require 'heighliner/cmds/deinit'
15
+ require 'heighliner/cmds/up'
16
+ require 'heighliner/cmds/down'
17
+ require 'heighliner/cmds/shutdown'
18
+ require 'heighliner/cmds/db_save'
19
+ require 'heighliner/cmds/db_load'
20
+ require 'heighliner/cmds/db_reset'
21
+ require 'heighliner/cmds/db_reset_hard'
22
+ require 'heighliner/cmds/logs'
23
+ require 'heighliner/cmds/attach'
24
+ require 'heighliner/cmds/login'
25
+ require 'heighliner/cmds/show'
26
+ require 'heighliner/cmds/set'
27
+ require 'heighliner/cmds/root'
28
+
29
+ require 'heighliner/plugin'
30
+
31
+ require 'heighliner/service'
32
+
33
+ # Heighliner
34
+ module Heighliner
35
+ SUB_COMMANDS = {
36
+ init: Heighliner::Cmds::Init,
37
+ deinit: Heighliner::Cmds::Deinit,
38
+ up: Heighliner::Cmds::Up,
39
+ down: Heighliner::Cmds::Down,
40
+ shutdown: Heighliner::Cmds::Shutdown,
41
+ db_save: Heighliner::Cmds::DbSave,
42
+ db_load: Heighliner::Cmds::DbLoad,
43
+ db_reset: Heighliner::Cmds::DbReset,
44
+ db_reset_hard: Heighliner::Cmds::DbResetHard,
45
+ logs: Heighliner::Cmds::Logs,
46
+ attach: Heighliner::Cmds::Attach,
47
+ login: Heighliner::Cmds::Login,
48
+ show: Heighliner::Cmds::Show,
49
+ set: Heighliner::Cmds::Set,
50
+ root: Heighliner::Cmds::Root
51
+ }.freeze
52
+ end