[UNSTABLE][WIP] Need to finish moving files into correct dirs, coverage, and adding batch support to more resources. #squashThis
parent
4134a893f6
commit
e0a0f8f11e
|
@ -0,0 +1,25 @@
|
|||
module Resources
|
||||
DEVICE_REGEX = /device_\d*/
|
||||
ACTIONS = [
|
||||
DESTROY = "destroy"
|
||||
]
|
||||
RESOURCES = { # Because I don't trust Kernel.const_get
|
||||
"DeviceConfig" => DeviceConfig,
|
||||
"DiagnosticDump" => DiagnosticDump,
|
||||
"FarmEvent" => FarmEvent,
|
||||
"FarmwareInstallations" => FarmwareInstallations,
|
||||
"Image" => Image,
|
||||
"Log" => Log,
|
||||
"Peripheral" => Peripheral,
|
||||
"PinBinding" => PinBinding,
|
||||
"PlantTemplate" => PlantTemplate,
|
||||
"Point" => Point,
|
||||
"Regimen" => Regimen,
|
||||
"SavedGarden" => SavedGarden,
|
||||
"SensorReading" => SensorReading,
|
||||
"Sensor" => Sensor,
|
||||
"Sequence" => Sequence,
|
||||
"Tool" => Tool,
|
||||
"WebcamFeed" => WebcamFeed,
|
||||
}
|
||||
end # Resources
|
|
@ -0,0 +1,45 @@
|
|||
module Resources
|
||||
class Job < Mutations::Command
|
||||
required do
|
||||
duck :body, methods: [:[], :[]=]
|
||||
duck :resource, duck: [:where, :find_by]
|
||||
integer :resource_id
|
||||
model :device, class: Device
|
||||
string :action, in: ACTIONS
|
||||
string :uuid
|
||||
end
|
||||
|
||||
def validate
|
||||
# Should never trigger in production.
|
||||
never unless RESOURCES.values.include?(resource) # Security critical
|
||||
end
|
||||
|
||||
def execute
|
||||
case action
|
||||
when DESTROY then do_deletion
|
||||
else; never
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def plural_resource
|
||||
@plural_resource ||= resource.name.pluralize
|
||||
end
|
||||
|
||||
def do_deletion
|
||||
model_name = resource.model_name
|
||||
mutation = Kernel.const_get(model_name.name.pluralize)::Destroy
|
||||
mutation.run!(model_name.singular => model, device: device)
|
||||
end
|
||||
|
||||
def model
|
||||
@model ||= device.send(plural_resource.downcase).find(resource_id)
|
||||
end
|
||||
|
||||
# Escape hatch for things that should "never happen".
|
||||
def never
|
||||
raise "PANIC"
|
||||
end
|
||||
end # Job
|
||||
end # Resources
|
|
@ -0,0 +1,65 @@
|
|||
module Resources
|
||||
# Takes a bunch of unsafe, string-y data that came in over AMQP and parses it
|
||||
# into fully formed
|
||||
class PreProcessor < Mutations::Command
|
||||
def self.from_amqp(delivery_info, body)
|
||||
# ["bot", "device_3", "resources_v0", "destroy", "Sequence", "2", "123-456
|
||||
_, device_name, _, action, resource, resource_id, uuid = \
|
||||
delivery_info.routing_key.split(".")
|
||||
run!(device_name: device_name,
|
||||
action: action,
|
||||
resource: resource,
|
||||
resource_id: resource_id,
|
||||
uuid: uuid,
|
||||
body: body.empty? ? "{}" : body)
|
||||
end
|
||||
|
||||
required do
|
||||
string :action, in: ACTIONS # "destroy"
|
||||
string :device_name, matches: DEVICE_REGEX # "device_3"
|
||||
string :resource, in: RESOURCES.keys # "Sequence"
|
||||
end
|
||||
|
||||
optional do
|
||||
integer :resource_id, default: 0 # 2
|
||||
string :body # "{\"json\":true}"
|
||||
string :uuid, default: "NONE" # "0dce-1d-41-1d-e95c3b"
|
||||
end
|
||||
|
||||
def validate
|
||||
maybe_set_device
|
||||
maybe_set_body
|
||||
end
|
||||
|
||||
def execute
|
||||
{
|
||||
action: action,
|
||||
device: @device,
|
||||
body: @body,
|
||||
resource_id: resource_id,
|
||||
resource: RESOURCES.fetch(resource),
|
||||
uuid: uuid,
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fail_body
|
||||
add_error :body, :body, "body must be a JSON object"
|
||||
end
|
||||
|
||||
def maybe_set_body
|
||||
hash = JSON.parse(body)
|
||||
fail_body unless hash.is_a?(Hash)
|
||||
@body = hash
|
||||
rescue JSON::ParserError
|
||||
fail_body
|
||||
end
|
||||
|
||||
def maybe_set_device
|
||||
id = device_name.gsub("device_", "").to_i
|
||||
@device = Device.find_by(id: id)
|
||||
add_error :device, :device, "Can't find device ##{id}" unless @device
|
||||
end
|
||||
end # PreProcessor
|
||||
end # Resources
|
|
@ -0,0 +1,23 @@
|
|||
module Resources
|
||||
class Service
|
||||
def self.process(delivery_info, body)
|
||||
params = PreProcessor.from_amqp(delivery_info, body)
|
||||
Job.run!(params)
|
||||
puts "Transport.amqp_send(message, id, channel)"
|
||||
rescue Mutations::ValidationException => q
|
||||
params ||= {}
|
||||
device_id = params[:device].try(:id)
|
||||
if device_id
|
||||
message = {
|
||||
kind: "rpc_error",
|
||||
args: { label: params.fetch(:uuid) { "NONE" } },
|
||||
body: (q
|
||||
.errors
|
||||
.values
|
||||
.map { |err| { kind: "explanation", args: { message: err.message }} })
|
||||
}.to_json
|
||||
Transport.amqp_send(message, device_id, "from_api")
|
||||
end
|
||||
end
|
||||
end # Service
|
||||
end # Resources
|
153
lib/resources.rb
153
lib/resources.rb
|
@ -1,153 +0,0 @@
|
|||
module Resources
|
||||
DEVICE_REGEX = /device_\d*/
|
||||
ACTIONS = [
|
||||
DESTROY = "destroy"
|
||||
]
|
||||
RESOURCES = { # Because I don't trust Kernel.const_get
|
||||
"DeviceConfig" => DeviceConfig,
|
||||
"DiagnosticDump" => DiagnosticDump,
|
||||
"FarmEvent" => FarmEvent,
|
||||
"FarmwareInstallations" => FarmwareInstallations,
|
||||
"Image" => Image,
|
||||
"Log" => Log,
|
||||
"Peripheral" => Peripheral,
|
||||
"PinBinding" => PinBinding,
|
||||
"PlantTemplate" => PlantTemplate,
|
||||
"Point" => Point,
|
||||
"Regimen" => Regimen,
|
||||
"SavedGarden" => SavedGarden,
|
||||
"SensorReading" => SensorReading,
|
||||
"Sensor" => Sensor,
|
||||
"Sequence" => Sequence,
|
||||
"Tool" => Tool,
|
||||
"WebcamFeed" => WebcamFeed,
|
||||
}
|
||||
|
||||
class Job < Mutations::Command
|
||||
required do
|
||||
duck :body, methods: [:[], :[]=]
|
||||
duck :resource, duck: [:where, :find_by]
|
||||
integer :resource_id
|
||||
model :device, class: Device
|
||||
string :action, in: ACTIONS
|
||||
string :uuid
|
||||
end
|
||||
|
||||
def validate
|
||||
# Security critical. Should never occur in production.
|
||||
never unless RESOURCES.values.include?(resource)
|
||||
end
|
||||
|
||||
def execute
|
||||
case action
|
||||
when DESTROY then do_deletion
|
||||
else; never
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def plural_resource
|
||||
@plural_resource ||= resource.name.pluralize
|
||||
end
|
||||
|
||||
def do_deletion
|
||||
model_name = resource.model_name
|
||||
mutation = Kernel.const_get(model_name.name.pluralize)::Destroy
|
||||
mutation.run!(model_name.singular => model, device: device)
|
||||
end
|
||||
|
||||
def model
|
||||
@model ||= device.send(plural_resource.downcase).find(resource_id)
|
||||
end
|
||||
|
||||
# Escape hatch for things that should "never happen".
|
||||
def never
|
||||
raise "PANIC"
|
||||
end
|
||||
end # Job
|
||||
|
||||
class Service
|
||||
def self.process(delivery_info, body)
|
||||
params = PreProcessor.from_amqp(delivery_info, body)
|
||||
Job.run!(params)
|
||||
puts "Transport.amqp_send(message, id, channel)"
|
||||
rescue Mutations::ValidationException => q
|
||||
params ||= {}
|
||||
device_id = params[:device].try(:id)
|
||||
if device_id
|
||||
message = {
|
||||
kind: "rpc_error",
|
||||
args: { label: params.fetch(:uuid) { "NONE" } },
|
||||
body: (q
|
||||
.errors
|
||||
.values
|
||||
.map { |err| { kind: "explanation", args: { message: err.message }} })
|
||||
}.to_json
|
||||
Transport.amqp_send(message, device_id, "from_api")
|
||||
end
|
||||
end
|
||||
end # Service
|
||||
|
||||
class PreProcessor < Mutations::Command
|
||||
def self.from_amqp(delivery_info, body)
|
||||
# ["bot", "device_3", "resources_v0", "destroy", "Sequence", "2", "123-456
|
||||
_, device_name, _, action, resource, resource_id, uuid = \
|
||||
delivery_info.routing_key.split(".")
|
||||
run!(device_name: device_name,
|
||||
action: action,
|
||||
resource: resource,
|
||||
resource_id: resource_id,
|
||||
uuid: uuid,
|
||||
body: body.empty? ? "{}" : body)
|
||||
end
|
||||
|
||||
required do
|
||||
string :action, in: ACTIONS # "destroy"
|
||||
string :device_name, matches: DEVICE_REGEX # "device_3"
|
||||
string :resource, in: RESOURCES.keys # "Sequence"
|
||||
end
|
||||
|
||||
optional do
|
||||
integer :resource_id, default: 0 # 2
|
||||
string :body # "{\"json\":true}"
|
||||
string :uuid, default: "NONE" # "0dce-1d-41-1d-e95c3b"
|
||||
end
|
||||
|
||||
def validate
|
||||
maybe_set_device
|
||||
maybe_set_body
|
||||
end
|
||||
|
||||
def execute
|
||||
{
|
||||
action: action,
|
||||
device: @device,
|
||||
body: @body,
|
||||
resource_id: resource_id,
|
||||
resource: RESOURCES.fetch(resource),
|
||||
uuid: uuid,
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fail_body
|
||||
add_error :body, :body, "body must be a JSON object"
|
||||
end
|
||||
|
||||
def maybe_set_body
|
||||
hash = JSON.parse(body)
|
||||
fail_body unless hash.is_a?(Hash)
|
||||
@body = hash
|
||||
rescue JSON::ParserError
|
||||
fail_body
|
||||
end
|
||||
|
||||
def maybe_set_device
|
||||
id = device_name.gsub("device_", "").to_i
|
||||
@device = Device.find_by(id: id)
|
||||
add_error :device, :device, "Can't find device ##{id}" unless @device
|
||||
end
|
||||
end # PreProcessor
|
||||
end # Resources
|
|
@ -1,5 +1,5 @@
|
|||
require "spec_helper"
|
||||
require_relative "../../lib/log_service"
|
||||
# require_relative "../../lib/log_service"
|
||||
|
||||
describe LogService do
|
||||
normal_payl = '{"meta":{"z":0,"y":0,"x":0,"type":"info","major_version":6},' +
|
||||
|
|
Loading…
Reference in New Issue