send mdm responses to pubsub (#254)

Wraps mdm.Responses into pubsub events and sends them downstream
This PR does not yet implement the full mdm.Response as an event, only the metadata.

The device db adds a subscription to mdm responses and updates the LastCheckin key.

Closes #251
This commit is contained in:
Victor Vrantchan
2017-10-21 16:17:37 -04:00
committed by GitHub
parent 665e2b8bcc
commit 4b285af74e
7 changed files with 264 additions and 3 deletions

View File

@@ -5,9 +5,14 @@ import (
"fmt"
"github.com/micromdm/mdm"
"github.com/pkg/errors"
"github.com/micromdm/micromdm/pubsub"
"github.com/micromdm/micromdm/queue"
)
const ConnectTopic = "mdm.Connect"
// The ConnectService accepts responses sent to an MDM server by an enrolled
// device.
type ConnectService interface {
@@ -19,23 +24,33 @@ type ConnectService interface {
type connectSvc struct {
queue Queue
pub pubsub.Publisher
}
type Queue interface {
Next(context.Context, mdm.Response) (*queue.Command, error)
}
func New(queue Queue) (ConnectService, error) {
func New(queue Queue, pub pubsub.Publisher) (ConnectService, error) {
return &connectSvc{
queue: queue,
pub: pub,
}, nil
}
func (svc *connectSvc) Acknowledge(ctx context.Context, req mdm.Response) (payload []byte, err error) {
fmt.Printf("connected udid=%s type=%s, status=%s\n", req.UDID, req.RequestType, req.Status)
event := NewEvent(req)
msg, err := MarshalEvent(event)
if err != nil {
return nil, errors.Wrap(err, "marshal connect response to proto")
}
if err := svc.pub.Publish(context.TODO(), ConnectTopic, msg); err != nil {
return nil, errors.Wrap(err, "publish connect Response on pubsub")
}
cmd, err := svc.queue.Next(ctx, req)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "calling Next with mdm response")
}
// next can return no errors and no payload.
if cmd == nil {

69
connect/event.go Normal file
View File

@@ -0,0 +1,69 @@
package connect
import (
"time"
"github.com/gogo/protobuf/proto"
"github.com/micromdm/mdm"
uuid "github.com/satori/go.uuid"
"github.com/micromdm/micromdm/connect/internal/connectproto"
)
type Event struct {
ID string
Time time.Time
Response mdm.Response
}
func NewEvent(resp mdm.Response) *Event {
event := Event{
ID: uuid.NewV4().String(),
Time: time.Now().UTC(),
Response: resp,
}
return &event
}
func MarshalEvent(e *Event) ([]byte, error) {
response := &connectproto.Response{
CommandUuid: e.Response.CommandUUID,
Udid: e.Response.UDID,
Status: e.Response.Status,
RequestType: e.Response.RequestType,
}
if e.Response.UserID != nil {
response.Udid = *e.Response.UserID
}
return proto.Marshal(&connectproto.Event{
Id: e.ID,
Time: e.Time.UnixNano(),
Response: response,
})
}
func UnmarshalEvent(data []byte, e *Event) error {
var pb connectproto.Event
if err := proto.Unmarshal(data, &pb); err != nil {
return err
}
e.ID = pb.Id
e.Time = time.Unix(0, pb.Time).UTC()
if pb.Response == nil {
return nil
}
r := pb.GetResponse()
e.Response = mdm.Response{
UDID: r.GetUdid(),
UserID: strPtr(r.GetUserId()),
Status: r.GetStatus(),
RequestType: r.GetRequestType(),
CommandUUID: r.GetCommandUuid(),
}
return nil
}
func strPtr(s string) *string {
return &s
}

View File

@@ -0,0 +1,3 @@
package connectproto
//go:generate protoc --go_out=. connect.proto

View File

@@ -0,0 +1,134 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: connect.proto
/*
Package connectproto is a generated protocol buffer package.
It is generated from these files:
connect.proto
It has these top-level messages:
Event
Response
*/
package connectproto
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Event struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Time int64 `protobuf:"varint,2,opt,name=time" json:"time,omitempty"`
Response *Response `protobuf:"bytes,3,opt,name=response" json:"response,omitempty"`
}
func (m *Event) Reset() { *m = Event{} }
func (m *Event) String() string { return proto.CompactTextString(m) }
func (*Event) ProtoMessage() {}
func (*Event) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *Event) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *Event) GetTime() int64 {
if m != nil {
return m.Time
}
return 0
}
func (m *Event) GetResponse() *Response {
if m != nil {
return m.Response
}
return nil
}
type Response struct {
Udid string `protobuf:"bytes,1,opt,name=udid" json:"udid,omitempty"`
UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId" json:"user_id,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status" json:"status,omitempty"`
RequestType string `protobuf:"bytes,4,opt,name=request_type,json=requestType" json:"request_type,omitempty"`
CommandUuid string `protobuf:"bytes,5,opt,name=command_uuid,json=commandUuid" json:"command_uuid,omitempty"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *Response) GetUdid() string {
if m != nil {
return m.Udid
}
return ""
}
func (m *Response) GetUserId() string {
if m != nil {
return m.UserId
}
return ""
}
func (m *Response) GetStatus() string {
if m != nil {
return m.Status
}
return ""
}
func (m *Response) GetRequestType() string {
if m != nil {
return m.RequestType
}
return ""
}
func (m *Response) GetCommandUuid() string {
if m != nil {
return m.CommandUuid
}
return ""
}
func init() {
proto.RegisterType((*Event)(nil), "connectproto.Event")
proto.RegisterType((*Response)(nil), "connectproto.Response")
}
func init() { proto.RegisterFile("connect.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 213 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8e, 0xb1, 0x52, 0xc3, 0x30,
0x10, 0x44, 0x47, 0x4e, 0x62, 0xe2, 0x4b, 0xa0, 0xb8, 0x22, 0xa8, 0x34, 0xa9, 0x5c, 0xb9, 0x08,
0xdf, 0x40, 0x41, 0xab, 0x81, 0xda, 0x13, 0x7c, 0x57, 0xa8, 0xb0, 0x64, 0xa4, 0x13, 0x33, 0xf9,
0x10, 0xfe, 0x97, 0xb1, 0x10, 0x1e, 0xba, 0x7b, 0xbb, 0x3b, 0xbb, 0x07, 0xf7, 0xa3, 0x77, 0x8e,
0x47, 0xe9, 0xe7, 0xe0, 0xc5, 0xe3, 0xb1, 0x60, 0xa6, 0xf3, 0x00, 0xbb, 0x97, 0x2f, 0x76, 0x82,
0x0f, 0x50, 0x59, 0xd2, 0xaa, 0x55, 0x5d, 0x63, 0x2a, 0x4b, 0x88, 0xb0, 0x15, 0x3b, 0xb1, 0xae,
0x5a, 0xd5, 0x6d, 0x4c, 0xbe, 0xf1, 0x02, 0xfb, 0xc0, 0x71, 0xf6, 0x2e, 0xb2, 0xde, 0xb4, 0xaa,
0x3b, 0x5c, 0x4e, 0xfd, 0xff, 0xb6, 0xde, 0x14, 0xd7, 0xac, 0xb9, 0xf3, 0xb7, 0x82, 0xfd, 0x9f,
0xbc, 0x94, 0x26, 0x5a, 0x67, 0xf2, 0x8d, 0x8f, 0x70, 0x97, 0x22, 0x87, 0xc1, 0x52, 0xde, 0x6a,
0x4c, 0xbd, 0xe0, 0x2b, 0xe1, 0x09, 0xea, 0x28, 0x57, 0x49, 0x31, 0x6f, 0x35, 0xa6, 0x10, 0x3e,
0xc1, 0x31, 0xf0, 0x67, 0xe2, 0x28, 0x83, 0xdc, 0x66, 0xd6, 0xdb, 0xec, 0x1e, 0x8a, 0xf6, 0x76,
0x9b, 0x79, 0x89, 0x8c, 0x7e, 0x9a, 0xae, 0x8e, 0x86, 0x94, 0x2c, 0xe9, 0xdd, 0x6f, 0xa4, 0x68,
0xef, 0xc9, 0xd2, 0x47, 0x9d, 0x3f, 0x7e, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x70, 0xa9, 0xb3,
0xa5, 0x1e, 0x01, 0x00, 0x00,
}

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package connectproto;
message Event {
string id = 1;
int64 time = 2;
Response response = 3;
}
message Response {
string udid = 1;
string user_id = 2;
string status = 3;
string request_type = 4;
string command_uuid = 5;
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/boltdb/bolt"
"github.com/micromdm/micromdm/checkin"
"github.com/micromdm/micromdm/connect"
"github.com/micromdm/micromdm/depsync"
"github.com/micromdm/micromdm/pubsub"
"github.com/pkg/errors"
@@ -186,6 +187,11 @@ func (db *DB) pollCheckin(pubsubSvc pubsub.PublishSubscriber) error {
return errors.Wrapf(err,
"subscribing devices to %s topic", depsync.SyncTopic)
}
connectEvents, err := pubsubSvc.Subscribe(context.TODO(), "devices", connect.ConnectTopic)
if err != nil {
return errors.Wrapf(err,
"subscribing devices to %s topic", connect.ConnectTopic)
}
go func() {
for {
select {
@@ -308,6 +314,22 @@ func (db *DB) pollCheckin(pubsubSvc pubsub.PublishSubscriber) error {
continue
}
}
case event := <-connectEvents:
var ev connect.Event
if err := connect.UnmarshalEvent(event.Message, &ev); err != nil {
fmt.Println(err)
continue
}
dev, err := db.DeviceByUDID(ev.Response.UDID)
if err != nil {
fmt.Println(err)
continue
}
dev.LastCheckin = time.Now()
if err := db.Save(dev); err != nil {
fmt.Println(err)
continue
}
case event := <-checkoutEvents:
var ev checkin.Event
if err := checkin.UnmarshalEvent(event.Message, &ev); err != nil {

View File

@@ -499,7 +499,7 @@ func (c *config) setupCommandQueue() {
return
}
connSvc, err := connect.New(q)
connSvc, err := connect.New(q, c.pubclient)
if err != nil {
c.err = err
return