pantry 0.0.0 → 0.1.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 (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