pantry 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +19 -0
  5. data/Gemfile +15 -0
  6. data/Guardfile +16 -0
  7. data/LICENSE +20 -0
  8. data/README.md +53 -0
  9. data/Rakefile +18 -0
  10. data/Vagrantfile +86 -0
  11. data/bin/pantry +11 -0
  12. data/bin/pantry-client +38 -0
  13. data/bin/pantry-server +33 -0
  14. data/dist/client.yml +79 -0
  15. data/dist/server.yml +56 -0
  16. data/dist/upstart/pantry-client.conf +12 -0
  17. data/dist/upstart/pantry-server.conf +12 -0
  18. data/doc/message_packet.dot +19 -0
  19. data/doc/message_packet.dot.png +0 -0
  20. data/doc/network_topology.dot +42 -0
  21. data/doc/network_topology.dot.png +0 -0
  22. data/lib/celluloid_zmq_patches.rb +16 -0
  23. data/lib/opt_parse_plus.rb +184 -0
  24. data/lib/pantry.rb +197 -0
  25. data/lib/pantry/cli.rb +154 -0
  26. data/lib/pantry/client.rb +131 -0
  27. data/lib/pantry/client_info.rb +34 -0
  28. data/lib/pantry/client_registry.rb +104 -0
  29. data/lib/pantry/command.rb +194 -0
  30. data/lib/pantry/command_handler.rb +53 -0
  31. data/lib/pantry/command_line.rb +115 -0
  32. data/lib/pantry/commands/create_client.rb +30 -0
  33. data/lib/pantry/commands/download_directory.rb +35 -0
  34. data/lib/pantry/commands/echo.rb +32 -0
  35. data/lib/pantry/commands/edit_application.rb +60 -0
  36. data/lib/pantry/commands/register_client.rb +38 -0
  37. data/lib/pantry/commands/status.rb +78 -0
  38. data/lib/pantry/commands/sync_directory.rb +50 -0
  39. data/lib/pantry/commands/update_application.rb +45 -0
  40. data/lib/pantry/commands/upload_file.rb +68 -0
  41. data/lib/pantry/communication.rb +20 -0
  42. data/lib/pantry/communication/client.rb +75 -0
  43. data/lib/pantry/communication/client_filter.rb +117 -0
  44. data/lib/pantry/communication/file_service.rb +125 -0
  45. data/lib/pantry/communication/file_service/file_progress.rb +164 -0
  46. data/lib/pantry/communication/file_service/receive_file.rb +97 -0
  47. data/lib/pantry/communication/file_service/send_file.rb +74 -0
  48. data/lib/pantry/communication/publish_socket.rb +20 -0
  49. data/lib/pantry/communication/reading_socket.rb +89 -0
  50. data/lib/pantry/communication/receive_socket.rb +23 -0
  51. data/lib/pantry/communication/security.rb +44 -0
  52. data/lib/pantry/communication/security/authentication.rb +98 -0
  53. data/lib/pantry/communication/security/curve_key_store.rb +120 -0
  54. data/lib/pantry/communication/security/curve_security.rb +70 -0
  55. data/lib/pantry/communication/security/null_security.rb +32 -0
  56. data/lib/pantry/communication/send_socket.rb +19 -0
  57. data/lib/pantry/communication/serialize_message.rb +84 -0
  58. data/lib/pantry/communication/server.rb +97 -0
  59. data/lib/pantry/communication/subscribe_socket.rb +33 -0
  60. data/lib/pantry/communication/wait_list.rb +45 -0
  61. data/lib/pantry/communication/writing_socket.rb +46 -0
  62. data/lib/pantry/config.rb +182 -0
  63. data/lib/pantry/file_editor.rb +67 -0
  64. data/lib/pantry/logger.rb +78 -0
  65. data/lib/pantry/message.rb +134 -0
  66. data/lib/pantry/multi_command.rb +36 -0
  67. data/lib/pantry/server.rb +132 -0
  68. data/lib/pantry/test/acceptance.rb +83 -0
  69. data/lib/pantry/test/support/fake_fs.rb +31 -0
  70. data/lib/pantry/test/support/matchers.rb +13 -0
  71. data/lib/pantry/test/support/minitest.rb +13 -0
  72. data/lib/pantry/test/support/mock_ui.rb +23 -0
  73. data/lib/pantry/test/unit.rb +13 -0
  74. data/lib/pantry/ui.rb +68 -0
  75. data/lib/pantry/version.rb +3 -0
  76. data/pantry.gemspec +40 -0
  77. data/test/acceptance/cli/error_handling_test.rb +7 -0
  78. data/test/acceptance/cli/execute_command_on_clients_test.rb +32 -0
  79. data/test/acceptance/cli/request_info_from_server_test.rb +44 -0
  80. data/test/acceptance/communication/client_requests_info_from_server_test.rb +28 -0
  81. data/test/acceptance/communication/heartbeat_test.rb +19 -0
  82. data/test/acceptance/communication/pub_sub_communication_test.rb +53 -0
  83. data/test/acceptance/communication/security_test.rb +117 -0
  84. data/test/acceptance/communication/server_requests_info_from_client_test.rb +41 -0
  85. data/test/acceptance/test_helper.rb +25 -0
  86. data/test/fixtures/config.yml +22 -0
  87. data/test/fixtures/empty.yml +2 -0
  88. data/test/fixtures/file_to_upload +3 -0
  89. data/test/root_dir/.gitkeep +0 -0
  90. data/test/unit/cli_test.rb +173 -0
  91. data/test/unit/client_registry_test.rb +61 -0
  92. data/test/unit/client_test.rb +128 -0
  93. data/test/unit/command_handler_test.rb +79 -0
  94. data/test/unit/command_line_test.rb +5 -0
  95. data/test/unit/command_test.rb +206 -0
  96. data/test/unit/commands/create_client_test.rb +25 -0
  97. data/test/unit/commands/download_directory_test.rb +58 -0
  98. data/test/unit/commands/echo_test.rb +22 -0
  99. data/test/unit/commands/edit_application_test.rb +84 -0
  100. data/test/unit/commands/register_client_test.rb +41 -0
  101. data/test/unit/commands/status_test.rb +81 -0
  102. data/test/unit/commands/sync_directory_test.rb +75 -0
  103. data/test/unit/commands/update_application_test.rb +35 -0
  104. data/test/unit/commands/upload_file_test.rb +51 -0
  105. data/test/unit/communication/client_filter_test.rb +262 -0
  106. data/test/unit/communication/client_test.rb +99 -0
  107. data/test/unit/communication/file_service/receive_file_test.rb +214 -0
  108. data/test/unit/communication/file_service/send_file_test.rb +110 -0
  109. data/test/unit/communication/file_service_test.rb +56 -0
  110. data/test/unit/communication/publish_socket_test.rb +19 -0
  111. data/test/unit/communication/reading_socket_test.rb +110 -0
  112. data/test/unit/communication/receive_socket_test.rb +20 -0
  113. data/test/unit/communication/security/authentication_test.rb +97 -0
  114. data/test/unit/communication/security/curve_key_store_test.rb +110 -0
  115. data/test/unit/communication/security/curve_security_test.rb +44 -0
  116. data/test/unit/communication/security/null_security_test.rb +15 -0
  117. data/test/unit/communication/security_test.rb +49 -0
  118. data/test/unit/communication/send_socket_test.rb +19 -0
  119. data/test/unit/communication/serialize_message_test.rb +128 -0
  120. data/test/unit/communication/server_test.rb +106 -0
  121. data/test/unit/communication/subscribe_socket_test.rb +46 -0
  122. data/test/unit/communication/wait_list_test.rb +60 -0
  123. data/test/unit/communication/writing_socket_test.rb +46 -0
  124. data/test/unit/config_test.rb +150 -0
  125. data/test/unit/logger_test.rb +79 -0
  126. data/test/unit/message_test.rb +179 -0
  127. data/test/unit/multi_command_test.rb +45 -0
  128. data/test/unit/opt_parse_plus_test.rb +218 -0
  129. data/test/unit/pantry_test.rb +82 -0
  130. data/test/unit/server_test.rb +166 -0
  131. data/test/unit/test_helper.rb +25 -0
  132. data/test/unit/ui_test.rb +58 -0
  133. metadata +389 -13
@@ -0,0 +1,12 @@
1
+ # /etc/init/pantry-client.conf
2
+ # Upstart script for the Pantry Client
3
+
4
+ description "Pantry Client"
5
+
6
+ start on runlevel [2345]
7
+ stop on runlevel [06]
8
+
9
+ respawn
10
+ respawn limit 5 10
11
+
12
+ exec pantry-client -c /etc/pantry/client.yml
@@ -0,0 +1,12 @@
1
+ # /etc/init/pantry-server.conf
2
+ # Upstart script for the Pantry Server
3
+
4
+ description "Pantry Server"
5
+
6
+ start on runlevel [2345]
7
+ stop on runlevel [06]
8
+
9
+ respawn
10
+ respawn limit 5 10
11
+
12
+ exec pantry-server -c /etc/pantry/server.yml
@@ -0,0 +1,19 @@
1
+ digraph message {
2
+ node [shape=record];
3
+ rankdir = LR;
4
+
5
+ subgraph cluster_zmq {
6
+ label = "ZMQ";
7
+ labeljust = right;
8
+ body [shape=record width=2 label="Stream |<metadata> Metadata | body[0] | body[1] | ..."];
9
+ }
10
+
11
+ subgraph cluster_metadata {
12
+ label = "JSON Metadata";
13
+ labeljust = right;
14
+
15
+ metadata [shape=record width=2 label="<top> Type | Source | Response\nRequired?"];
16
+ }
17
+
18
+ body:metadata -> metadata:top;
19
+ }
@@ -0,0 +1,42 @@
1
+ digraph pantry {
2
+ rankdir = LR;
3
+ color = black;
4
+ splines = ortho;
5
+ node [shape=rectangle];
6
+
7
+ subgraph cluster_server {
8
+ label = "Pantry Server";
9
+ labeljust = left;
10
+ edge [style=dashed];
11
+
12
+ server [label="Server"];
13
+ publish [label="PUB"];
14
+
15
+ server_dealer [label="ROUTER"];
16
+
17
+ server -> publish;
18
+ server -> server_dealer [dir=back];
19
+ }
20
+
21
+ subgraph cluster_client {
22
+ label = "Pantry Client";
23
+ labeljust = right;
24
+ edge [style=dashed];
25
+
26
+ client [label="Client"];
27
+ subscribe [label="SUB"];
28
+ provisioning [label="Chef"];
29
+ shell [label="Shell"];
30
+
31
+ client_dealer [label="DEALER"];
32
+
33
+ client -> provisioning [dir=both];
34
+ client -> shell [dir=both];
35
+
36
+ subscribe -> client;
37
+ client_dealer -> client [dir=back];
38
+ }
39
+
40
+ server_dealer -> client_dealer [dir=back];
41
+ publish -> subscribe;
42
+ }
@@ -0,0 +1,16 @@
1
+ # Monkey Patch Celluloid::ZMQ to allow pass through
2
+ # of direct setsockopt calls, so we can configure sockets
3
+ # as we need to
4
+
5
+ module Celluloid
6
+ module ZMQ
7
+ class Socket
8
+ def set(*args)
9
+ unless ::ZMQ::Util.resultcode_ok? @socket.setsockopt(*args)
10
+ raise IOError, "couldn't set value: #{::ZMQ::Util.error_string}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,184 @@
1
+ require 'optparse'
2
+
3
+ class OptParsePlus
4
+
5
+ attr_reader :options, :summary
6
+
7
+ class OptionsFound < Hash
8
+ attr_accessor :command_found
9
+
10
+ # Let these options be queried by a string key or symbol
11
+ # key equally.
12
+ def [](key)
13
+ super ||
14
+ (key.is_a?(Symbol) && super(key.to_s)) ||
15
+ (key.is_a?(String) && super(key.to_sym))
16
+ end
17
+ end
18
+
19
+ def initialize(parent = nil)
20
+ @parent = parent || self
21
+ @parser = OptionParser.new
22
+ @options = OptionsFound.new
23
+ @commands = {}
24
+ @summary = ""
25
+
26
+ add_default_help
27
+ end
28
+
29
+ def add_options(&block)
30
+ instance_exec(&block)
31
+ end
32
+
33
+ def add_command(command_name, &block)
34
+ command_parser = OptParsePlus.new(self)
35
+
36
+ base_command = base_command_name(command_name.to_s)
37
+ rest = command_name.gsub(base_command, '')
38
+
39
+ command_parser.banner "Usage: #$0 #{base_command} [options]#{rest}"
40
+ command_parser.add_options(&block) if block_given?
41
+ @commands[base_command] = command_parser
42
+ end
43
+
44
+ def option(*arguments)
45
+ argument_name = parse_argument_name(arguments)
46
+ @parser.on(*arguments) do |arg|
47
+ @options[argument_name] = arg
48
+ end
49
+ end
50
+
51
+ def banner(message)
52
+ @parser.banner = message
53
+ end
54
+
55
+ def group(group_name = nil)
56
+ if group_name
57
+ @group = group_name
58
+ else
59
+ @group
60
+ end
61
+ end
62
+
63
+ def description(message)
64
+ @summary = clean_up_white_space(message)
65
+ @parser.separator("")
66
+ @parser.separator(@summary)
67
+ @parser.separator("")
68
+ end
69
+
70
+ def set(key, value)
71
+ @options[key] = value
72
+ end
73
+
74
+ def parse!(command_line)
75
+ @parser.order!(command_line)
76
+ final = @options
77
+
78
+ next_token = command_line.first
79
+
80
+ if command_parser = @commands[next_token]
81
+ command_line.shift
82
+ command_parser.parse!(command_line)
83
+
84
+ final.command_found = next_token
85
+ final.merge!({
86
+ next_token => command_parser.options
87
+ })
88
+ end
89
+
90
+ final
91
+ end
92
+
93
+ def help
94
+ help_parts = []
95
+ help_parts << @parser.to_s
96
+
97
+ if @commands.any?
98
+ help_parts << ["Known Commands", ""]
99
+ command_list = group_and_sort_command_help
100
+
101
+ command_list.each do |cmd_line|
102
+ help_parts << cmd_line
103
+ end
104
+
105
+ help_parts << ""
106
+ end
107
+
108
+ help_parts << ""
109
+ help_parts.flatten.join("\n")
110
+ end
111
+
112
+ protected
113
+
114
+ def clean_up_white_space(message)
115
+ message.split("\n").map(&:strip).join("\n")
116
+ end
117
+
118
+ def add_default_help
119
+ @parser.on_tail('-h', '--help', 'Show this help message') do
120
+ puts help
121
+ @parent.set('help', true)
122
+ end
123
+ end
124
+
125
+ def base_command_name(command_string)
126
+ command_string.split(/\s/).first
127
+ end
128
+
129
+ def parse_argument_name(arguments)
130
+ full_name_arg = arguments.select {|a| a =~ /\A--/ }.first
131
+ full_name_arg.split(/\s/).first.gsub("--", "")
132
+ end
133
+
134
+ def group_and_sort_command_help
135
+ grouped_commands = Hash.new {|hash, key| hash[key] = []}
136
+
137
+ @commands.each do |command_name, parser|
138
+ grouped_commands[parser.group] << [command_name, parser]
139
+ end
140
+
141
+ command_list = []
142
+ sorted_group_names = grouped_commands.keys.sort {|a, b| a.to_s <=> b.to_s }
143
+ sorted_group_names.each do |group_name|
144
+ command_list << build_help_for_command_group(group_name, grouped_commands[group_name])
145
+ end
146
+
147
+ command_list.flatten(1)
148
+ end
149
+
150
+ def build_help_for_command_group(group_name, group_commands)
151
+ command_list = []
152
+
153
+ if group_name
154
+ command_list << nil
155
+ command_list << "#{group_name} commands"
156
+ command_list << nil
157
+ end
158
+
159
+ command_list + generate_short_help_for_commands(group_commands)
160
+ end
161
+
162
+ def generate_short_help_for_commands(group_commands)
163
+ longest_command_length = 0
164
+ group_commands.each do |(command_name, _)|
165
+ longest_command_length = command_name.length if command_name.length > longest_command_length
166
+ end
167
+ # Give ourselves a small buffer between command and summary
168
+ longest_command_length += 3
169
+
170
+ sorted_group_commands = group_commands.sort {|a, b| a[0] <=> b[0]}
171
+ sorted_group_commands.map do |(command_name, parser)|
172
+ sprintf(
173
+ "%-#{longest_command_length}s %s",
174
+ command_name,
175
+ first_line_of_summary(parser)
176
+ )
177
+ end
178
+ end
179
+
180
+ def first_line_of_summary(parser)
181
+ parser.summary.split("\n").first
182
+ end
183
+
184
+ end
@@ -0,0 +1,197 @@
1
+ require 'celluloid'
2
+ require 'celluloid/zmq'
3
+ require 'json'
4
+ require 'logger'
5
+ require 'pathname'
6
+ require 'rubygems'
7
+ require 'safe_yaml/load'
8
+ require 'securerandom'
9
+ require 'socket'
10
+ require 'syslog/logger'
11
+ require 'tempfile'
12
+ require 'open3'
13
+ require 'yaml'
14
+
15
+ require 'opt_parse_plus'
16
+ require 'celluloid_zmq_patches'
17
+
18
+ require 'pantry/version'
19
+ require 'pantry/ui'
20
+ require 'pantry/config'
21
+ require 'pantry/logger'
22
+ require 'pantry/message'
23
+ require 'pantry/command_line'
24
+ require 'pantry/file_editor'
25
+
26
+ require 'pantry/command'
27
+ require 'pantry/multi_command'
28
+ require 'pantry/command_handler'
29
+
30
+ require 'pantry/commands/echo'
31
+ require 'pantry/commands/edit_application'
32
+ require 'pantry/commands/update_application'
33
+ require 'pantry/commands/status'
34
+ require 'pantry/commands/register_client'
35
+ require 'pantry/commands/upload_file'
36
+ require 'pantry/commands/sync_directory'
37
+ require 'pantry/commands/download_directory'
38
+ require 'pantry/commands/create_client'
39
+
40
+ require 'pantry/communication'
41
+ require 'pantry/communication/serialize_message'
42
+ require 'pantry/communication/server'
43
+ require 'pantry/communication/client'
44
+ require 'pantry/communication/client_filter'
45
+ require 'pantry/communication/wait_list'
46
+
47
+ require 'pantry/communication/security/null_security'
48
+ require 'pantry/communication/security/curve_security'
49
+ require 'pantry/communication/security/curve_key_store'
50
+ require 'pantry/communication/security/authentication'
51
+ require 'pantry/communication/security'
52
+
53
+ require 'pantry/communication/reading_socket'
54
+ require 'pantry/communication/writing_socket'
55
+ require 'pantry/communication/publish_socket'
56
+ require 'pantry/communication/subscribe_socket'
57
+ require 'pantry/communication/receive_socket'
58
+ require 'pantry/communication/send_socket'
59
+
60
+ require 'pantry/communication/file_service'
61
+ require 'pantry/communication/file_service/file_progress'
62
+ require 'pantry/communication/file_service/receive_file'
63
+ require 'pantry/communication/file_service/send_file'
64
+
65
+ require 'pantry/client_info'
66
+ require 'pantry/client_registry'
67
+
68
+ require 'pantry/client'
69
+ require 'pantry/server'
70
+ require 'pantry/cli'
71
+
72
+ module Pantry
73
+
74
+ # Default identity of a Server, so as to help differentiate where
75
+ # messages are coming from.
76
+ SERVER_IDENTITY = ""
77
+
78
+ ##
79
+ # Various exceptions Pantry can raise
80
+ ##
81
+
82
+ # A command is expecting a certain option
83
+ class MissingOption < Exception; end
84
+
85
+ # Thrown when trying to add a non Pantry::Command class as a Pantry command
86
+ class InvalidCommandError < Exception; end
87
+
88
+ # Thrown when trying to add a Command that has the same message_type
89
+ # as a Command already registered
90
+ class DuplicateCommandError < Exception; end
91
+
92
+ # The root of all stored Pantry data for this Server/Client
93
+ # Uses Pantry.config.root_dir
94
+ def root(config = Pantry.config)
95
+ Pathname.new(config.root_dir)
96
+ end
97
+
98
+ # Update the process's proc title with the given string
99
+ def set_proc_title(title)
100
+ $0 = title
101
+ end
102
+
103
+ # Calculate the checksum of a file at the given path
104
+ def file_checksum(file_path)
105
+ Digest::SHA256.file(file_path).hexdigest
106
+ end
107
+
108
+ # Register a command object class to be handled only by Clients
109
+ def add_client_command(command_class)
110
+ ensure_proper_command_class(command_class)
111
+ check_for_duplicates(client_commands, command_class)
112
+
113
+ client_commands << command_class
114
+ end
115
+
116
+ # Register a command object class to be handled only by the Server
117
+ def add_server_command(command_class)
118
+ ensure_proper_command_class(command_class)
119
+ check_for_duplicates(server_commands, command_class)
120
+
121
+ server_commands << command_class
122
+ end
123
+
124
+ # Return the list of known Client command classes
125
+ def client_commands
126
+ @client_commands ||= []
127
+ end
128
+
129
+ # Return the list of known Server command classes
130
+ def server_commands
131
+ @server_commands ||= []
132
+ end
133
+
134
+ # Return all known commands
135
+ def all_commands
136
+ [client_commands, server_commands].flatten
137
+ end
138
+
139
+ def ensure_proper_command_class(command_class)
140
+ unless command_class.is_a?(Class)
141
+ raise Pantry::InvalidCommandError.new("Expected a Class, got an #{command_class.class}")
142
+ end
143
+
144
+ unless command_class.ancestors.include?(Pantry::Command)
145
+ raise Pantry::InvalidCommandError.new("Expected a class that's a subclass of Pantry::Command")
146
+ end
147
+ end
148
+
149
+ def check_for_duplicates(command_list, command_class_to_add)
150
+ known_commands = command_list.map(&:message_type)
151
+ if known_commands.include?(command_class_to_add.message_type)
152
+ raise Pantry::DuplicateCommandError.new("Command with type #{command_class_to_add.message_type} already registered")
153
+ end
154
+ end
155
+
156
+ # Find all installed Pantry plugin gems.
157
+ # Plugins are defined as gems who contain a file named "pantry/init.rb".
158
+ def load_plugins
159
+ Gem.find_latest_files("pantry/init.rb").each do |path|
160
+ begin
161
+ gem_path = path.gsub("#{Gem.dir}/gems/", '')
162
+ Pantry.logger.debug("Installing plugin from #{gem_path}")
163
+ require path
164
+ rescue Exception => ex
165
+ Pantry.logger.warn("Unable to load plugin at #{gem_path}, #{ex.message}")
166
+ end
167
+ end
168
+ end
169
+
170
+ extend self
171
+ end
172
+
173
+ Celluloid.exception_handler do |exception|
174
+ Pantry.logger.error("Exception thrown: #{exception.inspect}")
175
+ end
176
+
177
+ SafeYAML::OPTIONS[:default_mode] = :safe
178
+ SafeYAML::OPTIONS[:deserialize_symbols] = false
179
+
180
+ ####################
181
+ # Command Registry #
182
+ ####################
183
+
184
+ ## Client Commands
185
+
186
+ Pantry.add_client_command(Pantry::Commands::Echo)
187
+
188
+ ## Server Commands
189
+
190
+ Pantry.add_server_command(Pantry::Commands::EditApplication)
191
+ Pantry.add_server_command(Pantry::Commands::UpdateApplication)
192
+ Pantry.add_server_command(Pantry::Commands::Status)
193
+ Pantry.add_server_command(Pantry::Commands::RegisterClient)
194
+ Pantry.add_server_command(Pantry::Commands::DownloadDirectory)
195
+ Pantry.add_server_command(Pantry::Commands::CreateClient)
196
+
197
+ Pantry.load_plugins