kuzushi 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README +6 -6
  2. data/Rakefile +1 -0
  3. data/VERSION +1 -1
  4. data/bin/kuzushi +8 -1
  5. data/lib/kuzushi.rb +221 -0
  6. metadata +12 -2
data/README CHANGED
@@ -1,9 +1,9 @@
1
1
 
2
- Kuzushi ( ku ZOO she ) - a technique used by a martial artist or wrestler to
3
- keep control of an opponent by keeping them slightly off balance
2
+ Kuzushi ( ka ZU she ) - a technique used by martial artists to control of an
3
+ opponent by keeping them slightly off balance.
4
4
 
5
- The 'kuzushi' is a companion gem to 'sumo'. Sumo handles AWS/instance/volume
6
- management of a server based on its specificatoion. Kuzushi handles the
7
- management from inside the server by installing packages, setting up drives and
8
- users, writing config files, etc
5
+ The 'kuzushi' gem is a companion to the 'sumo' gem. Sumo handles
6
+ AWS/instance/volume management of a server based on its specificatoion.
7
+ Kuzushi handles the management from inside the server by installing packages,
8
+ setting up drives and users, writing config files, etc
9
9
 
data/Rakefile CHANGED
@@ -10,6 +10,7 @@ Jeweler::Tasks.new do |s|
10
10
  # s.rubyforge_project = "sumo"
11
11
  s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
12
12
  s.executables = %w(kuzushi)
13
+ s.add_dependency "rest-client"
13
14
  s.add_dependency "json"
14
15
  end
15
16
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -2,5 +2,12 @@
2
2
 
3
3
  require File.dirname(__FILE__) + '/../lib/kuzushi'
4
4
 
5
- Kuzushi.new
5
+ usage = "usage: kuzushi start URL"
6
+
7
+ fail(usage) unless ARGV.length == 2
8
+ fail(usage) unless ARGV[0] == "start"
9
+
10
+ url = ARGV[1]
11
+
12
+ Kuzushi.new(url).start
6
13
 
@@ -1,4 +1,225 @@
1
+ require 'rubygems'
1
2
  require 'json'
3
+ require 'restclient'
4
+ require 'ostruct'
2
5
 
3
6
  class Kuzushi
7
+ def initialize(url)
8
+ @base_url = File.dirname(url)
9
+ @name = File.basename(url)
10
+ @config_names = []
11
+ @config = []
12
+ @packages = []
13
+ @tasks = []
14
+ load_config_stack(@name)
15
+ @merge = @config.reverse.inject({}) { |i,c| i.merge(c) }
16
+ process_stack
17
+ end
18
+
19
+ def load_config_stack(name)
20
+ @config_names << name
21
+ @config << JSON.parse(RestClient.get("#{@base_url}/#{name}"))
22
+ if import = @config.last["import"]
23
+ load_config_stack(import)
24
+ end
25
+ end
26
+
27
+ def process_stack
28
+ script get("before")
29
+
30
+ process :packages
31
+ process :local_packages
32
+ process :gems
33
+ process :volumes
34
+ process :raids
35
+ process :mounts
36
+ process :files
37
+ process :users
38
+
39
+ script get("after")
40
+ end
41
+
42
+ ## magic goes here
43
+ def process(type)
44
+ ## if the file takes no args - just call it once
45
+ if method("process_#{type}").arity == 0
46
+ send("process_#{type}")
47
+ else
48
+ ## else call it once per item
49
+ get_array(type).each do |item|
50
+ script item["before"]
51
+ if item.is_a? Hash
52
+ send("process_#{type}", OpenStruct.new(item))
53
+ else
54
+ send("process_#{type}", item)
55
+ end
56
+ script item["after"]
57
+ end
58
+ end
59
+ end
60
+
61
+ def process_packages
62
+ @packages = get_array("packages")
63
+ task "install packages" do
64
+ shell "apt-get update && apt-get upgrade -y && apt-get install -y #{@packages.join(" ")}", "DEBIAN_FRONTEND" => "noninteractive", "DEBIAN_PRIORITY" => "critical"
65
+ end
66
+ end
67
+
68
+ def process_local_packages(p)
69
+ package(p) do |file|
70
+ task "install local package #{p}" do
71
+ shell "dpkg -i #{file}"
72
+ end
73
+ end
74
+ end
75
+
76
+ def process_gems(gem)
77
+ task "install gem #{gem}" do
78
+ shell "gem install #{gem} --no-rdoc --no-ri"
79
+ end
80
+ end
81
+
82
+ def process_volumes(v)
83
+ task "wait for volume #{v.device}" do
84
+ wait_for_volume v.device
85
+ end
86
+ set_scheduler(v)
87
+ check_format(v)
88
+ end
89
+
90
+ def process_raids(r)
91
+ task "assemble raid #{r.device}" do
92
+ shell "mdadm --assemble #{r.device} #{r.drives.join(" ")}"
93
+ end
94
+ set_scheduler r
95
+ check_format r
96
+ add_package "mdadm"
97
+ end
98
+
99
+ def process_mounts(m)
100
+ task "mount #{m.label}" do
101
+ shell "mount -o #{m.options} -L #{m.label} #{m.label}"
102
+ end
103
+ end
104
+
105
+ def process_files(f)
106
+ fetch("/templates/#{f.template}") do |file|
107
+ task "setting up #{f.file}" do
108
+ shell "erb #{f.template} > #{f.file}" ## FIXME
109
+ end
110
+ end
111
+ end
112
+
113
+ def process_users(user)
114
+ (user.authorized_keys || []).each do |key|
115
+ task "add authorized_key for user #{user.name}" do
116
+ shell "su - #{user.name} -c 'mkdir -p .ssh; echo \"#{key}\" >> .ssh/authorized_keys; chmod -R 600 .ssh'"
117
+ end
118
+ end
119
+ end
120
+
121
+ def set_scheduler(v)
122
+ if v.scheduler
123
+ task "set scheduler for #{v.device}" do
124
+ shell "echo #{v.scheduler} > /sys/block/#{File.basename(v.device)}/queue/scheduler"
125
+ end
126
+ end
127
+ end
128
+
129
+ def check_format(v)
130
+ add_package "xfsprogs" if v.format == "xfs"
131
+ end
132
+
133
+ def add_package(p)
134
+ @packages << p unless @packages.include? p
135
+ end
136
+
137
+ def package(p, &block)
138
+ fetch("/packages/#{p}_i386.deb") do |file|
139
+ block.call(file)
140
+ end
141
+ end
142
+
143
+ def inline_script(script)
144
+ tmpfile(script) do |tmp|
145
+ task "run inline script" do
146
+ shell "#{tmp}"
147
+ end
148
+ end
149
+ end
150
+
151
+ def script(script)
152
+ return if script.nil?
153
+ return inline_script(script) if script =~ /^#!/
154
+
155
+ fetch("/scripts/#{script}") do |file|
156
+ task "run script #{script}" do
157
+ shell "#{file}"
158
+ end
159
+ end
160
+ end
161
+
162
+ def tmpfile(content, file = "tmp_#{rand(1_000_000_000)}", &block)
163
+ tmp_dir = "/tmp/kuzushi"
164
+ Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir)
165
+ file = "#{tmp_dir}/#{File.basename(file)}"
166
+ File.open(file,"w") do |f|
167
+ f.write(content)
168
+ f.chmod(700)
169
+ end if content
170
+ block.call(file) if block
171
+ file
172
+ end
173
+
174
+ def fetch(file, &block)
175
+ names = @config_names.clone
176
+ begin
177
+ tmpfile RestClient.get("#{@base_url}/#{names.first}#{file}"), file do |tmp|
178
+ block.call(tmp)
179
+ end
180
+ rescue RestClient::ResourceNotFound
181
+ names.shift
182
+ retry unless names.empty?
183
+ error("file not found: #{file}")
184
+ rescue Object => e
185
+ error("error fetching file: #{names.first}/#{file}", e)
186
+ end
187
+ end
188
+
189
+ def error(message, exception = nil)
190
+ puts "ERROR :#{message}"
191
+ end
192
+
193
+ def get(key)
194
+ @merge[key.to_s]
195
+ end
196
+
197
+ def get_array(key)
198
+ [ get(key) || [] ].flatten
199
+ end
200
+
201
+ def wait_for_volume(vol)
202
+ puts "waiting for volume #{vol}"
203
+ end
204
+
205
+ def start
206
+ puts "----"
207
+ @tasks.each do |t|
208
+ puts "TASK: #{t[:description]}"
209
+ t[:blk].call
210
+ end
211
+ puts "----"
212
+ end
213
+
214
+ def shell(cmd, env = {})
215
+ puts " SHELL: #{cmd}"
216
+ end
217
+
218
+ def task(description, &blk)
219
+ @tasks << { :description => description, :blk => blk }
220
+ end
221
+
222
+ def path
223
+ Dir["**/config.json"]
224
+ end
4
225
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kuzushi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Orion Henry
@@ -9,9 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-18 00:00:00 -05:00
12
+ date: 2010-02-19 00:00:00 -05:00
13
13
  default_executable: kuzushi
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rest-client
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
15
25
  - !ruby/object:Gem::Dependency
16
26
  name: json
17
27
  type: :runtime