izanami 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.env.development +6 -0
- data/.gitignore +23 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.md +133 -0
- data/Rakefile +34 -0
- data/assets/css/app.css +1 -0
- data/assets/css/bootstrap.css +6805 -0
- data/assets/css/bootstrap.min.css +9 -0
- data/assets/js/app.js +47 -0
- data/assets/js/bootstrap.js +1999 -0
- data/assets/js/bootstrap.min.js +6 -0
- data/assets/js/jquery-2.0.3.min.js +6 -0
- data/bin/izanami +14 -0
- data/bin/izanami-app +14 -0
- data/bin/izanami-watchdog +12 -0
- data/izanami.gemspec +30 -0
- data/lib/izanami/app.rb +165 -0
- data/lib/izanami/channel.rb +133 -0
- data/lib/izanami/mapper.rb +64 -0
- data/lib/izanami/mappers/command.rb +161 -0
- data/lib/izanami/version.rb +3 -0
- data/lib/izanami/worker.rb +59 -0
- data/lib/izanami/workers/command.rb +82 -0
- data/lib/izanami/workers/watchdog.rb +65 -0
- data/lib/izanami.rb +5 -0
- data/spec/helper.rb +12 -0
- data/spec/izanami/app_spec.rb +195 -0
- data/spec/izanami/channel/input_spec.rb +38 -0
- data/spec/izanami/channel_spec.rb +66 -0
- data/spec/izanami/mapper_spec.rb +48 -0
- data/spec/izanami/mappers/command_spec.rb +154 -0
- data/spec/izanami/workers/command_spec.rb +122 -0
- data/spec/sandbox/Capfile +4 -0
- data/spec/sandbox/Gemfile +3 -0
- data/views/_commands_panel.erb +12 -0
- data/views/command.erb +10 -0
- data/views/home.erb +44 -0
- data/views/layout.erb +32 -0
- metadata +207 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Izanami
|
4
|
+
|
5
|
+
# Open a communication channel between workers to handle the output
|
6
|
+
# of a command.
|
7
|
+
#
|
8
|
+
# A channel does two things: it publish and reads the data
|
9
|
+
# sent by a worker to a Redis pub/sub channel and stores that data when
|
10
|
+
# the channel is closed. That way, a process can read the output of
|
11
|
+
# a command when this is been executed or when is finished.
|
12
|
+
class Channel
|
13
|
+
EOC = '--EOC--' # End Of Channel
|
14
|
+
EOC.freeze
|
15
|
+
|
16
|
+
SEPARATOR = "\n"
|
17
|
+
SEPARATOR.freeze
|
18
|
+
|
19
|
+
# Handles the input written to the channel.
|
20
|
+
class Input
|
21
|
+
|
22
|
+
# @param [Izanami::Mapper::Command] mapper the instance which talks to redis.
|
23
|
+
# @param [String] id the command id.
|
24
|
+
def initialize(mapper, id)
|
25
|
+
@mapper = mapper
|
26
|
+
@id = id
|
27
|
+
@content = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add new content to the channel.
|
31
|
+
#
|
32
|
+
# @param [String] payload the new content to add.
|
33
|
+
def <<(payload)
|
34
|
+
@mapper.publish(@id, payload)
|
35
|
+
@content << payload
|
36
|
+
end
|
37
|
+
alias_method :add, :<<
|
38
|
+
|
39
|
+
# Closes the channel.
|
40
|
+
#
|
41
|
+
# It stores all the content sent and sends an {EOC} signal
|
42
|
+
#
|
43
|
+
# @return [String] all the written content.
|
44
|
+
def close
|
45
|
+
string = to_s
|
46
|
+
@mapper.update(@id, 'output', string)
|
47
|
+
@mapper.publish(@id, EOC)
|
48
|
+
|
49
|
+
string
|
50
|
+
end
|
51
|
+
|
52
|
+
# Content that has been written.
|
53
|
+
#
|
54
|
+
# @note each line of the content is separated by {SEPARATOR}.
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
def to_s
|
58
|
+
@content.join(SEPARATOR)
|
59
|
+
end
|
60
|
+
alias_method :read, :to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param [Izanami::Mapper::Command] mapper the instance which talks to redis.
|
64
|
+
def initialize(mapper)
|
65
|
+
@mapper = mapper
|
66
|
+
end
|
67
|
+
|
68
|
+
# Opens the channel to write.
|
69
|
+
#
|
70
|
+
# @note if a block is given, the input is closed when the block
|
71
|
+
# finish. If not, the client of the channel must close it.
|
72
|
+
#
|
73
|
+
# @param [String] id the command id.
|
74
|
+
#
|
75
|
+
# @yield [Izanami::Channel::Input] content handler.
|
76
|
+
#
|
77
|
+
# @return [Izanami::Channel::Input]
|
78
|
+
def write(id)
|
79
|
+
input = Input.new(@mapper, id)
|
80
|
+
if block_given?
|
81
|
+
yield input
|
82
|
+
input.close
|
83
|
+
end
|
84
|
+
|
85
|
+
input
|
86
|
+
end
|
87
|
+
|
88
|
+
# Opens the channel for reading.
|
89
|
+
#
|
90
|
+
# @param [String] id the command id.
|
91
|
+
#
|
92
|
+
# @yield [String] each of the lines read from the channel.
|
93
|
+
def read(id, &block)
|
94
|
+
command = @mapper.find(id) || {}
|
95
|
+
output = command['output']
|
96
|
+
|
97
|
+
if output
|
98
|
+
read_string(output, &block)
|
99
|
+
else
|
100
|
+
read_buffer(id, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Reads the content from a string.
|
105
|
+
#
|
106
|
+
# @param [String] output the stored command's output.
|
107
|
+
#
|
108
|
+
# @yield [String] each of the lines read from the stored output.
|
109
|
+
def read_string(output, &block)
|
110
|
+
output.split(SEPARATOR).each(&block)
|
111
|
+
end
|
112
|
+
protected :read_string
|
113
|
+
|
114
|
+
# Reads the content from Redis channel.
|
115
|
+
#
|
116
|
+
# @param [String] id the command id.
|
117
|
+
#
|
118
|
+
# @yield [String] each of the lines read from the Redis channel.
|
119
|
+
# It stops when a {EOC} flag is read.
|
120
|
+
def read_buffer(id)
|
121
|
+
@mapper.subscribe(id) do |on|
|
122
|
+
on.message do |_, line|
|
123
|
+
if line.match(/\A#{EOC}\z/)
|
124
|
+
@mapper.unsubscribe(id)
|
125
|
+
else
|
126
|
+
yield line
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
protected :read_buffer
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'redis/namespace'
|
3
|
+
|
4
|
+
module Izanami
|
5
|
+
|
6
|
+
# Proxy around a Redis client.
|
7
|
+
# It handles the configuration and the namespaces.
|
8
|
+
class Mapper
|
9
|
+
attr_reader :namespace, :options
|
10
|
+
|
11
|
+
# New Mapper client.
|
12
|
+
#
|
13
|
+
# @param [Hash] options
|
14
|
+
# @option options [String] :namespace the default namespace for all the keys.
|
15
|
+
# @option options [Hash] :redis Redis client options
|
16
|
+
def initialize(options = {})
|
17
|
+
@options = options.dup
|
18
|
+
@redis = @options.delete(:redis)
|
19
|
+
@namespace = build_namespace(@options.delete(:namespace))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generate the namespace based in a key.
|
23
|
+
#
|
24
|
+
# @param [String] key the default mapper key.
|
25
|
+
#
|
26
|
+
# @return [String] the namespace.
|
27
|
+
def build_namespace(key)
|
28
|
+
key || 'izanami'
|
29
|
+
end
|
30
|
+
protected :build_namespace
|
31
|
+
|
32
|
+
# Redis client, without the namespace.
|
33
|
+
#
|
34
|
+
# @return [Redis::Client]
|
35
|
+
def redis
|
36
|
+
@redis ||= initialize_redis
|
37
|
+
end
|
38
|
+
|
39
|
+
# Redis client, with the namespace.
|
40
|
+
#
|
41
|
+
# @return [Redis::Namespace]
|
42
|
+
def client
|
43
|
+
@client ||= initialize_client
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize_redis
|
47
|
+
Redis.new(@options)
|
48
|
+
end
|
49
|
+
protected :initialize_redis
|
50
|
+
|
51
|
+
def initialize_client
|
52
|
+
Redis::Namespace.new(@namespace, redis: redis)
|
53
|
+
end
|
54
|
+
protected :initialize_client
|
55
|
+
|
56
|
+
# Inspect the mapper
|
57
|
+
def to_s(*)
|
58
|
+
name = self.class.name
|
59
|
+
info = redis.inspect
|
60
|
+
|
61
|
+
"<#{name} connected to #{info} with @namespace=\"#{@namespace}\">"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'izanami/mapper'
|
2
|
+
|
3
|
+
module Izanami
|
4
|
+
module Mappers
|
5
|
+
|
6
|
+
# Mapper that handles the storage of `commands`.
|
7
|
+
class Command < Mapper
|
8
|
+
|
9
|
+
# Find a command.
|
10
|
+
#
|
11
|
+
# @param [String] id the command's id.
|
12
|
+
#
|
13
|
+
# @return [Hash, nil] the command or nil if is not found.
|
14
|
+
def find(id)
|
15
|
+
if exists?(id)
|
16
|
+
client.hgetall(id)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the attribute of a command.
|
21
|
+
#
|
22
|
+
# @param [String] id the command's id.
|
23
|
+
# @param [String] attribute the command's attribute.
|
24
|
+
#
|
25
|
+
# @return [String, nil] the attribute or nil if is empty.
|
26
|
+
def get(id, attribute)
|
27
|
+
client.hget(id, attribute)
|
28
|
+
end
|
29
|
+
|
30
|
+
# The command exists?
|
31
|
+
#
|
32
|
+
# @param [String] id the command's id.
|
33
|
+
#
|
34
|
+
# @return [true, false]
|
35
|
+
def exists?(id)
|
36
|
+
client.exists(id)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Count all commands.
|
40
|
+
#
|
41
|
+
# @return [Fixnum]
|
42
|
+
def count
|
43
|
+
client.scard('ids')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Retrieve only the specified number of commands, sorted by id.
|
47
|
+
#
|
48
|
+
# @param [Fixnum, String] number the quantity of commands to retrieve.
|
49
|
+
#
|
50
|
+
# @return [Array] all the commands found.
|
51
|
+
def take(number)
|
52
|
+
options = { order: 'DESC', limit: [0, number] }
|
53
|
+
client.sort('ids', options).map { |id| find(id) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Delete the specified commands
|
57
|
+
#
|
58
|
+
# @param [Array] ids the commands' ids.
|
59
|
+
def delete(ids)
|
60
|
+
client.multi do
|
61
|
+
client.del(ids)
|
62
|
+
client.srem('ids', ids)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Delete all commands
|
67
|
+
def delete_all
|
68
|
+
delete client.smembers('ids')
|
69
|
+
end
|
70
|
+
|
71
|
+
# Save the hash as a command.
|
72
|
+
#
|
73
|
+
# If the hash has an {'id'}, the command exists. If not,
|
74
|
+
# a new id is generated.
|
75
|
+
#
|
76
|
+
# @note all the commands has an expiration time of {#ttl} time.
|
77
|
+
#
|
78
|
+
# @return [Hash] the stored attributes.
|
79
|
+
def save(hash)
|
80
|
+
attributes = hash.dup
|
81
|
+
attributes['id'] ||= generate_id
|
82
|
+
|
83
|
+
client.multi do
|
84
|
+
client.hmset(attributes['id'], *attributes.to_a)
|
85
|
+
client.sadd('ids', attributes['id'])
|
86
|
+
|
87
|
+
# just keep the command enough time
|
88
|
+
client.expire(attributes['id'], ttl)
|
89
|
+
end
|
90
|
+
|
91
|
+
attributes
|
92
|
+
end
|
93
|
+
|
94
|
+
# Update one field.
|
95
|
+
#
|
96
|
+
# @param [String] id the command's id.
|
97
|
+
# @param [String] attribute the command's attribute name.
|
98
|
+
# @param [String] value the new value for the attribute.
|
99
|
+
#
|
100
|
+
# @return [Hash] the attributes stored.
|
101
|
+
def update(id, attribute, value)
|
102
|
+
save('id' => id, attribute => value)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Generate a new unique id.
|
106
|
+
def generate_id
|
107
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
108
|
+
end
|
109
|
+
protected :generate_id
|
110
|
+
|
111
|
+
# Default expiration time (defaults to 604800s == 7 days)
|
112
|
+
#
|
113
|
+
# @return [String, Fixnum]
|
114
|
+
def ttl
|
115
|
+
@options[:ttl] || 604800 # 7 days
|
116
|
+
end
|
117
|
+
|
118
|
+
# Publish to a Redis channel.
|
119
|
+
#
|
120
|
+
# @param [String] id the command's id.
|
121
|
+
# @param [String] payload what to publish.
|
122
|
+
def publish(id, payload)
|
123
|
+
client.publish(channel(id), payload)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Subscribe to a Redis channel.
|
127
|
+
#
|
128
|
+
# @see {Redis::Client#subscribe}
|
129
|
+
#
|
130
|
+
# @param [String] id the command's id.
|
131
|
+
def subscribe(id, &block)
|
132
|
+
client.subscribe(channel(id), &block)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Unsubscribe from a Redis channel.
|
136
|
+
#
|
137
|
+
# @see {Redis::Client#subscribe}
|
138
|
+
def unsubscribe(id)
|
139
|
+
client.unsubscribe(channel(id))
|
140
|
+
end
|
141
|
+
|
142
|
+
# Get the channel name based on an ID.
|
143
|
+
#
|
144
|
+
# @param [String] id the command's id.
|
145
|
+
#
|
146
|
+
# @return [String] the channel name.
|
147
|
+
def channel(id)
|
148
|
+
"#{id}:channel"
|
149
|
+
end
|
150
|
+
protected :channel
|
151
|
+
|
152
|
+
# Build the namespace for all the commands' keys.
|
153
|
+
#
|
154
|
+
# @return [String]
|
155
|
+
def build_namespace(key)
|
156
|
+
"#{super(key)}:commands"
|
157
|
+
end
|
158
|
+
protected :build_namespace
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Izanami
|
4
|
+
|
5
|
+
# Inject in a class or module all the {Izanami::Worker} modules.
|
6
|
+
#
|
7
|
+
# @param [Module, Class] target where to inject (include and extend) the modules.
|
8
|
+
def self.Worker(target)
|
9
|
+
target.send :extend, Worker::ClassMethods
|
10
|
+
target.send :include, Worker::InstanceMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
# Basic interface to create {Izanami::Workers}
|
14
|
+
module Worker
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# @see {#initialize} and {#defer}
|
18
|
+
def defer(*args)
|
19
|
+
new(*args).defer
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see {#initialize} and {#call}
|
23
|
+
def call(*args)
|
24
|
+
new(*args).call
|
25
|
+
end
|
26
|
+
|
27
|
+
# @see {.call}
|
28
|
+
def run(*args)
|
29
|
+
new(*args).run
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module InstanceMethods
|
34
|
+
|
35
|
+
# Executes a {#call} method in a different process, forking the
|
36
|
+
# current one.
|
37
|
+
#
|
38
|
+
# @note the process forked is detached.
|
39
|
+
#
|
40
|
+
# @return [String, Fixnum] the new process pid.
|
41
|
+
def defer
|
42
|
+
pid = Process.fork { call }
|
43
|
+
Process.detach(pid)
|
44
|
+
|
45
|
+
pid
|
46
|
+
end
|
47
|
+
|
48
|
+
# Main worker action. Must be defined in the concrete class.
|
49
|
+
def call
|
50
|
+
raise 'The method #call should be defined in the worker class'
|
51
|
+
end
|
52
|
+
|
53
|
+
# @see {#call}
|
54
|
+
def run
|
55
|
+
call
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'izanami/channel'
|
3
|
+
require 'izanami/worker'
|
4
|
+
require 'izanami/mappers/command'
|
5
|
+
|
6
|
+
module Izanami
|
7
|
+
module Workers
|
8
|
+
# Worker that executes the {cap} commands and stores the results.
|
9
|
+
#
|
10
|
+
# @param [Hash] redis_options Options for the redis mapper.
|
11
|
+
# @param [Hash] command the command to execute.
|
12
|
+
class Command < Struct.new(:redis_options, :command)
|
13
|
+
Izanami::Worker self
|
14
|
+
|
15
|
+
attr_writer :shell
|
16
|
+
|
17
|
+
# Executes the cap command with the correct environment and handles the
|
18
|
+
# IO streams.
|
19
|
+
def call
|
20
|
+
Open3.popen2e(shell_env, cmd, shell_options) do |stdin, stdout, thread|
|
21
|
+
handle_streams(stdin, stdout, thread)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# ENV vars for the {cap} command.
|
26
|
+
#
|
27
|
+
# This worker is executed inside a ruby process initiated by {bundler}.
|
28
|
+
# Because the {cap} process is executed in a sandbox with its own {bundler}
|
29
|
+
# ecosystem, we need to remove all the {bundler} environment variables to have
|
30
|
+
# a clean execution.
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
def shell_env
|
34
|
+
bundler_keys = ENV.select { |var, _| var.to_s.match(/\ABUNDLE/) }.keys
|
35
|
+
bundler_keys.reduce({}) do |hash, (k,_)|
|
36
|
+
hash[k] = nil
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Options for the new process.
|
42
|
+
#
|
43
|
+
# Here we define the directory for the {cap} process, which is the
|
44
|
+
# ENV['IZANAMI_SANDBOX'] configuration variable.
|
45
|
+
#
|
46
|
+
# @return [Hash]
|
47
|
+
def shell_options
|
48
|
+
{ chdir: ENV['IZANAMI_SANDBOX'] }
|
49
|
+
end
|
50
|
+
|
51
|
+
# The {cap} command
|
52
|
+
#
|
53
|
+
# @return [String]
|
54
|
+
def cmd
|
55
|
+
"bundle exec cap #{command['tasks']}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Handle the process IO streams.
|
59
|
+
#
|
60
|
+
# It writes the process output to a {Izanami::Channel} and
|
61
|
+
# saves the status of the command.
|
62
|
+
#
|
63
|
+
# @see {Open3.popen2e}
|
64
|
+
def handle_streams(stdin, stdout, thread)
|
65
|
+
mapper = Izanami::Mappers::Command.new(redis_options)
|
66
|
+
channel = Izanami::Channel.new(mapper)
|
67
|
+
|
68
|
+
channel.write(command['id']) do |input|
|
69
|
+
while (line = stdout.gets)
|
70
|
+
input << line.chomp
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
status = thread.value.success? ? 'success' : 'fail'
|
75
|
+
mapper.update(command['id'], 'status', status)
|
76
|
+
|
77
|
+
stdin.close
|
78
|
+
stdout.close
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'izanami/worker'
|
3
|
+
|
4
|
+
module Izanami
|
5
|
+
module Workers
|
6
|
+
|
7
|
+
# Utility worker that watch the IZANAMI_SANDBOX repository
|
8
|
+
# and updates the content each IZANAMI_WATCHDOG_SLEEP_TIME time
|
9
|
+
# via {git}.
|
10
|
+
class Watchdog
|
11
|
+
Izanami::Worker self
|
12
|
+
|
13
|
+
# Main action. Updates the repo in a loop,
|
14
|
+
# waiting the defined time.
|
15
|
+
def call
|
16
|
+
loop do
|
17
|
+
update_repo
|
18
|
+
sleep wait_time
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Update the repo. Prints to $stdout the results of the {git} command.
|
23
|
+
def update_repo
|
24
|
+
Open3.popen2e(cmd, shell_options) do |stdin, stdout, thread|
|
25
|
+
while (line = stdout.gets)
|
26
|
+
$stdout.puts line.chomp
|
27
|
+
end
|
28
|
+
|
29
|
+
status = thread.value.success? ? 'success' : 'fail'
|
30
|
+
$stdout.puts "\"#{cmd}\" => #{status}"
|
31
|
+
|
32
|
+
stdin.close
|
33
|
+
stdout.close
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Options for the new process.
|
38
|
+
#
|
39
|
+
# Here we define the directory for the {cap} process, which is the
|
40
|
+
# ENV['IZANAMI_SANDBOX'] configuration variable.
|
41
|
+
#
|
42
|
+
# @return [Hash]
|
43
|
+
def shell_options
|
44
|
+
{ chdir: ENV['IZANAMI_SANDBOX'] }
|
45
|
+
end
|
46
|
+
|
47
|
+
# {git} command for the updating.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def cmd
|
51
|
+
'git pull'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Time to wait between each update
|
55
|
+
#
|
56
|
+
# Can be configured with the ENV['IZANAMI_WATCHDOG_SLEEP_TIME'] variable.
|
57
|
+
#
|
58
|
+
# @return [Fixnum] the time (default to 300 seconds).
|
59
|
+
def wait_time
|
60
|
+
time = ENV['IZANAMI_WATCHDOG_SLEEP_TIME'] || 300
|
61
|
+
time.to_i
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/izanami.rb
ADDED
data/spec/helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
ENV['RACK_ENV'] ||= 'test'
|
2
|
+
ENV['IZANAMI_USER'] = 'spec'
|
3
|
+
ENV['IZANAMI_PASSWORD'] = 'spec'
|
4
|
+
ENV['IZANAMI_SANDBOX'] = File.expand_path('..', __FILE__) + '/sandbox'
|
5
|
+
ENV['IZANAMI_REDIS_URL'] ||= 'redis://127.0.0.1:6379/0'
|
6
|
+
|
7
|
+
require 'bundler'
|
8
|
+
Bundler.setup(:default, :test)
|
9
|
+
|
10
|
+
require 'rspec'
|
11
|
+
require 'rspec/autorun'
|
12
|
+
require 'rack/test'
|