masamune 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +54 -0
  4. data/Rakefile +15 -0
  5. data/bin/masamune-elastic-mapreduce +4 -0
  6. data/bin/masamune-hive +4 -0
  7. data/bin/masamune-psql +4 -0
  8. data/bin/masamune-shell +4 -0
  9. data/lib/masamune.rb +56 -0
  10. data/lib/masamune/accumulate.rb +60 -0
  11. data/lib/masamune/actions.rb +38 -0
  12. data/lib/masamune/actions/data_flow.rb +131 -0
  13. data/lib/masamune/actions/date_parse.rb +75 -0
  14. data/lib/masamune/actions/elastic_mapreduce.rb +68 -0
  15. data/lib/masamune/actions/execute.rb +52 -0
  16. data/lib/masamune/actions/filesystem.rb +37 -0
  17. data/lib/masamune/actions/hadoop_filesystem.rb +40 -0
  18. data/lib/masamune/actions/hadoop_streaming.rb +41 -0
  19. data/lib/masamune/actions/hive.rb +74 -0
  20. data/lib/masamune/actions/postgres.rb +76 -0
  21. data/lib/masamune/actions/postgres_admin.rb +34 -0
  22. data/lib/masamune/actions/s3cmd.rb +44 -0
  23. data/lib/masamune/actions/transform.rb +89 -0
  24. data/lib/masamune/after_initialize_callbacks.rb +55 -0
  25. data/lib/masamune/cached_filesystem.rb +110 -0
  26. data/lib/masamune/commands.rb +37 -0
  27. data/lib/masamune/commands/elastic_mapreduce.rb +119 -0
  28. data/lib/masamune/commands/hadoop_filesystem.rb +57 -0
  29. data/lib/masamune/commands/hadoop_streaming.rb +116 -0
  30. data/lib/masamune/commands/hive.rb +178 -0
  31. data/lib/masamune/commands/interactive.rb +37 -0
  32. data/lib/masamune/commands/postgres.rb +128 -0
  33. data/lib/masamune/commands/postgres_admin.rb +72 -0
  34. data/lib/masamune/commands/postgres_common.rb +33 -0
  35. data/lib/masamune/commands/retry_with_backoff.rb +60 -0
  36. data/lib/masamune/commands/s3cmd.rb +70 -0
  37. data/lib/masamune/commands/shell.rb +202 -0
  38. data/lib/masamune/configuration.rb +195 -0
  39. data/lib/masamune/data_plan.rb +31 -0
  40. data/lib/masamune/data_plan/builder.rb +66 -0
  41. data/lib/masamune/data_plan/elem.rb +190 -0
  42. data/lib/masamune/data_plan/engine.rb +162 -0
  43. data/lib/masamune/data_plan/rule.rb +292 -0
  44. data/lib/masamune/data_plan/set.rb +176 -0
  45. data/lib/masamune/environment.rb +164 -0
  46. data/lib/masamune/filesystem.rb +567 -0
  47. data/lib/masamune/has_environment.rb +40 -0
  48. data/lib/masamune/helpers.rb +27 -0
  49. data/lib/masamune/helpers/postgres.rb +84 -0
  50. data/lib/masamune/io.rb +33 -0
  51. data/lib/masamune/last_element.rb +53 -0
  52. data/lib/masamune/method_logger.rb +41 -0
  53. data/lib/masamune/multi_io.rb +39 -0
  54. data/lib/masamune/schema.rb +36 -0
  55. data/lib/masamune/schema/catalog.rb +233 -0
  56. data/lib/masamune/schema/column.rb +527 -0
  57. data/lib/masamune/schema/dimension.rb +133 -0
  58. data/lib/masamune/schema/event.rb +121 -0
  59. data/lib/masamune/schema/fact.rb +133 -0
  60. data/lib/masamune/schema/map.rb +265 -0
  61. data/lib/masamune/schema/row.rb +133 -0
  62. data/lib/masamune/schema/store.rb +115 -0
  63. data/lib/masamune/schema/table.rb +308 -0
  64. data/lib/masamune/schema/table_reference.rb +76 -0
  65. data/lib/masamune/spec_helper.rb +23 -0
  66. data/lib/masamune/string_format.rb +34 -0
  67. data/lib/masamune/tasks/elastic_mapreduce_thor.rb +60 -0
  68. data/lib/masamune/tasks/hive_thor.rb +55 -0
  69. data/lib/masamune/tasks/postgres_thor.rb +47 -0
  70. data/lib/masamune/tasks/shell_thor.rb +63 -0
  71. data/lib/masamune/template.rb +77 -0
  72. data/lib/masamune/thor.rb +186 -0
  73. data/lib/masamune/thor_loader.rb +38 -0
  74. data/lib/masamune/topological_hash.rb +34 -0
  75. data/lib/masamune/transform.rb +47 -0
  76. data/lib/masamune/transform/bulk_upsert.psql.erb +64 -0
  77. data/lib/masamune/transform/bulk_upsert.rb +52 -0
  78. data/lib/masamune/transform/consolidate_dimension.rb +54 -0
  79. data/lib/masamune/transform/deduplicate_dimension.psql.erb +52 -0
  80. data/lib/masamune/transform/deduplicate_dimension.rb +53 -0
  81. data/lib/masamune/transform/define_event_view.hql.erb +51 -0
  82. data/lib/masamune/transform/define_event_view.rb +60 -0
  83. data/lib/masamune/transform/define_index.psql.erb +34 -0
  84. data/lib/masamune/transform/define_schema.hql.erb +23 -0
  85. data/lib/masamune/transform/define_schema.psql.erb +79 -0
  86. data/lib/masamune/transform/define_schema.rb +56 -0
  87. data/lib/masamune/transform/define_table.hql.erb +34 -0
  88. data/lib/masamune/transform/define_table.psql.erb +95 -0
  89. data/lib/masamune/transform/define_table.rb +40 -0
  90. data/lib/masamune/transform/define_unique.psql.erb +30 -0
  91. data/lib/masamune/transform/insert_reference_values.psql.erb +43 -0
  92. data/lib/masamune/transform/insert_reference_values.rb +64 -0
  93. data/lib/masamune/transform/load_dimension.rb +47 -0
  94. data/lib/masamune/transform/load_fact.rb +45 -0
  95. data/lib/masamune/transform/operator.rb +96 -0
  96. data/lib/masamune/transform/relabel_dimension.psql.erb +76 -0
  97. data/lib/masamune/transform/relabel_dimension.rb +39 -0
  98. data/lib/masamune/transform/rollup_fact.psql.erb +79 -0
  99. data/lib/masamune/transform/rollup_fact.rb +149 -0
  100. data/lib/masamune/transform/snapshot_dimension.psql.erb +75 -0
  101. data/lib/masamune/transform/snapshot_dimension.rb +74 -0
  102. data/lib/masamune/transform/stage_dimension.psql.erb +39 -0
  103. data/lib/masamune/transform/stage_dimension.rb +83 -0
  104. data/lib/masamune/transform/stage_fact.psql.erb +80 -0
  105. data/lib/masamune/transform/stage_fact.rb +111 -0
  106. data/lib/masamune/version.rb +25 -0
  107. data/spec/fixtures/aggregate.sql.erb +25 -0
  108. data/spec/fixtures/comment.sql.erb +27 -0
  109. data/spec/fixtures/invalid.sql.erb +23 -0
  110. data/spec/fixtures/relative.sql.erb +23 -0
  111. data/spec/fixtures/simple.sql.erb +28 -0
  112. data/spec/fixtures/whitespace.sql.erb +30 -0
  113. data/spec/masamune/actions/elastic_mapreduce_spec.rb +108 -0
  114. data/spec/masamune/actions/execute_spec.rb +50 -0
  115. data/spec/masamune/actions/hadoop_filesystem_spec.rb +44 -0
  116. data/spec/masamune/actions/hadoop_streaming_spec.rb +74 -0
  117. data/spec/masamune/actions/hive_spec.rb +117 -0
  118. data/spec/masamune/actions/postgres_admin_spec.rb +58 -0
  119. data/spec/masamune/actions/postgres_spec.rb +134 -0
  120. data/spec/masamune/actions/s3cmd_spec.rb +44 -0
  121. data/spec/masamune/actions/transform_spec.rb +144 -0
  122. data/spec/masamune/after_initialization_callbacks_spec.rb +61 -0
  123. data/spec/masamune/cached_filesystem_spec.rb +167 -0
  124. data/spec/masamune/commands/hadoop_filesystem_spec.rb +50 -0
  125. data/spec/masamune/commands/hadoop_streaming_spec.rb +106 -0
  126. data/spec/masamune/commands/hive_spec.rb +117 -0
  127. data/spec/masamune/commands/postgres_admin_spec.rb +69 -0
  128. data/spec/masamune/commands/postgres_spec.rb +100 -0
  129. data/spec/masamune/commands/retry_with_backoff_spec.rb +116 -0
  130. data/spec/masamune/commands/s3cmd_spec.rb +50 -0
  131. data/spec/masamune/commands/shell_spec.rb +101 -0
  132. data/spec/masamune/configuration_spec.rb +102 -0
  133. data/spec/masamune/data_plan/builder_spec.rb +91 -0
  134. data/spec/masamune/data_plan/elem_spec.rb +102 -0
  135. data/spec/masamune/data_plan/engine_spec.rb +356 -0
  136. data/spec/masamune/data_plan/rule_spec.rb +407 -0
  137. data/spec/masamune/data_plan/set_spec.rb +517 -0
  138. data/spec/masamune/environment_spec.rb +65 -0
  139. data/spec/masamune/filesystem_spec.rb +1421 -0
  140. data/spec/masamune/helpers/postgres_spec.rb +95 -0
  141. data/spec/masamune/schema/catalog_spec.rb +613 -0
  142. data/spec/masamune/schema/column_spec.rb +696 -0
  143. data/spec/masamune/schema/dimension_spec.rb +137 -0
  144. data/spec/masamune/schema/event_spec.rb +75 -0
  145. data/spec/masamune/schema/fact_spec.rb +117 -0
  146. data/spec/masamune/schema/map_spec.rb +593 -0
  147. data/spec/masamune/schema/row_spec.rb +28 -0
  148. data/spec/masamune/schema/store_spec.rb +49 -0
  149. data/spec/masamune/schema/table_spec.rb +395 -0
  150. data/spec/masamune/string_format_spec.rb +60 -0
  151. data/spec/masamune/tasks/elastic_mapreduce_thor_spec.rb +57 -0
  152. data/spec/masamune/tasks/hive_thor_spec.rb +75 -0
  153. data/spec/masamune/tasks/postgres_thor_spec.rb +42 -0
  154. data/spec/masamune/tasks/shell_thor_spec.rb +51 -0
  155. data/spec/masamune/template_spec.rb +77 -0
  156. data/spec/masamune/thor_spec.rb +238 -0
  157. data/spec/masamune/transform/bulk_upsert.dimension_spec.rb +200 -0
  158. data/spec/masamune/transform/consolidate_dimension_spec.rb +62 -0
  159. data/spec/masamune/transform/deduplicate_dimension_spec.rb +84 -0
  160. data/spec/masamune/transform/define_event_view_spec.rb +84 -0
  161. data/spec/masamune/transform/define_schema_spec.rb +83 -0
  162. data/spec/masamune/transform/define_table.dimension_spec.rb +306 -0
  163. data/spec/masamune/transform/define_table.fact_spec.rb +291 -0
  164. data/spec/masamune/transform/define_table.table_spec.rb +525 -0
  165. data/spec/masamune/transform/insert_reference_values.dimension_spec.rb +111 -0
  166. data/spec/masamune/transform/insert_reference_values.fact_spec.rb +149 -0
  167. data/spec/masamune/transform/load_dimension_spec.rb +76 -0
  168. data/spec/masamune/transform/load_fact_spec.rb +89 -0
  169. data/spec/masamune/transform/relabel_dimension_spec.rb +102 -0
  170. data/spec/masamune/transform/rollup_fact_spec.rb +333 -0
  171. data/spec/masamune/transform/snapshot_dimension_spec.rb +103 -0
  172. data/spec/masamune/transform/stage_dimension_spec.rb +115 -0
  173. data/spec/masamune/transform/stage_fact_spec.rb +204 -0
  174. data/spec/masamune_spec.rb +32 -0
  175. data/spec/spec_helper.rb +41 -0
  176. data/spec/support/masamune/example_group.rb +36 -0
  177. data/spec/support/masamune/mock_command.rb +99 -0
  178. data/spec/support/masamune/mock_delegate.rb +51 -0
  179. data/spec/support/masamune/mock_filesystem.rb +96 -0
  180. data/spec/support/masamune/thor_mute.rb +35 -0
  181. data/spec/support/rspec/example/action_example_group.rb +34 -0
  182. data/spec/support/rspec/example/task_example_group.rb +80 -0
  183. data/spec/support/rspec/example/transform_example_group.rb +36 -0
  184. data/spec/support/shared_examples/postgres_common_examples.rb +53 -0
  185. metadata +462 -0
@@ -0,0 +1,72 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'delegate'
24
+
25
+ module Masamune::Commands
26
+ class PostgresAdmin < SimpleDelegator
27
+ include Masamune::Commands::PostgresCommon
28
+
29
+ DEFAULT_ATTRIBUTES =
30
+ {
31
+ :create_db_path => 'createdb',
32
+ :drop_db_path => 'dropdb',
33
+ :options => [],
34
+ :hostname => 'localhost',
35
+ :username => 'postgres',
36
+ :pgpass_file => nil,
37
+ :action => nil,
38
+ :database => nil
39
+ }
40
+
41
+ def initialize(delegate, attrs = {})
42
+ super delegate
43
+ DEFAULT_ATTRIBUTES.merge(configuration.postgres).merge(configuration.postgres_admin).merge(attrs).each do |name, value|
44
+ instance_variable_set("@#{name}", value)
45
+ end
46
+ end
47
+
48
+ def command_args
49
+ raise ArgumentError, ':database must be given' unless @database
50
+ args = []
51
+ args << command_path
52
+ args << '--host=%s' % @hostname if @hostname
53
+ args << '--username=%s' % @username if @username
54
+ args << '--no-password'
55
+ args << @database
56
+ args.flatten.compact
57
+ end
58
+
59
+ private
60
+
61
+ def command_path
62
+ case @action
63
+ when :create
64
+ [@create_db_path]
65
+ when :drop
66
+ [@drop_db_path, '--if-exists']
67
+ else
68
+ raise ArgumentError, ':action must be :create or :drop'
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ module Masamune::Commands
24
+ module PostgresCommon
25
+ def command_env
26
+ { 'PGOPTIONS'=> '--client-min-messages=warning' }.tap do |env|
27
+ if @pgpass_file && File.readable?(@pgpass_file)
28
+ env['PGPASSFILE'] = @pgpass_file
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,60 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'delegate'
24
+
25
+ module Masamune::Commands
26
+ class RetryWithBackoff < SimpleDelegator
27
+ MAX_RETRY_EXIT_STATUS = 8
28
+
29
+ def initialize(delegate, attrs = {})
30
+ super delegate
31
+ @delegate = delegate
32
+ @retries = attrs.fetch(:retries, configuration.retries)
33
+ @backoff = attrs.fetch(:backoff, configuration.backoff)
34
+ @retry_count = 0
35
+ end
36
+
37
+ def around_execute(&block)
38
+ begin
39
+ status = if @delegate.respond_to?(:around_execute)
40
+ @delegate.around_execute(&block)
41
+ else
42
+ block.call
43
+ end
44
+ raise "exited with code: #{status.exitstatus}" unless status.success?
45
+ status
46
+ rescue => e
47
+ logger.error(e.to_s)
48
+ sleep @backoff
49
+ @retry_count += 1
50
+ unless @retry_count > @retries
51
+ logger.debug("retrying (#{@retry_count}/#{@retries})")
52
+ retry
53
+ else
54
+ logger.debug("max retries (#{@retries}) attempted, bailing")
55
+ OpenStruct.new(:success? => false, :exitstatus => MAX_RETRY_EXIT_STATUS)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,70 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'delegate'
24
+
25
+ module Masamune::Commands
26
+ class S3Cmd < SimpleDelegator
27
+ DEFAULT_ATTRIBUTES =
28
+ {
29
+ :path => 's3cmd',
30
+ :options => [],
31
+ :extra => [],
32
+ :block => nil
33
+ }
34
+
35
+ def initialize(delegate, attrs = {})
36
+ super delegate
37
+ DEFAULT_ATTRIBUTES.merge(configuration.s3cmd).merge(attrs).each do |name, value|
38
+ instance_variable_set("@#{name}", value)
39
+ end
40
+ end
41
+
42
+ def command_args
43
+ args = []
44
+ args << @path
45
+ args << @options.map(&:to_a)
46
+ args << @extra
47
+ args.flatten
48
+ end
49
+
50
+ def handle_stdout(line, line_no)
51
+ @block.call(line) if @block
52
+ end
53
+
54
+ module ClassMethods
55
+ def s3n(file, options = {})
56
+ file.dup.tap do |out|
57
+ out.sub!(%r{\As3://}, 's3n://')
58
+ out.sub!(%r{/?\z}, '/') if options[:dir]
59
+ end
60
+ end
61
+
62
+ def s3b(file, options = {})
63
+ file.dup.tap do |out|
64
+ out.sub!(%r{\As3n://}, 's3://')
65
+ out.sub!(%r{/?\z}, '/') if options[:dir]
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,202 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'open3'
24
+ require 'readline'
25
+ require 'ostruct'
26
+ require 'delegate'
27
+
28
+ module Masamune::Commands
29
+ class Shell < SimpleDelegator
30
+ SIGINT_EXIT_STATUS = 130
31
+ PIPE_TIMEOUT = 10
32
+
33
+ attr_accessor :safe, :fail_fast
34
+
35
+ def initialize(delegate, opts = {})
36
+ super delegate
37
+ @delegate = delegate
38
+ self.safe = opts.fetch(:safe, false)
39
+ self.fail_fast = opts.fetch(:fail_fast, true)
40
+ @stdout_line_no = 0
41
+ @stderr_line_no = 0
42
+ end
43
+
44
+ def replace
45
+ logger.debug('replace: ' + command_args.join(' '))
46
+ before_execute
47
+ around_execute do
48
+ pid = Process.fork
49
+ if pid
50
+ STDIN.close; STDOUT.close; STDERR.close
51
+ Process.waitpid(pid)
52
+ exit
53
+ else
54
+ exec(command_env, *command_args)
55
+ end
56
+ end
57
+ end
58
+
59
+ def before_execute
60
+ logger.debug(command_args)
61
+
62
+ if configuration.verbose
63
+ trace(command_args)
64
+ end
65
+
66
+ if @delegate.respond_to?(:before_execute)
67
+ @delegate.before_execute
68
+ end
69
+ end
70
+
71
+ def after_execute
72
+ if @delegate.respond_to?(:after_execute)
73
+ @delegate.after_execute
74
+ end
75
+ end
76
+
77
+ def around_execute(&block)
78
+ if configuration.no_op && !safe
79
+ return OpenStruct.new(:success? => true)
80
+ end
81
+
82
+ if @delegate.respond_to?(:around_execute)
83
+ @delegate.around_execute(&block)
84
+ else
85
+ block.call
86
+ end
87
+ end
88
+
89
+ def command_env
90
+ (@delegate.respond_to?(:command_env) ? @delegate.command_env : {}).merge('TZ' => 'UTC')
91
+ end
92
+
93
+ def command_args
94
+ if @delegate.respond_to?(:command_args)
95
+ @delegate.command_args
96
+ else
97
+ raise 'no command_args'
98
+ end
99
+ end
100
+
101
+ def command_bin
102
+ command_args.first
103
+ end
104
+
105
+ def execute
106
+ status = OpenStruct.new(:success? => false, :exitstatus => 1)
107
+
108
+ before_execute
109
+ status = around_execute do
110
+ execute_block
111
+ end
112
+ after_execute
113
+
114
+ unless status.success?
115
+ handle_failure(exit_code(status))
116
+ end
117
+ status
118
+ rescue Interrupt
119
+ handle_failure(SIGINT_EXIT_STATUS)
120
+ rescue SystemExit
121
+ handle_failure(exit_code(status))
122
+ end
123
+
124
+ def execute_block
125
+ Open3.popen3(command_env, *command_args) do |p_stdin, p_stdout, p_stderr, t_stdin|
126
+ p_stdin.wait_writable(PIPE_TIMEOUT) or raise "IO stdin not ready for write in #{PIPE_TIMEOUT}"
127
+
128
+ Thread.new {
129
+ if @delegate.respond_to?(:stdin)
130
+ @delegate.stdin.rewind
131
+ while line = @delegate.stdin.gets
132
+ trace(line.chomp)
133
+ p_stdin.puts line
134
+ p_stdin.flush
135
+ end
136
+ else
137
+ while !p_stdin.closed? do
138
+ input = Readline.readline('', true).strip
139
+ p_stdin.puts input
140
+ p_stdin.flush
141
+ end
142
+ end
143
+ p_stdin.close unless p_stdin.closed?
144
+ }
145
+
146
+ t_stderr = Thread.new {
147
+ while !p_stderr.eof? do
148
+ handle_stderr(p_stderr)
149
+ end
150
+ p_stderr.close unless p_stderr.closed?
151
+ }
152
+
153
+ t_stdout = Thread.new {
154
+ while !p_stdout.eof? do
155
+ handle_stdout(p_stdout)
156
+ end
157
+ p_stdout.close unless p_stdout.closed?
158
+ }
159
+
160
+ [t_stderr, t_stdout, t_stdin].compact.each { |t| t.join }
161
+ logger.debug(t_stdin.value)
162
+ t_stdin.value
163
+ end
164
+ end
165
+
166
+ def handle_stdout(io)
167
+ line = io.gets.chomp
168
+ return unless line
169
+ if @delegate.respond_to?(:handle_stdout)
170
+ @delegate.handle_stdout(line, @stdout_line_no)
171
+ else
172
+ logger.debug(line)
173
+ end
174
+ @stdout_line_no += 1
175
+ end
176
+
177
+ def handle_stderr(io)
178
+ line = io.gets.chomp
179
+ return unless line
180
+ if @delegate.respond_to?(:handle_stderr)
181
+ @delegate.handle_stderr(line, @stderr_line_no)
182
+ else
183
+ logger.debug(line)
184
+ end
185
+ @stderr_line_no += 1
186
+ end
187
+
188
+ def handle_failure(status)
189
+ if @delegate.respond_to?(:handle_failure)
190
+ @delegate.handle_failure(status)
191
+ end
192
+ raise "fail_fast: #{command_args.join(' ')}" if fail_fast
193
+ end
194
+
195
+ private
196
+
197
+ def exit_code(status, code = 1)
198
+ return code unless status
199
+ status.exitstatus
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,195 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'delegate'
24
+ require 'yaml'
25
+ require 'tilt/erb'
26
+ require 'pp'
27
+
28
+ require 'active_support/core_ext/hash'
29
+
30
+ class Masamune::Configuration
31
+ extend Forwardable
32
+ include Masamune::HasEnvironment
33
+
34
+ attr_accessor :quiet
35
+ attr_accessor :verbose
36
+ attr_accessor :debug
37
+ attr_accessor :no_op
38
+ attr_accessor :dry_run
39
+ attr_accessor :lock
40
+ attr_accessor :retries
41
+ attr_accessor :backoff
42
+ attr_accessor :params
43
+
44
+ COMMANDS = %w(hive hadoop_streaming hadoop_filesystem elastic_mapreduce s3cmd postgres postgres_admin)
45
+ COMMANDS.each do |command|
46
+ attr_accessor command
47
+ define_method(command) do
48
+ instance_variable_get("@#{command}").symbolize_keys!
49
+ end
50
+ end
51
+
52
+ def initialize(environment)
53
+ self.environment = environment
54
+ self.quiet = false
55
+ self.verbose = false
56
+ self.debug = false
57
+ self.no_op = false
58
+ self.dry_run = false
59
+ self.lock = nil
60
+ self.retries = 3
61
+ self.backoff = 5
62
+ self.params = HashWithIndifferentAccess.new
63
+
64
+ @templates = Hash.new { |h,k| h[k] = {} }
65
+
66
+ COMMANDS.each do |command|
67
+ instance_variable_set("@#{command}", {})
68
+ end
69
+ end
70
+
71
+ def load(file)
72
+ @load_once ||= begin
73
+ load_yaml_erb_file(file).each_pair do |command, value|
74
+ if COMMANDS.include?(command)
75
+ send("#{command}=", value)
76
+ elsif command == 'paths'
77
+ load_paths(value)
78
+ elsif command == 'params'
79
+ raise ArgumentError, 'params section must only contain key value pairs' unless value.is_a?(Hash)
80
+ self.params.merge! value
81
+ end
82
+ end
83
+ logger.debug("Loaded configuration #{file}")
84
+ load_catalog(configuration.postgres.fetch(:schema_files, []) + configuration.hive.fetch(:schema_files, []))
85
+ true
86
+ end
87
+ end
88
+
89
+ def load_catalog(paths = [])
90
+ paths.each do |path|
91
+ filesystem.glob_sort(path, order: :basename).each do |file|
92
+ configuration.with_quiet do
93
+ catalog.load(file)
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def to_s
100
+ io = StringIO.new
101
+ rep = {"path" => filesystem.paths}
102
+ COMMANDS.each do |command|
103
+ rep[command] = send(command)
104
+ end
105
+ PP.pp(rep, io)
106
+ io.string
107
+ end
108
+
109
+ def debug=(debug)
110
+ @debug = debug
111
+ environment.reload_logger!
112
+ end
113
+
114
+ def bind_template(section, template, input_args = {})
115
+ free_command = load_template(section, template)[:command].split(/\s+/)
116
+ [].tap do |bind_command|
117
+ free_command.each do |free_expr|
118
+ if free_expr =~ /(%.*)/
119
+ free_param = $1
120
+ bind_command << free_expr.gsub!(free_param, bind_param(section, template, free_param, input_args))
121
+ elsif param = free_expr
122
+ bind_command << param
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def as_options
129
+ opts = []
130
+ opts << '--quiet' if quiet
131
+ opts << '--verbose' if verbose
132
+ opts << '--debug' if debug
133
+ opts << '--no_op' if no_op
134
+ opts << '--dry_run' if dry_run
135
+ opts
136
+ end
137
+
138
+ def_delegators :filesystem, :add_path, :get_path
139
+
140
+ def with_quiet(&block)
141
+ prev_quiet, self.quiet = quiet, true
142
+ yield
143
+ ensure
144
+ self.quiet = prev_quiet
145
+ end
146
+
147
+ def load_yaml_erb_file(file)
148
+ t = ERB.new(File.read(file))
149
+ t.filename = file
150
+ YAML.load(t.result(binding))
151
+ end
152
+
153
+ class << self
154
+ def default_config_file=(config_file)
155
+ @default_config_file = config_file
156
+ end
157
+
158
+ def default_config_file
159
+ @default_config_file ||= File.join(File.expand_path('../../../', __FILE__), 'config', 'masamune.yml.erb')
160
+ end
161
+ end
162
+
163
+ def default_config_file
164
+ self.class.default_config_file
165
+ end
166
+
167
+ private
168
+
169
+ def load_template(section, template_name)
170
+ @templates[section][template_name] ||= begin
171
+ raise ArgumentError, "no configuration section #{section}" unless COMMANDS.include?(section.to_s)
172
+ raise ArgumentError, 'no template_name' unless template_name
173
+ templates = send(section).fetch(:templates, {}).symbolize_keys!
174
+ template = templates[template_name.to_sym] or raise ArgumentError, "no template for #{template_name}"
175
+ template.symbolize_keys!
176
+ template.has_key?(:command) or raise ArgumentError, "no command for template #{template_name}"
177
+ template[:default] ||= {}
178
+ template[:default] = Hash[ template[:default].collect { |key,val| [key.to_sym, val.to_s] } ]
179
+ template
180
+ end
181
+ end
182
+
183
+ def bind_param(section, template, free_param, input_args = {})
184
+ default = load_template(section, template).fetch(:default, {})
185
+ param = free_param[/(?<=%).*/].to_sym
186
+ default.merge(input_args.symbolize_keys || {})[param] or raise ArgumentError, "no param for #{free_param}"
187
+ end
188
+
189
+ def load_paths(paths)
190
+ paths.each do |value|
191
+ symbol, path, options = *value.to_a.flatten
192
+ add_path(symbol, path, options)
193
+ end
194
+ end
195
+ end