kurosawa 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fff5741b6786fceddee8b3934e4f370848797c46
4
- data.tar.gz: d925216514517c42099bdf1570b030ec88b05554
3
+ metadata.gz: 14e1940ade23b020c48dd0ea923424bf56fafae2
4
+ data.tar.gz: 325c412026e1c1efde785a888b8e70254f7ba59b
5
5
  SHA512:
6
- metadata.gz: 292c41fe1c4c7d513e440b2c48b95f7750e0d3036d618c6d6d7496bad8d3ada4d3b8cecef48f9bdfde71afeebb724f964c6ac9524aa90f7c3e51ec79c8eacae2
7
- data.tar.gz: c6394aa4aaac02b6421bd5099b5b9effb523882ec1924beafd33375e5ffa44fd55efbf1755512dff2d9c5671795417347169be7736fa99f9da8834f546bb29ce
6
+ metadata.gz: 0d4dd6523104e8d90f51c5cdae0998ca417de04e7b81eb6328be73dc4309145f50a17bdff2cedc291afa2dd4e60ae8e5aecef1fb3ff09ffe9af73e797969f527
7
+ data.tar.gz: ac744719d7b169a2f8811712c3d7f72c3c56583fa1590ef9c30e2adb56b55703866c58f99fe4b422b1a5c0ac62cef31f6973ebb19d7cdd097f00ac97e82a1d37
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
- # Kurosawa
1
+ # kurosawa.rb
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/kurosawa`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ![Kurosawa Ruby](https://github.com/astrobunny/kurosawa.rb/raw/master/docs/images/kurosawa-ruby.jpg)
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ A RESTful JSON-based database for eventually-consistent filesystem backends. Uses the REST path to determine object hierarchy.
6
+
7
+ # THIS DATABASE IS STILL IN ALPHA
6
8
 
7
9
  ## Installation
8
10
 
@@ -22,7 +24,54 @@ Or install it yourself as:
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ ### On your local environment
28
+
29
+ Run the server
30
+
31
+ ```
32
+ KUROSAWA_FILESYSTEM=file://fs/db bundle exec kurosawa
33
+ ```
34
+
35
+ ### With a docker container
36
+
37
+ ```
38
+ docker run -ti -p4567:4567 astrobunny/kurosawa.rb
39
+ ```
40
+
41
+ ### Try it out!
42
+
43
+ Send it REST commands! (Here I am using resty)
44
+
45
+ ```
46
+ $ GET /
47
+ null
48
+ $ PUT / '{"a": 7, "b": {"e":[100,200,300]} }'
49
+ {"a":"7","b":{"e":["300","200","100"]}}
50
+ $ GET /
51
+ {"a":"7","b":{"e":["300","200","100"]}}
52
+ $ GET /a/b
53
+ null
54
+ $ GET /a
55
+ "7"
56
+ $ GET /b
57
+ {"e":["300","200","100"]}
58
+ $ GET /b/e
59
+ ["300","200","100"]
60
+ $ PATCH / '{"c": "Idols"}'
61
+ {"a":"7","b":{"e":["300","200","100"]},"c":"Idols"}
62
+ $ GET /c
63
+ "Idols"
64
+ $ DELETE /b/e
65
+ null
66
+ $ GET /
67
+ {"a":"7","b":{},"c":"Idols"}
68
+ $ PUT /c '{}'
69
+ {}
70
+ $ PUT /c '{"5": "a"}'
71
+ {"5":"a"}
72
+
73
+ ```
74
+
26
75
 
27
76
  ## Development
28
77
 
@@ -32,5 +81,5 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
81
 
33
82
  ## Contributing
34
83
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/kurosawa.
84
+ Bug reports and pull requests are welcome on GitHub at https://github.com/astrobunny/kurosawa.rb.
36
85
 
data/exe/kurosawa ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require "kurosawa/database"
3
+
4
+ Kurosawa::Database.run!
data/lib/kurosawa.rb CHANGED
@@ -1,5 +1,3 @@
1
1
  require "kurosawa/version"
2
2
 
3
- module Kurosawa
4
- # Your code goes here...
5
- end
3
+ require "kurosawa/connection"
@@ -0,0 +1,4 @@
1
+ module Kurosawa
2
+ class Connection
3
+ end
4
+ end
@@ -0,0 +1,205 @@
1
+ require "sinatra/base"
2
+ require "sinatra/reloader"
3
+ require "json"
4
+
5
+ # filesystems
6
+ require "kurosawa/filesystems"
7
+
8
+ module Kurosawa
9
+ class Database < ::Sinatra::Base
10
+
11
+ set :bind, '0.0.0.0'
12
+ configure :development do
13
+ register Sinatra::Reloader
14
+ end
15
+
16
+ filesystem = ENV["KUROSAWA_FILESYSTEM"] || "file://~/.kurosawa"
17
+
18
+ set :filesystem, Filesystem.instantiate(filesystem)
19
+
20
+ def sanitize_path(path)
21
+ match = /^\/[a-z0-9\-_\/]*$/i.match(URI.unescape(path))
22
+ if match
23
+ match.to_s.sub(/\/+$/, "")
24
+ else
25
+ p URI.unescape(path)
26
+ halt 400
27
+ end
28
+ end
29
+
30
+ def sanitize_body(body)
31
+ begin
32
+ JSON.parse(body)
33
+ rescue => e
34
+ puts e
35
+ halt 400
36
+ end
37
+ end
38
+
39
+ def random_key
40
+ SecureRandom.hex(4)
41
+ end
42
+
43
+ def get_property(fs, path)
44
+ property_entries = fs.ls("#{path}/$")
45
+ if property_entries.length == 0
46
+ nil
47
+ elsif property_entries.length == 1
48
+ JSON.parse(fs.get(property_entries[0]), symbolize_names: true)
49
+ else
50
+ # conflict
51
+ raise "TODO get_property handle conflict"
52
+ end
53
+ end
54
+
55
+ def set_property(fs, path, type:)
56
+ puts "set property: #{path}"
57
+ property_entries = fs.ls("#{path}/$")
58
+ p property_entries
59
+ if property_entries.length > 0
60
+ property_entries.each do |x|
61
+ fs.del(x)
62
+ end
63
+ end
64
+ fs.put("#{path}/$#{random_key}", {type: type}.to_json)
65
+ end
66
+
67
+
68
+ def read(fs, path, params)
69
+
70
+ puts "read: path=#{path.inspect}"
71
+ prop = get_property(fs, path)
72
+ if prop == nil
73
+ nil
74
+ elsif prop[:type] == "object"
75
+ res = {}
76
+
77
+ inner_entries = fs.ls("#{path}")
78
+ .select{ |x| /^\/\$/.match(x) == nil }
79
+ .map{ |x| x.sub(/\/[^\/]+$/, "") }
80
+ .map{ |x| match = /(?<first>^\/[^\/]+)/.match(x.sub(/^#{path}/, "")); match[:first] if match }
81
+ .select{ |x| x != nil}
82
+ .uniq
83
+
84
+ puts "read: object: inner: #{fs.ls("#{path}").inspect}"
85
+ puts "read: object: inner: #{inner_entries.inspect}"
86
+
87
+ inner_entries.each do |x|
88
+ res[x[1..-1]] = read(fs, path + x, params)
89
+ end
90
+
91
+ res
92
+
93
+ elsif prop[:type] == "array"
94
+ inner_entries = fs.ls("#{path}")
95
+ .select{ |x| /\/\$/.match(x) == nil }
96
+ .map{ |x| match = /(?<first>^\/[^\/]+)/.match(x.sub(/^#{path}/, "")); match[:first] if match }
97
+ .select{ |x| x != nil}
98
+ .uniq
99
+
100
+ puts "read: array: #{inner_entries.inspect}"
101
+
102
+ inner_entries.map do |x|
103
+ read(fs, path + x, params)
104
+ end
105
+
106
+ else
107
+ inner_entries = fs.ls("#{path}")
108
+ .select{ |x| /\/\$/.match(x) == nil }
109
+
110
+ puts "read: string: #{inner_entries.inspect}"
111
+
112
+ if inner_entries.length == 1
113
+ JSON.parse(fs.get(inner_entries[0]), symbolize_names: true)[:value]
114
+ elsif inner_entries.length == 0
115
+ nil
116
+ else
117
+ {"$conflict": inner_entries.map{|x| JSON.parse(fs.get(x), symbolize_names: true) }}
118
+ #raise "read conflict at #{path} #{inner_entries.inspect}"
119
+ end
120
+ end
121
+ end
122
+
123
+ def write(fs, path, body)
124
+
125
+ puts "write: path=#{path.inspect}"
126
+
127
+ if body.is_a? Hash
128
+ set_property(fs, path, type: "object")
129
+ body.each do |k,v|
130
+ write(fs, "#{path}/#{k}", v)
131
+ end
132
+
133
+ elsif body.is_a? Array
134
+ set_property(fs, path, type: "array")
135
+ body.each do |value|
136
+ write(fs, "#{path}/#{random_key}", value)
137
+ end
138
+
139
+ else
140
+ existing_entries = fs.ls("#{path}")
141
+
142
+ if existing_entries.length > 0
143
+ existing_entries.each do |x|
144
+ fs.del(x)
145
+ end
146
+ end
147
+ set_property(fs, path, type: "string")
148
+ fs.put("#{path}/#{random_key}", {value: body.to_s}.to_json)
149
+ end
150
+ end
151
+
152
+ def delete(fs, path)
153
+ inner_entries = fs.ls("#{path}")
154
+
155
+ puts "delete: #{inner_entries.inspect}"
156
+
157
+ inner_entries.each do |x|
158
+ fs.del(x)
159
+ end
160
+ end
161
+
162
+ get "*" do
163
+ path = sanitize_path(request.path)
164
+ x = read(settings.filesystem, path, params)
165
+ status 404 if x == nil
166
+ x.to_json
167
+ end
168
+
169
+ post "*" do
170
+ path = sanitize_path(request.path)
171
+
172
+ end
173
+
174
+ put "*" do
175
+ path = sanitize_path(request.path)
176
+ delete(settings.filesystem, path)
177
+ write(settings.filesystem, path, sanitize_body(request.body.read))
178
+ read(settings.filesystem, path, params).to_json
179
+ end
180
+
181
+ patch "*" do
182
+ path = sanitize_path(request.path)
183
+ write(settings.filesystem, path, sanitize_body(request.body.read))
184
+ read(settings.filesystem, path, params).to_json
185
+ end
186
+
187
+ delete "*" do
188
+ path = sanitize_path(request.path)
189
+ delete(settings.filesystem, path)
190
+ read(settings.filesystem, path, params).to_json
191
+ end
192
+
193
+ head "*" do
194
+ path = sanitize_path(request.path)
195
+
196
+ end
197
+
198
+ options "*" do
199
+ path = sanitize_path(request.path)
200
+
201
+ end
202
+
203
+
204
+ end
205
+ end
@@ -0,0 +1,29 @@
1
+
2
+ require "kurosawa/filesystems/local"
3
+ require "kurosawa/filesystems/s3"
4
+
5
+ require "uri"
6
+
7
+ module Kurosawa
8
+ class Filesystem
9
+ def self.instantiate(fs)
10
+ if fs.start_with?("file://")
11
+ Kurosawa::Filesystems::Local.new(fs.sub("file://", ""))
12
+ elsif fs.start_with?("s3://") or fs.start_with?("s3http://") or fs.start_with?("s3https://")
13
+
14
+ url = URI.parse(fs);
15
+ tokens = url.userinfo.split(":")
16
+
17
+ Kurosawa::Filesystems::S3.new(
18
+ access_key_id:"#{tokens[0]}",
19
+ secret_access_key:"#{tokens[1]}",
20
+ region:"#{tokens[2]}",
21
+ bucket:url.scheme == "s3" ? url.host : "",
22
+ endpoint:url.scheme == "s3" ? nil : "#{url.scheme.sub("s3","")}://#{url.host}:#{url.port}",
23
+ force_path_style: url.scheme != "s3")
24
+ else
25
+ raise "Unknown filesystem"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+
2
+ module Kurosawa::Filesystems
3
+ class Base
4
+ def ls(prefix)
5
+ []
6
+ end
7
+
8
+ def get(key)
9
+ ""
10
+ end
11
+
12
+ def put(key, value)
13
+ end
14
+
15
+ def del(key)
16
+ end
17
+
18
+ def exists(key)
19
+ false
20
+ end
21
+
22
+ def cleanup!
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ require 'kurosawa/filesystems/base'
2
+ require 'fileutils'
3
+
4
+ module Kurosawa::Filesystems
5
+ class Local < Base
6
+
7
+ def initialize(root_path)
8
+ @root_path = File.expand_path(root_path)
9
+ FileUtils.mkdir_p(@root_path)
10
+ end
11
+
12
+ def root_path
13
+ @root_path
14
+ end
15
+
16
+ def path(key)
17
+ File.join(@root_path, key)
18
+ end
19
+
20
+ def ls(prefix)
21
+ puts "fs: ls #{prefix}"
22
+ (Dir["#{@root_path}/#{prefix}*"] + Dir["#{@root_path}/#{prefix}/**/*"]).
23
+ select{|x| File.file?(x)}.
24
+ map{|x| x.sub(/^#{root_path}/, "").
25
+ gsub(/\/+/, "/")}.
26
+ uniq
27
+
28
+ end
29
+
30
+ def get(key)
31
+ puts "fs: get #{path(key)}"
32
+ File.read(path(key))
33
+ end
34
+
35
+ def put(key, value)
36
+ puts "fs: put #{path(key)}"
37
+ FileUtils.mkdir_p(File.dirname(path(key)))
38
+ File.write(path(key), value)
39
+ end
40
+
41
+ def del(key)
42
+ puts "fs: delete #{path(key)}"
43
+ File.delete(path(key)) if File.file?(path(key))
44
+ end
45
+
46
+ def exists(key)
47
+ puts "fs: exists #{path(key)}"
48
+ File.exists?(path(key)) and File.file?(path(key))
49
+ end
50
+
51
+ def cleanup!
52
+ Dir['#{@root_path}**/*'].
53
+ select { |d| File.directory? d }.
54
+ select { |d| (Dir.entries(d) - %w[ . .. ]).empty? }.
55
+ each { |d| Dir.rmdir d }
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,72 @@
1
+ require 'kurosawa/filesystems/base'
2
+ require 'uri'
3
+ require 'aws-sdk'
4
+
5
+ module Kurosawa::Filesystems
6
+ class S3 < Base
7
+
8
+ def initialize(access_key_id:,secret_access_key:,region:,endpoint:,bucket:,force_path_style:false)
9
+
10
+ options = {
11
+ :access_key_id => URI.encode(access_key_id),
12
+ :secret_access_key => URI.decode(secret_access_key),
13
+ :region => region,
14
+ :force_path_style => force_path_style
15
+ }
16
+ options[:endpoint] = endpoint if endpoint
17
+
18
+ @s3 = Aws::S3::Client.new(options)
19
+ @bucket_name = bucket
20
+ @bucket = Aws::S3::Bucket.new(bucket, client: @s3)
21
+ end
22
+
23
+ def ls(prefix)
24
+ prefix.gsub!(/^\/+/, "")
25
+ puts "s3:ls #{prefix}"
26
+ result = []
27
+ next_marker = nil
28
+ loop do
29
+ options = {
30
+ bucket: @bucket_name,
31
+ marker: next_marker
32
+ }
33
+
34
+ options[:prefix] = prefix if prefix.length != 0
35
+
36
+ resp = @s3.list_objects(options);
37
+ result += resp.contents.map { |x| "/#{x.key}" }
38
+ break if resp.next_marker == nil
39
+ next_marker = resp.next_marker
40
+ end
41
+ result
42
+ end
43
+
44
+ def get(key)
45
+ key.gsub!(/^\/+/, "")
46
+ puts "s3:get #{key}"
47
+ resp = @s3.get_object(bucket: @bucket_name, key: key)
48
+ resp.body.read
49
+ end
50
+
51
+ def put(key, value)
52
+ key.gsub!(/^\/+/, "")
53
+ puts "s3:put #{key}"
54
+ @s3.put_object(bucket: @bucket_name, key: key, body: value)
55
+ end
56
+
57
+ def del(key)
58
+ key.gsub!(/^\/+/, "")
59
+ puts "s3:del #{key}"
60
+ @s3.delete_object(bucket: @bucket_name, key: key)
61
+ end
62
+
63
+ def exists(key)
64
+ key.gsub!(/^\/+/, "")
65
+ puts "s3:exists #{key}"
66
+ @bucket.objects[key].exists?
67
+ end
68
+
69
+ def cleanup!
70
+ end
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  module Kurosawa
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kurosawa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - astrobunny
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-07-17 00:00:00.000000000 Z
11
+ date: 2016-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,25 +52,84 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fakes3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.4'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sinatra-contrib
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: aws-sdk
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 2.4.4
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.4.4
55
111
  description: Tribute to Kurosawa Ruby
56
112
  email:
57
113
  - admin@astrobunny.com
58
- executables: []
114
+ executables:
115
+ - kurosawa
59
116
  extensions: []
60
117
  extra_rdoc_files: []
61
118
  files:
62
- - ".gitignore"
63
- - ".rspec"
64
- - ".travis.yml"
65
119
  - Gemfile
66
120
  - README.md
67
- - Rakefile
68
121
  - bin/console
69
122
  - bin/setup
70
- - kurosawa.gemspec
123
+ - exe/kurosawa
71
124
  - lib/kurosawa.rb
125
+ - lib/kurosawa/connection.rb
126
+ - lib/kurosawa/database.rb
127
+ - lib/kurosawa/filesystems.rb
128
+ - lib/kurosawa/filesystems/base.rb
129
+ - lib/kurosawa/filesystems/local.rb
130
+ - lib/kurosawa/filesystems/s3.rb
72
131
  - lib/kurosawa/version.rb
73
- homepage: https://github.com/astrobunny/kurosawa
132
+ homepage: https://github.com/astrobunny/kurosawa.rb
74
133
  licenses: []
75
134
  metadata:
76
135
  allowed_push_host: https://rubygems.org
@@ -90,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
149
  version: '0'
91
150
  requirements: []
92
151
  rubyforge_project:
93
- rubygems_version: 2.5.1
152
+ rubygems_version: 2.4.8
94
153
  signing_key:
95
154
  specification_version: 4
96
155
  summary: kurosawa.rb
data/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --format documentation
2
- --color
data/.travis.yml DELETED
@@ -1,5 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.3.0
5
- before_install: gem install bundler -v 1.12.4
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
data/kurosawa.gemspec DELETED
@@ -1,32 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'kurosawa/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "kurosawa"
8
- spec.version = Kurosawa::VERSION
9
- spec.authors = ["astrobunny"]
10
- spec.email = ["admin@astrobunny.com"]
11
-
12
- spec.summary = %q{kurosawa.rb}
13
- spec.description = %q{Tribute to Kurosawa Ruby}
14
- spec.homepage = "https://github.com/astrobunny/kurosawa"
15
-
16
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
- # to allow pushing to a single host or delete this section to allow pushing to any host.
18
- if spec.respond_to?(:metadata)
19
- spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
- else
21
- raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
- end
23
-
24
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
- spec.bindir = "exe"
26
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
- spec.require_paths = ["lib"]
28
-
29
- spec.add_development_dependency "bundler", "~> 1.12"
30
- spec.add_development_dependency "rake", "~> 10.0"
31
- spec.add_development_dependency "rspec", "~> 3.0"
32
- end