store_agent 1.0.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +5 -0
  6. data/LICENSE +202 -0
  7. data/README.md +433 -0
  8. data/Rakefile +2 -0
  9. data/lib/store_agent.rb +31 -0
  10. data/lib/store_agent/config.rb +152 -0
  11. data/lib/store_agent/data_encoder.rb +32 -0
  12. data/lib/store_agent/data_encoder/gzip_encoder.rb +50 -0
  13. data/lib/store_agent/data_encoder/openssl_aes_256_cbc_encoder.rb +65 -0
  14. data/lib/store_agent/exceptions.rb +87 -0
  15. data/lib/store_agent/node.rb +26 -0
  16. data/lib/store_agent/node/attachment.rb +93 -0
  17. data/lib/store_agent/node/attachment/metadata.rb +120 -0
  18. data/lib/store_agent/node/attachment/permission.rb +121 -0
  19. data/lib/store_agent/node/object.rb +233 -0
  20. data/lib/store_agent/node/object/directory_object.rb +264 -0
  21. data/lib/store_agent/node/object/file_object.rb +197 -0
  22. data/lib/store_agent/node/object/virtual_object.rb +25 -0
  23. data/lib/store_agent/node/prepend_module/locker.rb +125 -0
  24. data/lib/store_agent/node/prepend_module/path_validator.rb +138 -0
  25. data/lib/store_agent/node/prepend_module/permission_checker.rb +96 -0
  26. data/lib/store_agent/user.rb +111 -0
  27. data/lib/store_agent/validator.rb +60 -0
  28. data/lib/store_agent/version.rb +19 -0
  29. data/lib/store_agent/version_manager.rb +101 -0
  30. data/lib/store_agent/version_manager/ruby_git.rb +100 -0
  31. data/lib/store_agent/version_manager/rugged_git.rb +133 -0
  32. data/lib/store_agent/workspace.rb +88 -0
  33. data/spec/spec_helper.rb +47 -0
  34. data/spec/store_agent/data_encoder/encoder_shared_context.rb +74 -0
  35. data/spec/store_agent/data_encoder/gzip_encoder_spec.rb +41 -0
  36. data/spec/store_agent/data_encoder/openssl_aes_256_cbc_encoder_spec.rb +42 -0
  37. data/spec/store_agent/data_encoder_spec.rb +78 -0
  38. data/spec/store_agent/node/directory_object_spec.rb +563 -0
  39. data/spec/store_agent/node/file_object_spec.rb +379 -0
  40. data/spec/store_agent/node/locker_spec.rb +191 -0
  41. data/spec/store_agent/node/metadata_spec.rb +339 -0
  42. data/spec/store_agent/node/object_spec.rb +73 -0
  43. data/spec/store_agent/node/path_validator_spec.rb +121 -0
  44. data/spec/store_agent/node/permission_spec.rb +232 -0
  45. data/spec/store_agent/user_spec.rb +127 -0
  46. data/spec/store_agent/version_manager/git_shared_context.rb +286 -0
  47. data/spec/store_agent/version_manager/ruby_git_spec.rb +32 -0
  48. data/spec/store_agent/version_manager/rugged_git_spec.rb +32 -0
  49. data/spec/store_agent/workspace_spec.rb +107 -0
  50. data/spec/store_agent_spec.rb +53 -0
  51. data/store_agent.gemspec +33 -0
  52. metadata +252 -0
@@ -0,0 +1,60 @@
1
+ #--
2
+ # Copyright 2015 realglobe, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #++
16
+
17
+ module StoreAgent
18
+ # バリデーションに使用するメソッドを定義したモジュール
19
+ module Validator
20
+ # 文字列またはシンボルでないとエラー
21
+ def validates_to_be_string_or_symbol!(value)
22
+ case
23
+ when value.nil?, value == "", value == :""
24
+ raise ArgumentError, "#{value} is empty string or symbol"
25
+ when !value.is_a?(String) && !value.is_a?(Symbol)
26
+ raise ArgumentError, "#{value} is not string or symbol"
27
+ else
28
+ true
29
+ end
30
+ end
31
+
32
+ # 文字列中に '/' を含むとエラー
33
+ def validates_to_be_excluded_slash!(value)
34
+ if value.to_s.include?("/")
35
+ raise ArgumentError, "#{value} includes '/'"
36
+ end
37
+ end
38
+
39
+ # スーパーユーザーのIDと一致している場合エラー
40
+ def validates_to_be_not_superuser_identifier!(value)
41
+ if value.to_s == StoreAgent.config.superuser_identifier
42
+ raise ArgumentError, "#{value} is reserved for superuser"
43
+ end
44
+ end
45
+
46
+ # ゲストユーザーのIDと一致している場合エラー
47
+ def validates_to_be_not_guest_identifier!(value)
48
+ if value.to_s == StoreAgent.config.guest_identifier
49
+ raise ArgumentError, "#{value} is reserved for guest"
50
+ end
51
+ end
52
+
53
+ # アクセサが nil を返す場合エラー
54
+ def validates_to_be_not_nil_value!(accessor_method_name)
55
+ if send(accessor_method_name).nil?
56
+ raise ArgumentError, "#{accessor_method_name} is nil"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ #--
2
+ # Copyright 2015 realglobe, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #++
16
+
17
+ module StoreAgent
18
+ VERSION = "1.0.0"
19
+ end
@@ -0,0 +1,101 @@
1
+ #--
2
+ # Copyright 2015 realglobe, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #++
16
+
17
+ require "store_agent/version_manager/rugged_git"
18
+ require "store_agent/version_manager/ruby_git"
19
+
20
+ module StoreAgent
21
+ # バージョン管理に使用するクラスの雛形。<br>
22
+ # デフォルトではこのクラスが使用されるが、その場合はバージョン管理を行わない。
23
+ class VersionManager
24
+ attr_reader :workspace
25
+
26
+ # バージョン管理システムが使用するため予約されているファイル名<br>
27
+ # 例えば git の場合は、.git .keep など
28
+ def self.reserved_filenames
29
+ []
30
+ end
31
+
32
+ def initialize(workspace: nil) # :nodoc:
33
+ @workspace = workspace
34
+ end
35
+
36
+ # :call-seq:
37
+ # init
38
+ #
39
+ # バージョン管理対象のリポジトリを初期化する
40
+ def init(*params, &block)
41
+ call_block(params, &block)
42
+ end
43
+
44
+ # :call-seq:
45
+ # add(*paths)
46
+ #
47
+ # 引数で渡されたパスをバージョン管理対象に追加する
48
+ def add(*params, &block)
49
+ call_block(params, &block)
50
+ end
51
+
52
+ # :call-seq:
53
+ # remove(*paths, directory: false)
54
+ #
55
+ # 引数で渡されたパスをバージョン管理対象から除外する<br>
56
+ # パスがディレクトリの場合には、directory に true が渡される
57
+ def remove(*params, &block)
58
+ call_block(params, &block)
59
+ end
60
+
61
+ # :call-seq:
62
+ # transaction(messsage, &block)
63
+ #
64
+ # 引数でコミットメッセージとブロックを受け取り、トランザクション処理で実行する<br>
65
+ # ブロックの実行に成功した場合には受け取ったメッセージで変更をコミットする。<br>
66
+ # 処理に失敗した場合は、ブロックの実行前の状態に戻す。
67
+ def transaction(*params, &block)
68
+ call_block(params, &block)
69
+ end
70
+
71
+ # :call-seq:
72
+ # read(path: "", revision: nil)
73
+ #
74
+ # 指定されたパスの指定リビジョン時の中身を返す
75
+ def read(*params, &block)
76
+ call_block(params, &block)
77
+ end
78
+
79
+ # :call-seq:
80
+ # revisions(path)
81
+ #
82
+ # 引数で渡されたパスのリビジョン一覧を返す
83
+ def revisions(*params, &block)
84
+ call_block(params, &block)
85
+ end
86
+
87
+ private
88
+
89
+ def relative_path(path)
90
+ path[(workspace.namespace_dirname.length + 1)..-1]
91
+ end
92
+
93
+ def call_block(*, &block)
94
+ FileUtils.cd(workspace.namespace_dirname) do
95
+ if block
96
+ yield
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,100 @@
1
+ #--
2
+ # Copyright 2015 realglobe, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #++
16
+
17
+ module StoreAgent
18
+ class VersionManager
19
+ # ruby-git(https://github.com/schacon/ruby-git) を使用してバージョン管理を行うクラス<br>
20
+ # StoreAgent::VersionManager を継承しているので、詳細はそちらを参照
21
+ class RubyGit < VersionManager
22
+ # :enddoc:
23
+ def self.reserved_filenames
24
+ %w(.git .keep)
25
+ end
26
+
27
+ def init
28
+ super do
29
+ Git.init
30
+ end
31
+ end
32
+
33
+ def add(*paths)
34
+ super do
35
+ FileUtils.touch(paths)
36
+ repository.add(paths, force: true)
37
+ end
38
+ end
39
+
40
+ def remove(*paths, **_)
41
+ super do
42
+ repository.remove(paths, recursive: true)
43
+ end
44
+ end
45
+
46
+ def transaction(message)
47
+ if @transaction
48
+ yield
49
+ else
50
+ begin
51
+ @transaction = true
52
+ super do
53
+ yield
54
+ end
55
+ repository.commit(message)
56
+ rescue => e
57
+ repository.reset_hard
58
+ raise e
59
+ ensure
60
+ @transaction = false
61
+ end
62
+ end
63
+ end
64
+
65
+ def read(path: "", revision: nil)
66
+ if path.end_with?("/")
67
+ read_directory(path: path, revision: revision)
68
+ else
69
+ read_file(path: path, revision: revision)
70
+ end
71
+ end
72
+
73
+ def revisions(path = ".")
74
+ logs(path).map(&:objectish)
75
+ end
76
+
77
+ private
78
+
79
+ def repository
80
+ @repository ||= Git.open(workspace.namespace_dirname)
81
+ end
82
+
83
+ def logs(path)
84
+ repository.log.path(path)
85
+ end
86
+
87
+ def read_directory(path: ".", revision: "HEAD")
88
+ repository.gtree("#{revision}:#{relative_path(path)}").children.keys
89
+ rescue Git::GitExecuteError
90
+ raise StoreAgent::InvalidRevisionError.new(path: path, revision: revision)
91
+ end
92
+
93
+ def read_file(path: ".", revision: "HEAD")
94
+ repository.gblob("#{revision}:#{relative_path(path)}").contents
95
+ rescue Git::GitExecuteError
96
+ raise StoreAgent::InvalidRevisionError.new(path: path, revision: revision)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,133 @@
1
+ #--
2
+ # Copyright 2015 realglobe, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #++
16
+
17
+ module StoreAgent
18
+ class VersionManager
19
+ # rugged(https://github.com/libgit2/rugged) を使用してバージョン管理を行うクラス<br>
20
+ # StoreAgent::VersionManager を継承しているので、詳細はそちらを参照
21
+ class RuggedGit < VersionManager
22
+ # :enddoc:
23
+ def self.reserved_filenames
24
+ %w(.git .keep)
25
+ end
26
+
27
+ def init
28
+ super do
29
+ Rugged::Repository.init_at(".")
30
+ end
31
+ end
32
+
33
+ def add(*paths)
34
+ super do
35
+ FileUtils.touch(paths)
36
+ paths.flatten.each do |path|
37
+ oid = Rugged::Blob.from_workdir(repository, relative_path(path))
38
+ repository.index.add(relative_path(path))
39
+ end
40
+ end
41
+ end
42
+
43
+ def remove(*paths, directory: false)
44
+ super do
45
+ paths.flatten.each do |path|
46
+ if directory
47
+ repository.index.remove_dir(relative_path(path))
48
+ else
49
+ repository.index.remove(relative_path(path))
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def transaction(message)
56
+ if @transaction
57
+ yield
58
+ else
59
+ begin
60
+ @transaction = true
61
+ super do
62
+ yield
63
+ end
64
+ commit(message)
65
+ rescue => e
66
+ repository.reset("HEAD", :hard)
67
+ raise e
68
+ ensure
69
+ @transaction = false
70
+ end
71
+ end
72
+ end
73
+
74
+ def read(path: "", revision: nil)
75
+ if path.end_with?("/")
76
+ read_directory(path: path, revision: revision)
77
+ else
78
+ read_file(path: path, revision: revision)
79
+ end
80
+ end
81
+
82
+ def revisions(path = "*")
83
+ logs(path).map(&:oid)
84
+ end
85
+
86
+ private
87
+
88
+ def repository
89
+ @repository ||= Rugged::Repository.new(workspace.namespace_dirname)
90
+ end
91
+
92
+ def walker
93
+ @walker ||= Rugged::Walker.new(repository)
94
+ end
95
+
96
+ def logs(path)
97
+ walker.sorting(Rugged::SORT_DATE)
98
+ walker.push(repository.head.target)
99
+ walker.select do |commit|
100
+ commit.parents.size == 1 && commit.diff(paths: [path]).size > 0
101
+ end
102
+ end
103
+
104
+ def lookup_path(path: "", revision: nil)
105
+ paths = relative_path(path).split("/")
106
+ paths.inject(repository.lookup(revision).tree) do |tree, path|
107
+ repository.lookup(tree.find{|t| t[:name] == path}[:oid])
108
+ end
109
+ rescue Rugged::InvalidError
110
+ raise StoreAgent::InvalidRevisionError.new(path: path, revision: revision)
111
+ end
112
+
113
+ def read_directory(path: "", revision: nil)
114
+ lookup_path(path: path, revision: revision).map{|t| t[:name]}
115
+ end
116
+
117
+ def read_file(path: "", revision: nil)
118
+ lookup_path(path: path, revision: revision).content
119
+ end
120
+
121
+ def commit(message)
122
+ options = {
123
+ tree: repository.index.write_tree(repository),
124
+ message: message,
125
+ parents: repository.empty? ? [] : [ repository.head.target ].compact,
126
+ update_ref: "HEAD"
127
+ }
128
+ Rugged::Commit.create(repository, options)
129
+ repository.index.write
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,88 @@
1
+ #--
2
+ # Copyright 2015 realglobe, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #++
16
+
17
+ module StoreAgent
18
+ # ワークスペース
19
+ class Workspace
20
+ extend Forwardable
21
+ include StoreAgent::Validator
22
+
23
+ attr_reader :current_user, :namespace, :version_manager
24
+ def_delegators :root, *%w(find_object directory file exists?)
25
+
26
+ def initialize(current_user: nil, namespace: nil) # :nodoc:
27
+ @current_user = current_user
28
+ @namespace = namespace
29
+ validates_to_be_not_nil_value!(:current_user)
30
+ validates_to_be_string_or_symbol!(@namespace)
31
+ validates_to_be_excluded_slash!(@namespace)
32
+ @version_manager = StoreAgent.config.version_manager.new(workspace: self)
33
+ end
34
+
35
+ # ワークスペースを新規作成する
36
+ def create
37
+ if exists?
38
+ raise InvalidPathError, "workspace #{@namespace} is already exists"
39
+ end
40
+ FileUtils.mkdir_p(namespace_dirname)
41
+ @version_manager.init
42
+ root.create
43
+ end
44
+
45
+ # ワークスペースを削除する
46
+ def delete
47
+ if !exists?
48
+ raise InvalidPathError, "workspace #{@namespace} not found"
49
+ end
50
+ FileUtils.remove_dir(namespace_dirname)
51
+ end
52
+
53
+ # ワークスペースのファイルツリーの最上位ノード
54
+ def root
55
+ @root ||= StoreAgent::Node::DirectoryObject.new(workspace: self, path: "/")
56
+ end
57
+
58
+ # ワークスペースの絶対パス
59
+ def namespace_dirname
60
+ File.absolute_path("#{StoreAgent.config.storage_root}/#{@namespace}")
61
+ end
62
+
63
+ # ストレージとして使用する領域の絶対パス
64
+ def storage_dirname
65
+ File.absolute_path("#{namespace_dirname}/#{StoreAgent.config.storage_dirname}")
66
+ end
67
+
68
+ # メタデータの保存に使用する領域の絶対パス
69
+ def metadata_dirname
70
+ File.absolute_path("#{namespace_dirname}/#{StoreAgent.config.metadata_dirname}")
71
+ end
72
+
73
+ # 権限情報の保存に使用する領域の絶対パス
74
+ def permission_dirname
75
+ File.absolute_path("#{namespace_dirname}/#{StoreAgent.config.permission_dirname}")
76
+ end
77
+
78
+ # 全ワークスペース名の一覧を配列で返す
79
+ def self.name_list
80
+ if !File.exists?(StoreAgent.config.storage_root)
81
+ FileUtils.mkdir(StoreAgent.config.storage_root)
82
+ end
83
+ FileUtils.cd(StoreAgent.config.storage_root) do
84
+ return Dir.glob("*", File::FNM_DOTMATCH) - StoreAgent.reserved_filenames
85
+ end
86
+ end
87
+ end
88
+ end