mirror of
https://github.com/lloeki/islo.git
synced 2025-12-06 02:24:40 +01:00
Islo w/basic MySQL, Redis and PostgreSQL support
This commit is contained in:
commit
743d628733
10 changed files with 491 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*.gem
|
||||
Gemfile.lock
|
||||
3
Gemfile
Normal file
3
Gemfile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gemspec
|
||||
20
LICENSE
Normal file
20
LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2014 Loic Nageleisen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
74
README.md
Normal file
74
README.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Islo - Self-contained apps
|
||||
|
||||
Make apps completely self-contained by abstracting service process settings and execution.
|
||||
|
||||
## Quick, show me how to use my favorite daemon!
|
||||
|
||||
First, install Islo:
|
||||
|
||||
```
|
||||
$ gem install islo
|
||||
```
|
||||
|
||||
Then, a nice example might be worth a thousand words, so here goes:
|
||||
|
||||
### MySQL or MariaDB
|
||||
|
||||
```
|
||||
$ islo mysql_install_db # creates database in db/mysql
|
||||
$ islo mysqld # starts server without daemonizing[^1]
|
||||
$ islo mysql # connects to running server via unix socket in tmp/sockets
|
||||
```
|
||||
|
||||
### Redis
|
||||
|
||||
```
|
||||
$ islo redis-init # creates directory in db/redis
|
||||
$ islo redis-server # starts server without daemonizing[^1]
|
||||
$ islo redis-cli # connects to server via unix socket in tmp/sockets
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```
|
||||
$ islo initdb # creates directory in db/postgres
|
||||
$ islo postgres # starts server without daemonizing[^1]
|
||||
$ islo psql # connects to server via unix socket in tmp/sockets
|
||||
```
|
||||
|
||||
[^1]: Best used in a [Procfile](https://github.com/ddollar/foreman)
|
||||
|
||||
## What's more to know?
|
||||
|
||||
- Additional arguments are passed to the command.
|
||||
|
||||
Run `islo --help` for details.
|
||||
|
||||
- Servers will listen only on unix sockets, TCP will be disabled.
|
||||
|
||||
This saves headaches when you have to handle multiple projects, and thus
|
||||
conflicting ports. Also, it's too easy to forget not to listen for the world.
|
||||
|
||||
## My service is installed in a non-standard location/I want to use different versions in different projects
|
||||
|
||||
Configuration is a pending item, which will make locations selectable.
|
||||
|
||||
## I don't like how it assumes a Rails project layout
|
||||
|
||||
Configuration is a pending item, which will help set relevant paths.
|
||||
|
||||
## I've got a super service you don't seem to know about
|
||||
|
||||
Some configuration may help you soon. Also, contributions are welcome.
|
||||
|
||||
## I can't be bothered/always forget to type *islo* before my commands every single time!
|
||||
|
||||
Look soon enough under `support` for a few optional helpers for your favorite shell.
|
||||
|
||||
## I want to contribute. How?
|
||||
|
||||
Great! Write specs, have them all pass, respect rubocop, rebase on master and make your PR.
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE](LICENSE).
|
||||
0
Rakefile
Normal file
0
Rakefile
Normal file
5
bin/islo
Executable file
5
bin/islo
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'islo/cli'
|
||||
|
||||
Islo::CLI.start
|
||||
29
islo.gemspec
Normal file
29
islo.gemspec
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
||||
require 'islo/version'
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'islo'
|
||||
s.version = Islo::VERSION
|
||||
s.authors = ['Loic Nageleisen']
|
||||
s.email = ['loic.nageleisen@gmail.com']
|
||||
s.homepage = 'http://github.com/lloeki/islo'
|
||||
s.summary = %q(Self-contained apps)
|
||||
s.description = <<-EOT
|
||||
Makes app completely self-contained by abstracting
|
||||
service process settings and execution
|
||||
EOT
|
||||
s.license = 'MIT'
|
||||
s.files = `git ls-files`.split("\n")
|
||||
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||
s.executables = `git ls-files -- bin/*`.split("\n")
|
||||
.map { |f| File.basename(f) }
|
||||
s.require_paths = ['lib']
|
||||
|
||||
s.add_dependency 'rainbow', '~> 2.0'
|
||||
s.add_dependency 'slop'
|
||||
|
||||
s.add_development_dependency 'rspec', '~> 2.14'
|
||||
s.add_development_dependency 'rake', '~> 10.3'
|
||||
s.add_development_dependency 'pry'
|
||||
end
|
||||
264
lib/islo.rb
Normal file
264
lib/islo.rb
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
# Islo - application isolator
|
||||
module Islo
|
||||
# Generic command execution
|
||||
class Command
|
||||
class Error < StandardError; end
|
||||
|
||||
attr_reader :title, :wd
|
||||
attr_reader :command, :args
|
||||
|
||||
def initialize(args, title: nil, wd: nil)
|
||||
@command = args.shift
|
||||
@args = args
|
||||
@title = title unless title.nil? || title.empty?
|
||||
@wd = Pathname.new(wd || Dir.pwd)
|
||||
end
|
||||
|
||||
def title?
|
||||
!title.nil?
|
||||
end
|
||||
|
||||
def exec
|
||||
Dir.chdir(wd.to_s)
|
||||
Kernel.exec(*args_for_exec)
|
||||
rescue SystemCallError => e
|
||||
raise Command::Error, e.message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def args_for_exec
|
||||
command = (title? ? [@command, title] : @command)
|
||||
args = self.args
|
||||
|
||||
[command] + args
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def commands
|
||||
@commands ||= {}
|
||||
end
|
||||
|
||||
def register(command)
|
||||
commands[command.name.to_sym] = command
|
||||
end
|
||||
|
||||
def command(args, options = {})
|
||||
name = File.basename(args[0]).to_sym
|
||||
(commands[name] || Command).new(args, options)
|
||||
end
|
||||
end
|
||||
|
||||
# MySQL support
|
||||
module Mysql
|
||||
# MySQL client
|
||||
class Client < Command
|
||||
def self.name
|
||||
:mysql
|
||||
end
|
||||
|
||||
def args
|
||||
%W(
|
||||
--socket=#{wd}/tmp/sockets/mysql.sock
|
||||
-uroot
|
||||
) + super
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
|
||||
# MySQL server
|
||||
class Server < Command
|
||||
def self.name
|
||||
:mysqld
|
||||
end
|
||||
|
||||
def args
|
||||
%W(
|
||||
--no-defaults
|
||||
--datadir=#{wd}/db/mysql
|
||||
--pid-file=#{wd}/tmp/pids/mysqld.pid
|
||||
--socket=#{wd}/tmp/sockets/mysql.sock
|
||||
--skip-networking
|
||||
) + super
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
|
||||
# MySQL initializer
|
||||
class Init < Command
|
||||
def self.name
|
||||
:mysql_install_db
|
||||
end
|
||||
|
||||
def args
|
||||
%W(
|
||||
--no-defaults
|
||||
--basedir=#{Mysql.basedir}
|
||||
--datadir=#{wd}/db/mysql
|
||||
--pid-file=#{wd}/tmp/pids/mysqld.pid
|
||||
) + super
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
|
||||
def self.basedir
|
||||
'/usr/local'
|
||||
end
|
||||
end
|
||||
|
||||
# Redis support
|
||||
module Redis
|
||||
# Redis client
|
||||
class Client < Command
|
||||
def self.name
|
||||
:'redis-cli'
|
||||
end
|
||||
|
||||
def args
|
||||
%w(-s redis.sock)
|
||||
end
|
||||
|
||||
# Change working directory (makes for a nicer prompt)
|
||||
def wd
|
||||
super + 'tmp/sockets'
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
|
||||
# Redis server
|
||||
class Server < Command
|
||||
def self.name
|
||||
:'redis-server'
|
||||
end
|
||||
|
||||
def args
|
||||
%W(#{wd}/db/redis/redis.conf)
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
|
||||
# Redis initializer
|
||||
#
|
||||
# Creates a minimal configuration because redis-server doesn't accept
|
||||
# arguments allowing for paths to be set.
|
||||
class Init < Command
|
||||
def self.name
|
||||
:'redis-init'
|
||||
end
|
||||
|
||||
def exec
|
||||
FileUtils.mkdir_p(wd + 'db/redis')
|
||||
|
||||
File.open(wd + 'db/redis/redis.conf', 'w') do |f|
|
||||
f << template.gsub('${WORKING_DIR}', wd.to_s)
|
||||
end
|
||||
rescue SystemCallError => e
|
||||
raise Command::Error, e.message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# rubocop:disable MethodLength,LineLength
|
||||
def template
|
||||
<<-EOT.gsub(/^ +/, '')
|
||||
daemonize no
|
||||
pidfile ${WORKING_DIR}/pids/redis.pid
|
||||
port 0
|
||||
bind 127.0.0.1
|
||||
unixsocket ${WORKING_DIR}/tmp/sockets/redis.sock
|
||||
unixsocketperm 700
|
||||
timeout 0
|
||||
tcp-keepalive 0
|
||||
loglevel notice
|
||||
databases 1
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
dbfilename dump.rdb
|
||||
dir ${WORKING_DIR}/db/redis
|
||||
slave-serve-stale-data yes
|
||||
slave-read-only yes
|
||||
repl-disable-tcp-nodelay no
|
||||
slave-priority 100
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
lua-time-limit 5000
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
hash-max-ziplist-entries 512
|
||||
hash-max-ziplist-value 64
|
||||
list-max-ziplist-entries 512
|
||||
list-max-ziplist-value 64
|
||||
set-max-intset-entries 512
|
||||
zset-max-ziplist-entries 128
|
||||
zset-max-ziplist-value 64
|
||||
activerehashing yes
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
EOT
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
end
|
||||
|
||||
# PostgreSQL support
|
||||
module Postgres
|
||||
# PostgreSQL client
|
||||
class Client < Command
|
||||
def self.name
|
||||
:psql
|
||||
end
|
||||
|
||||
def args
|
||||
%W(--host=#{wd}/tmp/sockets) + super
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
|
||||
# PostgreSQL server
|
||||
class Server < Command
|
||||
def self.name
|
||||
:postgres
|
||||
end
|
||||
|
||||
def args
|
||||
%W(
|
||||
-D #{wd}/db/postgres
|
||||
-k #{wd}/tmp/sockets
|
||||
) + ['-h', ''] + super
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
|
||||
# PostgreSQL initializer
|
||||
class Init < Command
|
||||
def self.name
|
||||
:initdb
|
||||
end
|
||||
|
||||
def args
|
||||
%W(
|
||||
-D #{wd}/db/postgres
|
||||
) + super
|
||||
end
|
||||
|
||||
Islo.register(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
91
lib/islo/cli.rb
Normal file
91
lib/islo/cli.rb
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
require 'slop'
|
||||
require 'islo'
|
||||
require 'rainbow/ext/string'
|
||||
|
||||
module Islo
|
||||
# Handle and run the command line interface
|
||||
class CLI
|
||||
NAME = File.basename($PROGRAM_NAME)
|
||||
|
||||
# See sysexits(3)
|
||||
RC = { ok: 0,
|
||||
error: 1,
|
||||
usage: 64,
|
||||
data: 65,
|
||||
noinput: 66,
|
||||
nouser: 67,
|
||||
nohost: 68,
|
||||
unavailable: 69,
|
||||
software: 70,
|
||||
oserr: 71,
|
||||
osfile: 72,
|
||||
cantcreat: 73,
|
||||
ioerr: 74,
|
||||
tempfail: 75,
|
||||
protocol: 76,
|
||||
noperm: 77,
|
||||
config: 78 }
|
||||
|
||||
# Shortcut for Islo::CLI.new.start
|
||||
def self.start
|
||||
new.start
|
||||
end
|
||||
|
||||
attr_reader :args
|
||||
|
||||
def opts
|
||||
@opts.to_hash
|
||||
end
|
||||
|
||||
def initialize(args = ARGV)
|
||||
@args, @opts = parse(args)
|
||||
rescue Slop::InvalidOptionError => e
|
||||
die(:usage, e.message)
|
||||
rescue Slop::MissingArgumentError => e
|
||||
die(:usage, e.message)
|
||||
end
|
||||
|
||||
# Start execution based on options
|
||||
def start
|
||||
Islo.command(args, title: opts[:title]).exec
|
||||
rescue Islo::Command::Error => e
|
||||
die(:oserr, e.message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Stop with a popping message and exit with a return code
|
||||
def die(rc, message)
|
||||
$stderr.puts('Error: '.color(:red) << message)
|
||||
exit(rc.is_a?(Symbol) ? RC[rc] : rc)
|
||||
end
|
||||
|
||||
# Stop with a nice message
|
||||
def bye(message)
|
||||
$stdout.puts message
|
||||
exit
|
||||
end
|
||||
|
||||
def version_string
|
||||
"#{NAME} v#{Islo::VERSION}"
|
||||
end
|
||||
|
||||
# Parse arguments and set options
|
||||
# rubocop:disable MethodLength
|
||||
def parse(args)
|
||||
args = args.dup
|
||||
|
||||
opts = Slop.parse!(args, strict: true, help: true) do
|
||||
banner "Usage: #{NAME} [options] [--] command arguments"
|
||||
|
||||
on 't', 'title=', 'String displayed in process list', argument: true
|
||||
on 'd', 'directory=', 'Change working directory', argument: true
|
||||
on 'v', 'version', 'Print the version', -> { bye(version_string) }
|
||||
end
|
||||
|
||||
fail Slop::MissingArgumentError, 'missing an argument' if args.empty?
|
||||
|
||||
[args, opts]
|
||||
end
|
||||
end
|
||||
end
|
||||
3
lib/islo/version.rb
Normal file
3
lib/islo/version.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module Islo
|
||||
VERSION = '0.1.0'
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue