diff --git a/Documentation/dev/apispec/swagger/api.swagger.json b/Documentation/dev/apispec/swagger/api.swagger.json new file mode 100644 index 00000000..085cc2c4 --- /dev/null +++ b/Documentation/dev/apispec/swagger/api.swagger.json @@ -0,0 +1,124 @@ +{ + "swagger": "2.0", + "info": { + "title": "api/grpcapi/apipb/api.proto", + "version": "version not set" + }, + "schemes": [ + "http", + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/alerts": { + "get": { + "operationId": "Get", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/apipbAlertsGetResponse" + } + } + }, + "tags": [ + "Alerts" + ] + }, + "post": { + "operationId": "Add", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/apipbAlertsAddResponse" + } + } + }, + "tags": [ + "Alerts" + ] + } + } + }, + "definitions": { + "apipbAlert": { + "type": "object", + "properties": { + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "starts_at": { + "type": "string", + "format": "date-time" + }, + "ends_at": { + "type": "string", + "format": "date-time" + }, + "generator_url": { + "type": "string" + } + } + }, + "apipbAlertsAddRequest": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/definitions/apipbAlert" + } + } + } + }, + "apipbAlertsAddResponse": { + "type": "object", + "properties": { + "header": { + "$ref": "#/definitions/apipbResponseHeader" + } + } + }, + "apipbAlertsGetRequest": { + "type": "object" + }, + "apipbAlertsGetResponse": { + "type": "object", + "properties": { + "header": { + "$ref": "#/definitions/apipbResponseHeader" + }, + "alerts": { + "type": "array", + "items": { + "$ref": "#/definitions/apipbAlert" + } + } + } + }, + "apipbResponseHeader": { + "type": "object", + "properties": { + "peer_name": { + "type": "string" + } + }, + "description": "ResponseHeader is part of every API response." + } + } +} diff --git a/Makefile b/Makefile index a2567a3f..616967b9 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,6 @@ ifdef DEBUG bindata_flags = -debug endif - all: format build test test: @@ -66,5 +65,4 @@ promu: GOARCH=$(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m))) \ $(GO) get -u github.com/prometheus/promu - .PHONY: all style format build test vet assets tarball docker promu diff --git a/api/grpcapi/apipb/api.pb.go b/api/grpcapi/apipb/api.pb.go new file mode 100644 index 00000000..c041c5cf --- /dev/null +++ b/api/grpcapi/apipb/api.pb.go @@ -0,0 +1,318 @@ +// Code generated by protoc-gen-go. +// source: api.proto +// DO NOT EDIT! + +/* +Package apipb is a generated protocol buffer package. + +It is generated from these files: + api.proto + +It has these top-level messages: + ResponseHeader + AlertsGetRequest + AlertsAddRequest + AlertsGetResponse + AlertsAddResponse + Alert +*/ +package apipb + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/timestamp" +import _ "google.golang.org/genproto/googleapis/api/annotations" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// 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 + +// ResponseHeader is part of every API response. +type ResponseHeader struct { + PeerName string `protobuf:"bytes,1,opt,name=peer_name,json=peerName" json:"peer_name,omitempty"` +} + +func (m *ResponseHeader) Reset() { *m = ResponseHeader{} } +func (m *ResponseHeader) String() string { return proto.CompactTextString(m) } +func (*ResponseHeader) ProtoMessage() {} +func (*ResponseHeader) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *ResponseHeader) GetPeerName() string { + if m != nil { + return m.PeerName + } + return "" +} + +type AlertsGetRequest struct { +} + +func (m *AlertsGetRequest) Reset() { *m = AlertsGetRequest{} } +func (m *AlertsGetRequest) String() string { return proto.CompactTextString(m) } +func (*AlertsGetRequest) ProtoMessage() {} +func (*AlertsGetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +type AlertsAddRequest struct { + Alerts []*Alert `protobuf:"bytes,1,rep,name=alerts" json:"alerts,omitempty"` +} + +func (m *AlertsAddRequest) Reset() { *m = AlertsAddRequest{} } +func (m *AlertsAddRequest) String() string { return proto.CompactTextString(m) } +func (*AlertsAddRequest) ProtoMessage() {} +func (*AlertsAddRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *AlertsAddRequest) GetAlerts() []*Alert { + if m != nil { + return m.Alerts + } + return nil +} + +type AlertsGetResponse struct { + Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"` + Alerts []*Alert `protobuf:"bytes,2,rep,name=alerts" json:"alerts,omitempty"` +} + +func (m *AlertsGetResponse) Reset() { *m = AlertsGetResponse{} } +func (m *AlertsGetResponse) String() string { return proto.CompactTextString(m) } +func (*AlertsGetResponse) ProtoMessage() {} +func (*AlertsGetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *AlertsGetResponse) GetHeader() *ResponseHeader { + if m != nil { + return m.Header + } + return nil +} + +func (m *AlertsGetResponse) GetAlerts() []*Alert { + if m != nil { + return m.Alerts + } + return nil +} + +type AlertsAddResponse struct { + Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"` +} + +func (m *AlertsAddResponse) Reset() { *m = AlertsAddResponse{} } +func (m *AlertsAddResponse) String() string { return proto.CompactTextString(m) } +func (*AlertsAddResponse) ProtoMessage() {} +func (*AlertsAddResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *AlertsAddResponse) GetHeader() *ResponseHeader { + if m != nil { + return m.Header + } + return nil +} + +type Alert struct { + Labels map[string]string `protobuf:"bytes,1,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Annotations map[string]string `protobuf:"bytes,2,rep,name=annotations" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + StartsAt *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=starts_at,json=startsAt" json:"starts_at,omitempty"` + EndsAt *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=ends_at,json=endsAt" json:"ends_at,omitempty"` + GeneratorUrl string `protobuf:"bytes,5,opt,name=generator_url,json=generatorUrl" json:"generator_url,omitempty"` +} + +func (m *Alert) Reset() { *m = Alert{} } +func (m *Alert) String() string { return proto.CompactTextString(m) } +func (*Alert) ProtoMessage() {} +func (*Alert) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +func (m *Alert) GetLabels() map[string]string { + if m != nil { + return m.Labels + } + return nil +} + +func (m *Alert) GetAnnotations() map[string]string { + if m != nil { + return m.Annotations + } + return nil +} + +func (m *Alert) GetStartsAt() *google_protobuf.Timestamp { + if m != nil { + return m.StartsAt + } + return nil +} + +func (m *Alert) GetEndsAt() *google_protobuf.Timestamp { + if m != nil { + return m.EndsAt + } + return nil +} + +func (m *Alert) GetGeneratorUrl() string { + if m != nil { + return m.GeneratorUrl + } + return "" +} + +func init() { + proto.RegisterType((*ResponseHeader)(nil), "apipb.ResponseHeader") + proto.RegisterType((*AlertsGetRequest)(nil), "apipb.AlertsGetRequest") + proto.RegisterType((*AlertsAddRequest)(nil), "apipb.AlertsAddRequest") + proto.RegisterType((*AlertsGetResponse)(nil), "apipb.AlertsGetResponse") + proto.RegisterType((*AlertsAddResponse)(nil), "apipb.AlertsAddResponse") + proto.RegisterType((*Alert)(nil), "apipb.Alert") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Alerts service + +type AlertsClient interface { + Get(ctx context.Context, in *AlertsGetRequest, opts ...grpc.CallOption) (*AlertsGetResponse, error) + Add(ctx context.Context, in *AlertsAddRequest, opts ...grpc.CallOption) (*AlertsAddResponse, error) +} + +type alertsClient struct { + cc *grpc.ClientConn +} + +func NewAlertsClient(cc *grpc.ClientConn) AlertsClient { + return &alertsClient{cc} +} + +func (c *alertsClient) Get(ctx context.Context, in *AlertsGetRequest, opts ...grpc.CallOption) (*AlertsGetResponse, error) { + out := new(AlertsGetResponse) + err := grpc.Invoke(ctx, "/apipb.Alerts/Get", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *alertsClient) Add(ctx context.Context, in *AlertsAddRequest, opts ...grpc.CallOption) (*AlertsAddResponse, error) { + out := new(AlertsAddResponse) + err := grpc.Invoke(ctx, "/apipb.Alerts/Add", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Alerts service + +type AlertsServer interface { + Get(context.Context, *AlertsGetRequest) (*AlertsGetResponse, error) + Add(context.Context, *AlertsAddRequest) (*AlertsAddResponse, error) +} + +func RegisterAlertsServer(s *grpc.Server, srv AlertsServer) { + s.RegisterService(&_Alerts_serviceDesc, srv) +} + +func _Alerts_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AlertsGetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AlertsServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/apipb.Alerts/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AlertsServer).Get(ctx, req.(*AlertsGetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Alerts_Add_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AlertsAddRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AlertsServer).Add(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/apipb.Alerts/Add", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AlertsServer).Add(ctx, req.(*AlertsAddRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Alerts_serviceDesc = grpc.ServiceDesc{ + ServiceName: "apipb.Alerts", + HandlerType: (*AlertsServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Get", + Handler: _Alerts_Get_Handler, + }, + { + MethodName: "Add", + Handler: _Alerts_Add_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} + +func init() { proto.RegisterFile("api.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 445 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x52, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0x95, 0x63, 0xe2, 0xd6, 0x93, 0x02, 0x61, 0x05, 0xc2, 0x32, 0x20, 0x2a, 0xc3, 0xa1, 0x97, + 0x3a, 0x28, 0x3d, 0x50, 0x38, 0x80, 0x8c, 0x84, 0x0a, 0x12, 0xe2, 0x60, 0xc1, 0x39, 0x9a, 0xc8, + 0x43, 0x6a, 0xe1, 0xd8, 0xcb, 0xee, 0x18, 0xa9, 0x57, 0x4e, 0xdc, 0xb9, 0xf3, 0x53, 0xfc, 0x02, + 0x1f, 0x82, 0xb2, 0xbb, 0x6e, 0xb7, 0x41, 0x02, 0xc1, 0xcd, 0x7e, 0xf3, 0xde, 0x9b, 0xb7, 0x33, + 0x03, 0x31, 0xca, 0x3a, 0x97, 0xaa, 0xe3, 0x4e, 0x8c, 0x51, 0xd6, 0x72, 0x99, 0xde, 0x5f, 0x75, + 0xdd, 0xaa, 0xa1, 0x99, 0x01, 0x97, 0xfd, 0x87, 0x19, 0xd7, 0x6b, 0xd2, 0x8c, 0x6b, 0x69, 0x79, + 0xe9, 0x5d, 0x47, 0x40, 0x59, 0xcf, 0xb0, 0x6d, 0x3b, 0x46, 0xae, 0xbb, 0x56, 0xdb, 0x6a, 0x76, + 0x08, 0xd7, 0x4a, 0xd2, 0xb2, 0x6b, 0x35, 0xbd, 0x22, 0xac, 0x48, 0x89, 0x3b, 0x10, 0x4b, 0x22, + 0xb5, 0x68, 0x71, 0x4d, 0x49, 0xb0, 0x1f, 0x1c, 0xc4, 0xe5, 0xee, 0x06, 0x78, 0x8b, 0x6b, 0xca, + 0x04, 0x4c, 0x8b, 0x86, 0x14, 0xeb, 0x13, 0xe2, 0x92, 0x3e, 0xf5, 0xa4, 0x39, 0x3b, 0x1e, 0xb0, + 0xa2, 0xaa, 0x1c, 0x26, 0x1e, 0x42, 0x84, 0x06, 0x4b, 0x82, 0xfd, 0xf0, 0x60, 0x32, 0xdf, 0xcb, + 0x4d, 0xda, 0xdc, 0x10, 0x4b, 0x57, 0xcb, 0x4e, 0xe1, 0x86, 0xe7, 0x66, 0x53, 0x88, 0x43, 0x88, + 0x4e, 0x4d, 0x12, 0xd3, 0x7c, 0x32, 0xbf, 0xe5, 0xa4, 0x97, 0x63, 0x96, 0x8e, 0xe4, 0x75, 0x1a, + 0xfd, 0xa1, 0xd3, 0x8b, 0xa1, 0x93, 0xc9, 0xf8, 0x5f, 0x9d, 0xb2, 0xaf, 0x21, 0x8c, 0x8d, 0x89, + 0x78, 0x04, 0x51, 0x83, 0x4b, 0x6a, 0x86, 0xd7, 0x25, 0x7e, 0xcf, 0xfc, 0x8d, 0x29, 0xbd, 0x6c, + 0x59, 0x9d, 0x95, 0x8e, 0x27, 0x9e, 0xc3, 0xc4, 0x9b, 0xbd, 0x8b, 0x7a, 0xef, 0x92, 0xac, 0xb8, + 0xa8, 0x5b, 0xad, 0xaf, 0x10, 0x8f, 0x21, 0xd6, 0x8c, 0x8a, 0xf5, 0x02, 0x39, 0x09, 0x4d, 0xdc, + 0x34, 0xb7, 0x9b, 0xcd, 0x87, 0xd5, 0xe7, 0xef, 0x86, 0xd5, 0x97, 0xbb, 0x96, 0x5c, 0xb0, 0x38, + 0x82, 0x1d, 0x6a, 0x2b, 0x23, 0xbb, 0xf2, 0x57, 0x59, 0xb4, 0xa1, 0x16, 0x2c, 0x1e, 0xc0, 0xd5, + 0x15, 0xb5, 0xa4, 0x90, 0x3b, 0xb5, 0xe8, 0x55, 0x93, 0x8c, 0xcd, 0x1d, 0xec, 0x9d, 0x83, 0xef, + 0x55, 0x93, 0x3e, 0x81, 0x89, 0xf7, 0x54, 0x31, 0x85, 0xf0, 0x23, 0x9d, 0xb9, 0x8b, 0xd9, 0x7c, + 0x8a, 0x9b, 0x30, 0xfe, 0x8c, 0x4d, 0x4f, 0xc9, 0xc8, 0x60, 0xf6, 0xe7, 0xe9, 0xe8, 0x38, 0x48, + 0x9f, 0xc1, 0x74, 0xfb, 0xb9, 0xff, 0xa2, 0x9f, 0x7f, 0x0f, 0x20, 0xb2, 0xfb, 0x14, 0xaf, 0x21, + 0x3c, 0x21, 0x16, 0xb7, 0xfd, 0x59, 0x7a, 0xd7, 0x99, 0x26, 0xbf, 0x17, 0xec, 0x76, 0xb3, 0xeb, + 0x5f, 0x7e, 0xfc, 0xfc, 0x36, 0x8a, 0xc5, 0xce, 0x0c, 0xcf, 0xad, 0x8a, 0xaa, 0xda, 0xb2, 0xba, + 0x38, 0xea, 0x2d, 0x2b, 0xef, 0x92, 0x06, 0xab, 0x6c, 0xb0, 0x5a, 0x46, 0x66, 0xb8, 0x47, 0xbf, + 0x02, 0x00, 0x00, 0xff, 0xff, 0xea, 0x50, 0x36, 0x31, 0xb0, 0x03, 0x00, 0x00, +} diff --git a/api/grpcapi/apipb/api.pb.gw.go b/api/grpcapi/apipb/api.pb.gw.go new file mode 100644 index 00000000..18bfdc77 --- /dev/null +++ b/api/grpcapi/apipb/api.pb.gw.go @@ -0,0 +1,155 @@ +// Code generated by protoc-gen-grpc-gateway +// source: api/grpcapi/apipb/api.proto +// DO NOT EDIT! + +/* +Package apipb is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package apipb + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" +) + +var _ codes.Code +var _ io.Reader +var _ = runtime.String +var _ = utilities.NewDoubleArray + +func request_Alerts_Get_0(ctx context.Context, marshaler runtime.Marshaler, client AlertsClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AlertsGetRequest + var metadata runtime.ServerMetadata + + msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_Alerts_Add_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Alerts_Add_0(ctx context.Context, marshaler runtime.Marshaler, client AlertsClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AlertsAddRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_Alerts_Add_0); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Add(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterAlertsHandlerFromEndpoint is same as RegisterAlertsHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterAlertsHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterAlertsHandler(ctx, mux, conn) +} + +// RegisterAlertsHandler registers the http handlers for service Alerts to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterAlertsHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + client := NewAlertsClient(conn) + + mux.Handle("GET", pattern_Alerts_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_Alerts_Get_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_Alerts_Get_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Alerts_Add_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_Alerts_Add_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_Alerts_Add_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Alerts_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"alerts"}, "")) + + pattern_Alerts_Add_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"alerts"}, "")) +) + +var ( + forward_Alerts_Get_0 = runtime.ForwardResponseMessage + + forward_Alerts_Add_0 = runtime.ForwardResponseMessage +) diff --git a/api/grpcapi/apipb/api.proto b/api/grpcapi/apipb/api.proto new file mode 100644 index 00000000..a7195a35 --- /dev/null +++ b/api/grpcapi/apipb/api.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; +package apipb; + +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +service Alerts { + rpc Get(AlertsGetRequest) returns (AlertsGetResponse) { + option (google.api.http) = { + get: "/alerts" + }; + } + + rpc Add(AlertsAddRequest) returns (AlertsAddResponse) { + option (google.api.http) = { + post: "/alerts" + }; + } +} + +// ResponseHeader is part of every API response. It allows to identify +// the responding Alertmanager instance, which can be useful to debug temporary +// inconsistencies if the Alertmanager setup is accesssed via a reverse proxy. +message ResponseHeader { + string peer_name = 1; +} + +message AlertsGetRequest { + // repeated string filter = 1; + // string group_by = 2; +} + +message AlertsAddRequest { + repeated Alert alerts = 1; +} + +message AlertsGetResponse { + ResponseHeader header = 1; + repeated Alert alerts = 2; +} + +message AlertsAddResponse { + ResponseHeader header = 1; +} + +message Alert { + map labels = 1; + map annotations = 2; + + google.protobuf.Timestamp starts_at = 3; + google.protobuf.Timestamp ends_at = 4; + + string generator_url = 5; +} diff --git a/api/grpcapi/grpc.go b/api/grpcapi/grpc.go new file mode 100644 index 00000000..3158d4de --- /dev/null +++ b/api/grpcapi/grpc.go @@ -0,0 +1,85 @@ +package grpcapi + +import ( + "net" + "net/http" + "unsafe" + + "golang.org/x/net/context" + + "github.com/golang/protobuf/ptypes" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/prometheus/alertmanager/api/grpcapi/apipb" + "github.com/prometheus/alertmanager/provider" + "github.com/weaveworks/mesh" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +func Handle(grpcl net.Listener, alerts provider.Alerts, mrouter *mesh.Router) (*grpc.Server, http.Handler) { + grpcServer := grpc.NewServer() + + asvc := &alertsServer{ + alerts: alerts, + mrouter: mrouter, + } + apipb.RegisterAlertsServer(grpcServer, asvc) + + ctx := context.Background() + gwmux := runtime.NewServeMux() + + opts := []grpc.DialOption{grpc.WithInsecure()} + err := apipb.RegisterAlertsHandlerFromEndpoint(ctx, gwmux, grpcl.Addr().String(), opts) + if err != nil { + panic(err) + } + + return grpcServer, gwmux +} + +type alertsServer struct { + alerts provider.Alerts + mrouter *mesh.Router +} + +func (s *alertsServer) Get(ctx context.Context, req *apipb.AlertsGetRequest) (*apipb.AlertsGetResponse, error) { + alerts := s.alerts.GetPending() + defer alerts.Close() + + var ( + err error + res []*apipb.Alert + ) + for a := range alerts.Next() { + if err = alerts.Err(); err != nil { + return nil, err + } + start, _ := ptypes.TimestampProto(a.StartsAt) + end, _ := ptypes.TimestampProto(a.EndsAt) + + res = append(res, &apipb.Alert{ + Labels: *(*map[string]string)(unsafe.Pointer(&a.Labels)), + Annotations: *(*map[string]string)(unsafe.Pointer(&a.Annotations)), + StartsAt: start, + EndsAt: end, + }) + } + + resp := &apipb.AlertsGetResponse{ + Header: s.header(), + Alerts: res, + } + return resp, nil +} + +func (s *alertsServer) Add(ctx context.Context, req *apipb.AlertsAddRequest) (*apipb.AlertsAddResponse, error) { + resp := &apipb.AlertsAddResponse{ + Header: s.header(), + } + return resp, grpc.Errorf(codes.Unimplemented, "not implemented") +} + +func (s *alertsServer) header() *apipb.ResponseHeader { + status := mesh.NewStatus(s.mrouter) + return &apipb.ResponseHeader{PeerName: status.Name} +} diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index be85bdbf..28b910e2 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -33,6 +33,7 @@ import ( "time" "github.com/prometheus/alertmanager/api" + "github.com/prometheus/alertmanager/api/grpcapi" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/dispatch" "github.com/prometheus/alertmanager/inhibit" @@ -81,6 +82,7 @@ func main() { externalURL = flag.String("web.external-url", "", "The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Alertmanager. If omitted, relevant URL components will be derived automatically.") listenAddress = flag.String("web.listen-address", ":9093", "Address to listen on for the web interface and API.") + grpcAddress = flag.String("web.grpc-address", ":9094", "Address to listen on for GRPC API communication.") meshListen = flag.String("mesh.listen-address", net.JoinHostPort("0.0.0.0", strconv.Itoa(mesh.Port)), "mesh listen address") hwaddr = flag.String("mesh.peer-id", "", "mesh peer ID (default: MAC address)") @@ -249,14 +251,30 @@ func main() { os.Exit(1) } + grpcl, err := net.Listen("tcp", *grpcAddress) + if err != nil { + log.Fatal(err) + } + grpcs, h := grpcapi.Handle(grpcl, alerts, mrouter) + go grpcs.Serve(grpcl) + router := route.New(nil) webReload := make(chan struct{}) ui.Register(router.WithPrefix(amURL.Path), webReload) apiv.Register(router.WithPrefix(path.Join(amURL.Path, "/api"))) + mux := http.NewServeMux() + + // Register old API and UI + mux.Handle("/", router) + mux.Handle("/api/v2/", http.StripPrefix("/api/v2", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + fmt.Println("gotreq", req) + h.ServeHTTP(w, req) + }))) + log.Infoln("Listening on", *listenAddress) - go listen(*listenAddress, router) + go http.ListenAndServe(*listenAddress, mux) var ( hup = make(chan os.Signal) @@ -376,12 +394,6 @@ func extURL(listen, external string) (*url.URL, error) { return u, nil } -func listen(listen string, router *route.Router) { - if err := http.ListenAndServe(listen, router); err != nil { - log.Fatal(err) - } -} - type stringset map[string]struct{} func (ss stringset) Set(value string) error { diff --git a/nflog/nflogpb/nflog.pb.go b/nflog/nflogpb/nflog.pb.go index 09e1b071..776d64bd 100644 --- a/nflog/nflogpb/nflog.pb.go +++ b/nflog/nflogpb/nflog.pb.go @@ -1,12 +1,12 @@ // Code generated by protoc-gen-go. -// source: nflog/nflogpb/nflog.proto +// source: nflog.proto // DO NOT EDIT! /* Package nflogpb is a generated protocol buffer package. It is generated from these files: - nflog/nflogpb/nflog.proto + nflog.proto It has these top-level messages: Receiver @@ -46,6 +46,27 @@ func (m *Receiver) String() string { return proto.CompactTextString(m func (*Receiver) ProtoMessage() {} func (*Receiver) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (m *Receiver) GetGroupName() string { + if m != nil { + return m.GroupName + } + return "" +} + +func (m *Receiver) GetIntegration() string { + if m != nil { + return m.Integration + } + return "" +} + +func (m *Receiver) GetIdx() uint32 { + if m != nil { + return m.Idx + } + return 0 +} + // Entry holds information about a successful notification // sent to a receiver. type Entry struct { @@ -66,6 +87,13 @@ func (m *Entry) String() string { return proto.CompactTextString(m) } func (*Entry) ProtoMessage() {} func (*Entry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +func (m *Entry) GetGroupKey() []byte { + if m != nil { + return m.GroupKey + } + return nil +} + func (m *Entry) GetReceiver() *Receiver { if m != nil { return m.Receiver @@ -73,6 +101,20 @@ func (m *Entry) GetReceiver() *Receiver { return nil } +func (m *Entry) GetGroupHash() []byte { + if m != nil { + return m.GroupHash + } + return nil +} + +func (m *Entry) GetResolved() bool { + if m != nil { + return m.Resolved + } + return false +} + func (m *Entry) GetTimestamp() *google_protobuf.Timestamp { if m != nil { return m.Timestamp @@ -115,27 +157,27 @@ func init() { proto.RegisterType((*MeshEntry)(nil), "nflogpb.MeshEntry") } -func init() { proto.RegisterFile("nflog/nflogpb/nflog.proto", fileDescriptor0) } +func init() { proto.RegisterFile("nflog.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 299 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x50, 0xc1, 0x4e, 0x83, 0x40, - 0x10, 0x4d, 0xad, 0x55, 0x98, 0x56, 0xa3, 0x7b, 0xc2, 0x1a, 0x63, 0xd3, 0x78, 0xf0, 0xe2, 0x36, - 0xa9, 0x17, 0x3d, 0x7a, 0x30, 0x31, 0x31, 0x7a, 0xd8, 0x78, 0x35, 0x04, 0xec, 0x14, 0x36, 0x02, - 0x4b, 0x96, 0x6d, 0x53, 0xfe, 0xd0, 0xcf, 0x12, 0x66, 0x17, 0xea, 0xc9, 0x0b, 0xcc, 0xbe, 0xf7, - 0x32, 0xef, 0xbd, 0x81, 0x8b, 0x62, 0x9d, 0xa9, 0x64, 0x41, 0xdf, 0x32, 0xb6, 0x7f, 0x5e, 0x6a, - 0x65, 0x14, 0x3b, 0x76, 0xe0, 0xf4, 0x3a, 0x51, 0x2a, 0xc9, 0x70, 0x41, 0x70, 0xbc, 0x59, 0x2f, - 0x8c, 0xcc, 0xb1, 0x32, 0x51, 0x5e, 0x5a, 0xe5, 0xfc, 0x13, 0x3c, 0x81, 0x5f, 0x28, 0xb7, 0xa8, - 0xd9, 0x15, 0x40, 0xa2, 0xd5, 0xa6, 0x0c, 0x8b, 0x28, 0xc7, 0x60, 0x30, 0x1b, 0xdc, 0xfa, 0xc2, - 0x27, 0xe4, 0xbd, 0x01, 0xd8, 0x0c, 0xc6, 0xb2, 0x30, 0x98, 0xe8, 0xc8, 0x48, 0x55, 0x04, 0x07, - 0xc4, 0xff, 0x85, 0xd8, 0x19, 0x0c, 0xe5, 0x6a, 0x17, 0x0c, 0x1b, 0xe6, 0x44, 0xb4, 0xe3, 0xfc, - 0x67, 0x00, 0xa3, 0xe7, 0xc2, 0xe8, 0x9a, 0x5d, 0x82, 0x5d, 0x15, 0x7e, 0x63, 0x4d, 0xbb, 0x27, - 0xc2, 0x23, 0xe0, 0x15, 0x6b, 0x76, 0x07, 0x9e, 0x76, 0x29, 0x68, 0xef, 0x78, 0x79, 0xce, 0x5d, - 0x05, 0xde, 0xc5, 0x13, 0xbd, 0x64, 0x1f, 0x34, 0x8d, 0xaa, 0x94, 0xec, 0x26, 0x2e, 0xe8, 0x4b, - 0x03, 0xb0, 0x69, 0xbb, 0xad, 0x52, 0xd9, 0x16, 0x57, 0xc1, 0x61, 0x43, 0x7a, 0xa2, 0x7f, 0xb3, - 0x07, 0xf0, 0xfb, 0x13, 0x04, 0x23, 0xb2, 0x9a, 0x72, 0x7b, 0x24, 0xde, 0x1d, 0x89, 0x7f, 0x74, - 0x0a, 0xb1, 0x17, 0xcf, 0x33, 0xf0, 0xdf, 0xb0, 0x4a, 0x6d, 0x9b, 0x1b, 0x18, 0x61, 0x3b, 0x50, - 0x93, 0xf1, 0xf2, 0xb4, 0x4f, 0x4b, 0xb4, 0xb0, 0x24, 0x7b, 0x04, 0xc0, 0x5d, 0x29, 0x1b, 0xf3, - 0x30, 0x32, 0xae, 0xd8, 0xbf, 0x6e, 0x4e, 0xfd, 0x64, 0xe2, 0x23, 0xa2, 0xef, 0x7f, 0x03, 0x00, - 0x00, 0xff, 0xff, 0xd7, 0x8e, 0x51, 0xb4, 0xe5, 0x01, 0x00, 0x00, + // 296 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x50, 0x4d, 0x4b, 0xc3, 0x40, + 0x10, 0x25, 0xd6, 0x6a, 0x32, 0xa9, 0xa2, 0x7b, 0x0a, 0x15, 0x31, 0x14, 0x0f, 0xbd, 0xb8, 0x85, + 0x7a, 0xd1, 0xa3, 0x07, 0x41, 0x10, 0x3d, 0x2c, 0x5e, 0xa5, 0x6c, 0xec, 0x34, 0x59, 0x4c, 0xb2, + 0x61, 0x77, 0x5b, 0x9a, 0x7f, 0xe8, 0xcf, 0x92, 0x4c, 0x3e, 0xea, 0xc9, 0xdb, 0xcc, 0x7b, 0x8f, + 0x37, 0xef, 0x0d, 0x84, 0xe5, 0x26, 0xd7, 0x29, 0xaf, 0x8c, 0x76, 0x9a, 0x9d, 0xd2, 0x52, 0x25, + 0xd3, 0x9b, 0x54, 0xeb, 0x34, 0xc7, 0x05, 0xc1, 0xc9, 0x76, 0xb3, 0x70, 0xaa, 0x40, 0xeb, 0x64, + 0x51, 0xb5, 0xca, 0xd9, 0x27, 0xf8, 0x02, 0xbf, 0x50, 0xed, 0xd0, 0xb0, 0x6b, 0x80, 0xd4, 0xe8, + 0x6d, 0xb5, 0x2a, 0x65, 0x81, 0x91, 0x17, 0x7b, 0xf3, 0x40, 0x04, 0x84, 0xbc, 0xcb, 0x02, 0x59, + 0x0c, 0xa1, 0x2a, 0x1d, 0xa6, 0x46, 0x3a, 0xa5, 0xcb, 0xe8, 0x88, 0xf8, 0xbf, 0x10, 0xbb, 0x80, + 0x91, 0x5a, 0xef, 0xa3, 0x51, 0xec, 0xcd, 0xcf, 0x44, 0x33, 0xce, 0x7e, 0x3c, 0x18, 0x3f, 0x97, + 0xce, 0xd4, 0xec, 0x0a, 0x5a, 0xab, 0xd5, 0x37, 0xd6, 0xe4, 0x3d, 0x11, 0x3e, 0x01, 0xaf, 0x58, + 0xb3, 0x3b, 0xf0, 0x4d, 0x97, 0x82, 0x7c, 0xc3, 0xe5, 0x25, 0xef, 0x2a, 0xf0, 0x3e, 0x9e, 0x18, + 0x24, 0x87, 0xa0, 0x99, 0xb4, 0x19, 0x9d, 0x9b, 0x74, 0x41, 0x5f, 0xa4, 0xcd, 0xd8, 0xb4, 0x71, + 0xb3, 0x3a, 0xdf, 0xe1, 0x3a, 0x3a, 0x8e, 0xbd, 0xb9, 0x2f, 0x86, 0x9d, 0x3d, 0x40, 0x30, 0xbc, + 0x20, 0x1a, 0xd3, 0xa9, 0x29, 0x6f, 0x9f, 0xc4, 0xfb, 0x27, 0xf1, 0x8f, 0x5e, 0x21, 0x0e, 0xe2, + 0x59, 0x0e, 0xc1, 0x1b, 0xda, 0xac, 0x6d, 0x73, 0x0b, 0x63, 0x6c, 0x06, 0x6a, 0x12, 0x2e, 0xcf, + 0x87, 0xb4, 0x44, 0x8b, 0x96, 0x64, 0x8f, 0x00, 0xb8, 0xaf, 0x94, 0x41, 0xbb, 0x92, 0xae, 0x2b, + 0xf6, 0xef, 0xb5, 0x4e, 0xfd, 0xe4, 0x92, 0x13, 0xa2, 0xef, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, + 0xf3, 0x47, 0x57, 0xc3, 0xd7, 0x01, 0x00, 0x00, } diff --git a/nflog/nflogpb/nflog.proto b/nflog/nflogpb/nflog.proto index 30428ea5..f94c632e 100644 --- a/nflog/nflogpb/nflog.proto +++ b/nflog/nflogpb/nflog.proto @@ -17,7 +17,7 @@ message Receiver { // Entry holds information about a successful notification // sent to a receiver. message Entry { -// The key identifying the dispatching group. + // The key identifying the dispatching group. bytes group_key = 1; // The receiver that was notified. Receiver receiver = 2; diff --git a/scripts/genproto.sh b/scripts/genproto.sh index 7c2c90dd..f3c56760 100755 --- a/scripts/genproto.sh +++ b/scripts/genproto.sh @@ -1,2 +1,43 @@ -protoc --go_out=. nflog/nflogpb/nflog.proto -protoc --go_out=. silence/silencepb/silence.proto +#!/usr/bin/env bash +# +# Generate all etcd protobuf bindings. +# Run from repository root. +set -e +set -u + +if ! [[ "$0" =~ "scripts/genproto.sh" ]]; then + echo "must be run from repository root" + exit 255 +fi + +if ! [[ $(protoc --version) =~ "3.2.0" ]]; then + echo "could not find protoc 3.2.0, is it installed + in PATH?" + exit 255 +fi + +GOGOPROTO_ROOT="${GOPATH}/src/github.com/gogo/protobuf" +GOGOPROTO_PATH="${GOGOPROTO_ROOT}:${GOGOPROTO_ROOT}/protobuf" +GRPC_GATEWAY_ROOT="${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway" +PROMETHEUS_ROOT="${GOPATH}/src/github.com/prometheus" + +DIRS="nflog/nflogpb silence/silencepb/ api/grpcapi/apipb/" + +for dir in ${DIRS}; do + pushd ${dir} + protoc --go_out=plugins=grpc:. -I=. \ + -I="${PROMETHEUS_ROOT}" \ + -I="${GRPC_GATEWAY_ROOT}/third_party/googleapis" \ + *.proto + popd +done + +protoc -I. \ + -I="${GOGOPROTO_PATH}" \ + -I="${PROMETHEUS_ROOT}" \ + -I="${GRPC_GATEWAY_ROOT}/third_party/googleapis" \ + --grpc-gateway_out=logtostderr=true:. \ + --swagger_out=logtostderr=true:./Documentation/dev/apispec/swagger/. \ + api/grpcapi/apipb/api.proto + +mv Documentation/dev/apispec/swagger/api/grpcapi/apipb/api.swagger.json Documentation/dev/apispec/swagger +rm -rf Documentation/dev/apispec/swagger/api \ No newline at end of file diff --git a/silence/silencepb/silence.pb.go b/silence/silencepb/silence.pb.go index 4679ff3f..c1cc6745 100644 --- a/silence/silencepb/silence.pb.go +++ b/silence/silencepb/silence.pb.go @@ -1,12 +1,12 @@ // Code generated by protoc-gen-go. -// source: silence/silencepb/silence.proto +// source: silence.proto // DO NOT EDIT! /* Package silencepb is a generated protocol buffer package. It is generated from these files: - silence/silencepb/silence.proto + silence.proto It has these top-level messages: Matcher @@ -70,6 +70,27 @@ func (m *Matcher) String() string { return proto.CompactTextString(m) func (*Matcher) ProtoMessage() {} func (*Matcher) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (m *Matcher) GetType() Matcher_Type { + if m != nil { + return m.Type + } + return Matcher_EQUAL +} + +func (m *Matcher) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Matcher) GetPattern() string { + if m != nil { + return m.Pattern + } + return "" +} + // A comment can be attached to a silence. type Comment struct { Author string `protobuf:"bytes,1,opt,name=author" json:"author,omitempty"` @@ -82,6 +103,20 @@ func (m *Comment) String() string { return proto.CompactTextString(m) func (*Comment) ProtoMessage() {} func (*Comment) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +func (m *Comment) GetAuthor() string { + if m != nil { + return m.Author + } + return "" +} + +func (m *Comment) GetComment() string { + if m != nil { + return m.Comment + } + return "" +} + func (m *Comment) GetTimestamp() *google_protobuf.Timestamp { if m != nil { return m.Timestamp @@ -111,6 +146,13 @@ func (m *Silence) String() string { return proto.CompactTextString(m) func (*Silence) ProtoMessage() {} func (*Silence) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } +func (m *Silence) GetId() string { + if m != nil { + return m.Id + } + return "" +} + func (m *Silence) GetMatchers() []*Matcher { if m != nil { return m.Matchers @@ -180,32 +222,32 @@ func init() { proto.RegisterEnum("silencepb.Matcher_Type", Matcher_Type_name, Matcher_Type_value) } -func init() { proto.RegisterFile("silence/silencepb/silence.proto", fileDescriptor0) } +func init() { proto.RegisterFile("silence.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 372 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x91, 0xcf, 0x4b, 0xeb, 0x40, - 0x10, 0xc7, 0x5f, 0xd2, 0x34, 0x69, 0xa6, 0x50, 0xca, 0x1e, 0xde, 0x0b, 0x85, 0x47, 0x25, 0x27, - 0x41, 0xd9, 0x42, 0x7b, 0x50, 0x8f, 0x45, 0x8a, 0x17, 0x0b, 0xba, 0x56, 0xf0, 0x26, 0x69, 0x33, - 0xb6, 0x81, 0xe6, 0x07, 0xc9, 0x44, 0xf4, 0xec, 0x7f, 0xe2, 0x5f, 0xea, 0x66, 0xb3, 0x89, 0x48, - 0x0f, 0xf5, 0x94, 0x99, 0xcc, 0xe7, 0x3b, 0x33, 0xdf, 0x59, 0x18, 0x17, 0xd1, 0x1e, 0x93, 0x0d, - 0x4e, 0xf4, 0x37, 0x5b, 0x37, 0x11, 0xcf, 0xf2, 0x94, 0x52, 0xe6, 0xb6, 0x85, 0xd1, 0x78, 0x9b, - 0xa6, 0xdb, 0x3d, 0x4e, 0x54, 0x61, 0x5d, 0xbe, 0x4c, 0x28, 0x8a, 0xb1, 0xa0, 0x20, 0xce, 0x6a, - 0xd6, 0xff, 0x30, 0xc0, 0x59, 0x06, 0xb4, 0xd9, 0x61, 0xce, 0xce, 0xc0, 0xa2, 0xf7, 0x0c, 0x3d, - 0xe3, 0xc4, 0x38, 0x1d, 0x4c, 0xff, 0xf1, 0xb6, 0x0d, 0xd7, 0x04, 0x5f, 0xc9, 0xb2, 0x50, 0x10, - 0x63, 0x60, 0x25, 0x41, 0x8c, 0x9e, 0x29, 0x61, 0x57, 0xa8, 0x98, 0x79, 0xe0, 0x64, 0x01, 0x11, - 0xe6, 0x89, 0xd7, 0x51, 0xbf, 0x9b, 0xd4, 0xff, 0x0f, 0x56, 0xa5, 0x65, 0x2e, 0x74, 0x17, 0xf7, - 0x8f, 0xf3, 0xdb, 0xe1, 0x1f, 0x06, 0x60, 0x8b, 0xc5, 0xcd, 0xe2, 0xe9, 0x6e, 0x68, 0xf8, 0x25, - 0x38, 0xd7, 0x69, 0x1c, 0x63, 0x42, 0xec, 0x2f, 0xd8, 0x41, 0x49, 0xbb, 0x34, 0x57, 0x6b, 0xb8, - 0x42, 0x67, 0x55, 0xef, 0x4d, 0x8d, 0xe8, 0x91, 0x4d, 0xca, 0x2e, 0xc1, 0x6d, 0x5d, 0xa9, 0xb9, - 0xfd, 0xe9, 0x88, 0xd7, 0xbe, 0x79, 0xe3, 0x9b, 0xaf, 0x1a, 0x42, 0x7c, 0xc3, 0xfe, 0xa7, 0x09, - 0xce, 0x43, 0x6d, 0x92, 0x0d, 0xc0, 0x8c, 0x42, 0x3d, 0x53, 0x46, 0x8c, 0x43, 0x2f, 0xae, 0x5d, - 0x17, 0x72, 0x60, 0x47, 0x36, 0x65, 0x87, 0x07, 0x11, 0x2d, 0xc3, 0x2e, 0xc0, 0x95, 0x4d, 0x73, - 0x2a, 0x9e, 0x03, 0xfa, 0xc5, 0x16, 0xbd, 0x1a, 0x9e, 0x13, 0x9b, 0x81, 0x83, 0x49, 0xa8, 0x64, - 0xd6, 0x51, 0x99, 0x5d, 0xa1, 0x52, 0x74, 0x05, 0x50, 0x66, 0x61, 0x40, 0x18, 0x56, 0xba, 0xee, - 0x71, 0xd3, 0x9a, 0x96, 0x52, 0x69, 0x4c, 0x5f, 0xae, 0xf0, 0x9c, 0x03, 0x63, 0xfa, 0x19, 0x44, - 0xcb, 0xf8, 0xaf, 0xd0, 0x5f, 0x62, 0xb1, 0x6b, 0xee, 0x74, 0x0e, 0x8e, 0xa6, 0xd5, 0xb1, 0x7e, - 0xaa, 0x35, 0x24, 0x1a, 0xa4, 0xda, 0x13, 0xdf, 0xb2, 0x28, 0x47, 0xe5, 0xcf, 0x3c, 0xbe, 0xa7, - 0xa6, 0xe7, 0xb4, 0xb6, 0x55, 0x79, 0xf6, 0x15, 0x00, 0x00, 0xff, 0xff, 0x7d, 0xe9, 0xc3, 0x5f, - 0xef, 0x02, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x91, 0xc1, 0x6a, 0xdb, 0x40, + 0x10, 0x86, 0x2b, 0x59, 0x96, 0xac, 0x31, 0x35, 0x66, 0x0f, 0xad, 0x30, 0x94, 0x1a, 0x9d, 0x0c, + 0x2d, 0x6b, 0xb0, 0x0f, 0x6d, 0x8f, 0xa2, 0x98, 0x5e, 0x6a, 0x48, 0x36, 0x0e, 0xe4, 0x16, 0xd6, + 0xd6, 0xc4, 0x16, 0x58, 0xd2, 0x22, 0x8d, 0x42, 0x7c, 0xce, 0x9b, 0xe4, 0x49, 0x83, 0x56, 0x2b, + 0x85, 0xe0, 0x83, 0x73, 0xd3, 0x68, 0xbf, 0x7f, 0x67, 0xbe, 0x59, 0xf8, 0x5c, 0x26, 0x47, 0xcc, + 0x76, 0xc8, 0x55, 0x91, 0x53, 0xce, 0x7c, 0x53, 0xaa, 0xed, 0xe4, 0xfb, 0x3e, 0xcf, 0xf7, 0x47, + 0x9c, 0xeb, 0x83, 0x6d, 0xf5, 0x30, 0xa7, 0x24, 0xc5, 0x92, 0x64, 0xaa, 0x1a, 0x36, 0x7c, 0xb6, + 0xc0, 0x5b, 0x4b, 0xda, 0x1d, 0xb0, 0x60, 0x3f, 0xc0, 0xa1, 0x93, 0xc2, 0xc0, 0x9a, 0x5a, 0xb3, + 0xd1, 0xe2, 0x2b, 0xef, 0xae, 0xe1, 0x86, 0xe0, 0x9b, 0x93, 0x42, 0xa1, 0x21, 0xc6, 0xc0, 0xc9, + 0x64, 0x8a, 0x81, 0x3d, 0xb5, 0x66, 0xbe, 0xd0, 0xdf, 0x2c, 0x00, 0x4f, 0x49, 0x22, 0x2c, 0xb2, + 0xa0, 0xa7, 0x7f, 0xb7, 0x65, 0xf8, 0x0d, 0x9c, 0x3a, 0xcb, 0x7c, 0xe8, 0xaf, 0xae, 0x6f, 0xa3, + 0xff, 0xe3, 0x4f, 0x0c, 0xc0, 0x15, 0xab, 0x7f, 0xab, 0xbb, 0xab, 0xb1, 0x15, 0x56, 0xe0, 0xfd, + 0xcd, 0xd3, 0x14, 0x33, 0x62, 0x5f, 0xc0, 0x95, 0x15, 0x1d, 0xf2, 0x42, 0x8f, 0xe1, 0x0b, 0x53, + 0xd5, 0x77, 0xef, 0x1a, 0xc4, 0xb4, 0x6c, 0x4b, 0xf6, 0x1b, 0xfc, 0xce, 0x4a, 0xf7, 0x1d, 0x2e, + 0x26, 0xbc, 0xf1, 0xe6, 0xad, 0x37, 0xdf, 0xb4, 0x84, 0x78, 0x83, 0xc3, 0x17, 0x1b, 0xbc, 0x9b, + 0x46, 0x92, 0x8d, 0xc0, 0x4e, 0x62, 0xd3, 0xd3, 0x4e, 0x62, 0xc6, 0x61, 0x90, 0x36, 0xd6, 0x65, + 0x60, 0x4f, 0x7b, 0xb3, 0xe1, 0x82, 0x9d, 0x2f, 0x44, 0x74, 0x0c, 0xfb, 0x05, 0x7e, 0x49, 0xb2, + 0xa0, 0xf2, 0x5e, 0xd2, 0x07, 0xa6, 0x18, 0x34, 0x70, 0x44, 0x6c, 0x09, 0x1e, 0x66, 0xb1, 0x8e, + 0x39, 0x17, 0x63, 0x6e, 0x8d, 0x46, 0xc4, 0xfe, 0x00, 0x54, 0x2a, 0x96, 0x84, 0x71, 0x9d, 0xeb, + 0x5f, 0x96, 0x36, 0x74, 0x44, 0xb5, 0x98, 0xd9, 0x5c, 0x19, 0x78, 0x67, 0x62, 0xe6, 0x19, 0x44, + 0xc7, 0x84, 0x8f, 0x30, 0x5c, 0x63, 0x79, 0x68, 0xf7, 0xf4, 0x13, 0x3c, 0x43, 0xeb, 0x65, 0xbd, + 0x4f, 0x1b, 0x48, 0xb4, 0x48, 0x3d, 0x27, 0x3e, 0xa9, 0xa4, 0x40, 0xed, 0x67, 0x5f, 0x9e, 0xd3, + 0xd0, 0x11, 0x6d, 0x5d, 0x7d, 0xbc, 0x7c, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xd7, 0x1a, 0x8f, 0x20, + 0xdd, 0x02, 0x00, 0x00, }