create server endpoints for the DEP list/apply services (#160)

creates get endpoints for 

DEP device details
DEP account info
DEP fetch profile

and apply endpoint for defining a DEP profile
This commit is contained in:
Victor Vrantchan
2017-05-27 23:01:37 -04:00
committed by GitHub
parent 40ea5ab0ee
commit c63d0fc152
11 changed files with 354 additions and 38 deletions

View File

@@ -22,7 +22,7 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
"PUT",
copyURL(u, "/v1/blueprints"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeBlueprintRequest,
DecodeBlueprintResponse,
opts...,
).Endpoint()
}
@@ -32,7 +32,7 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
"PUT",
copyURL(u, "/v1/dep-tokens"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeDEPTokensRequest,
DecodeDEPTokensResponse,
opts...,
).Endpoint()
}
@@ -42,15 +42,27 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
"PUT",
copyURL(u, "/v1/profiles"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeProfileRequest,
DecodeProfileResponse,
opts...,
).Endpoint()
}
var defineDEPProfileEndpoint endpoint.Endpoint
{
defineDEPProfileEndpoint = httptransport.NewClient(
"POST",
copyURL(u, "/v1/dep/profiles"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeDEPProfileResponse,
opts...,
).Endpoint()
}
return Endpoints{
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
ApplyDEPTokensEndpoint: applyDEPTokensEndpoint,
ApplyProfileEndpoint: applyProfileEndpoint,
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
ApplyDEPTokensEndpoint: applyDEPTokensEndpoint,
ApplyProfileEndpoint: applyProfileEndpoint,
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
}, nil
}

View File

@@ -4,14 +4,26 @@ import (
"context"
"github.com/go-kit/kit/endpoint"
"github.com/micromdm/dep"
"github.com/micromdm/micromdm/blueprint"
"github.com/micromdm/micromdm/profile"
)
type Endpoints struct {
ApplyBlueprintEndpoint endpoint.Endpoint
ApplyDEPTokensEndpoint endpoint.Endpoint
ApplyProfileEndpoint endpoint.Endpoint
ApplyBlueprintEndpoint endpoint.Endpoint
ApplyDEPTokensEndpoint endpoint.Endpoint
ApplyProfileEndpoint endpoint.Endpoint
DefineDEPProfileEndpoint endpoint.Endpoint
}
func (e Endpoints) DefineDEPProfile(ctx context.Context, p *dep.Profile) (*dep.ProfileResponse, error) {
request := depProfileRequest{Profile: p}
resp, err := e.DefineDEPProfileEndpoint(ctx, request)
if err != nil {
return nil, err
}
response := resp.(depProfileResponse)
return response.ProfileResponse, response.Err
}
func (e Endpoints) ApplyBlueprint(ctx context.Context, bp *blueprint.Blueprint) error {
@@ -71,6 +83,17 @@ func MakeApplyProfileEndpoint(svc Service) endpoint.Endpoint {
}
}
func MakeDefineDEPProfile(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(depProfileRequest)
resp, err := svc.DefineDEPProfile(ctx, req.Profile)
return depProfileResponse{
ProfileResponse: resp,
Err: err,
}, nil
}
}
type blueprintRequest struct {
Blueprint *blueprint.Blueprint `json:"blueprint"`
}
@@ -100,3 +123,11 @@ type depTokensResponse struct {
}
func (r depTokensResponse) error() error { return r.Err }
type depProfileRequest struct{ *dep.Profile }
type depProfileResponse struct {
*dep.ProfileResponse
Err error `json:"err,omitempty"`
}
func (r *depProfileResponse) error() error { return r.Err }

View File

@@ -8,11 +8,13 @@ import (
"bytes"
"encoding/base64"
"encoding/json"
"github.com/fullsailor/pkcs7"
"io"
"net/textproto"
"github.com/fullsailor/pkcs7"
"github.com/boltdb/bolt"
"github.com/micromdm/dep"
"github.com/micromdm/micromdm/blueprint"
"github.com/micromdm/micromdm/core/list"
"github.com/micromdm/micromdm/profile"
@@ -22,9 +24,11 @@ type Service interface {
ApplyBlueprint(ctx context.Context, bp *blueprint.Blueprint) error
ApplyDEPToken(ctx context.Context, P7MContent []byte) error
ApplyProfile(ctx context.Context, p *profile.Profile) error
DEPService
}
type ApplyService struct {
DEPClient dep.Client
Blueprints *blueprint.DB
Profiles *profile.DB
DB *bolt.DB // TODO: replace with reference to DEP token svc/pkg

15
core/apply/service_dep.go Normal file
View File

@@ -0,0 +1,15 @@
package apply
import (
"context"
"github.com/micromdm/dep"
)
type DEPService interface {
DefineDEPProfile(ctx context.Context, p *dep.Profile) (*dep.ProfileResponse, error)
}
func (svc *ApplyService) DefineDEPProfile(ctx context.Context, p *dep.Profile) (*dep.ProfileResponse, error) {
return svc.DEPClient.DefineProfile(p)
}

View File

@@ -12,9 +12,10 @@ import (
)
type HTTPHandlers struct {
BlueprintHandler http.Handler
DEPTokensHandler http.Handler
ProfileHandler http.Handler
BlueprintHandler http.Handler
DEPTokensHandler http.Handler
ProfileHandler http.Handler
DefineDEPProfileHandler http.Handler
}
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
@@ -37,6 +38,12 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
encodeResponse,
opts...,
),
DefineDEPProfileHandler: httptransport.NewServer(
endpoints.DefineDEPProfileEndpoint,
decodeDEPProfileRequest,
encodeResponse,
opts...,
),
}
return h
}
@@ -65,6 +72,14 @@ func decodeProfileRequest(ctx context.Context, r *http.Request) (interface{}, er
return req, nil
}
func decodeDEPProfileRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var req depProfileRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
type errorWrapper struct {
Error string `json:"error"`
}
@@ -110,7 +125,7 @@ func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interf
return nil
}
func DecodeBlueprintRequest(_ context.Context, r *http.Response) (interface{}, error) {
func DecodeBlueprintResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
@@ -119,7 +134,7 @@ func DecodeBlueprintRequest(_ context.Context, r *http.Response) (interface{}, e
return resp, err
}
func DecodeDEPTokensRequest(_ context.Context, r *http.Response) (interface{}, error) {
func DecodeDEPTokensResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
@@ -128,7 +143,7 @@ func DecodeDEPTokensRequest(_ context.Context, r *http.Response) (interface{}, e
return resp, err
}
func DecodeProfileRequest(_ context.Context, r *http.Response) (interface{}, error) {
func DecodeProfileResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
@@ -136,3 +151,12 @@ func DecodeProfileRequest(_ context.Context, r *http.Response) (interface{}, err
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeDEPProfileResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp depProfileResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}

View File

@@ -55,11 +55,44 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
).Endpoint()
}
var getDEPAccountInfoEndpoint endpoint.Endpoint
{
getDEPAccountInfoEndpoint = httptransport.NewClient(
"GET",
copyURL(u, "/v1/dep/account"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeDEPAccountInfoResponse,
).Endpoint()
}
var getDEPDeviceDetailsEndpoint endpoint.Endpoint
{
getDEPDeviceDetailsEndpoint = httptransport.NewClient(
"GET",
copyURL(u, "/v1/dep/devices"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeDEPDeviceDetailsReponse,
).Endpoint()
}
var getDEPProfilesEndpoint endpoint.Endpoint
{
getDEPProfilesEndpoint = httptransport.NewClient(
"GET",
copyURL(u, "/v1/dep/profiles"),
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
DecodeDEPProfileResponse,
).Endpoint()
}
return Endpoints{
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPTokensEndpoint: getDEPTokensEndpoint,
GetBlueprintsEndpoint: getBlueprintsEndpoint,
GetProfilesEndpoint: getProfilesEndpoint,
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPTokensEndpoint: getDEPTokensEndpoint,
GetBlueprintsEndpoint: getBlueprintsEndpoint,
GetProfilesEndpoint: getProfilesEndpoint,
GetDEPAccountInfoEndpoint: getDEPAccountInfoEndpoint,
GetDEPDeviceEndpoint: getDEPDeviceDetailsEndpoint,
GetDEPProfileEndpoint: getDEPProfilesEndpoint,
}, nil
}

View File

@@ -5,15 +5,19 @@ import (
"time"
"github.com/go-kit/kit/endpoint"
"github.com/micromdm/dep"
"github.com/micromdm/micromdm/blueprint"
"github.com/micromdm/micromdm/profile"
)
type Endpoints struct {
ListDevicesEndpoint endpoint.Endpoint
GetDEPTokensEndpoint endpoint.Endpoint
GetBlueprintsEndpoint endpoint.Endpoint
GetProfilesEndpoint endpoint.Endpoint
ListDevicesEndpoint endpoint.Endpoint
GetDEPTokensEndpoint endpoint.Endpoint
GetBlueprintsEndpoint endpoint.Endpoint
GetProfilesEndpoint endpoint.Endpoint
GetDEPAccountInfoEndpoint endpoint.Endpoint
GetDEPDeviceEndpoint endpoint.Endpoint
GetDEPProfileEndpoint endpoint.Endpoint
}
func (e Endpoints) ListDevices(ctx context.Context, opts ListDevicesOption) ([]DeviceDTO, error) {
@@ -51,6 +55,24 @@ func (e Endpoints) GetProfiles(ctx context.Context, opt GetProfilesOption) ([]pr
return response.(profilesResponse).Profiles, response.(profilesResponse).Err
}
func (e Endpoints) GetDEPAccountInfo(ctx context.Context) (*dep.Account, error) {
request := depAccountInforequest{}
response, err := e.GetDEPAccountInfoEndpoint(ctx, request)
if err != nil {
return nil, err
}
return response.(depAccountInfoResponse).Account, response.(depAccountInfoResponse).Err
}
func (e Endpoints) GetDEPDevice(ctx context.Context, serials []string) (*dep.DeviceDetailsResponse, error) {
request := depDeviceDetailsRequest{Serials: serials}
response, err := e.GetDEPDeviceEndpoint(ctx, request)
if err != nil {
return nil, err
}
return response.(depDeviceDetailsResponse).DeviceDetailsResponse, response.(depDeviceDetailsResponse).Err
}
func MakeListDevicesEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(devicesRequest)
@@ -62,6 +84,15 @@ func MakeListDevicesEndpoint(svc Service) endpoint.Endpoint {
}
}
func (e Endpoints) GetDEPProfile(ctx context.Context, uuid string) (*dep.Profile, error) {
request := depProfileRequest{UUID: uuid}
response, err := e.GetDEPProfileEndpoint(ctx, request)
if err != nil {
return nil, err
}
return response.(depProfileResponse).Profile, response.(depProfileResponse).Err
}
func MakeGetDEPTokensEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
tokens, pubkey, err := svc.GetDEPTokens(ctx)
@@ -95,6 +126,29 @@ func MakeGetProfilesEndpoint(svc Service) endpoint.Endpoint {
}
}
func MakeGetDEPAccountInfoEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
account, err := svc.GetDEPAccountInfo(ctx)
return depAccountInfoResponse{Account: account, Err: err}, nil
}
}
func MakeGetDEPDeviceDetailsEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(depDeviceDetailsRequest)
details, err := svc.GetDEPDevice(ctx, req.Serials)
return depDeviceDetailsResponse{DeviceDetailsResponse: details, Err: err}, nil
}
}
func MakeGetDEPProfileEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(depProfileRequest)
profile, err := svc.GetDEPProfile(ctx, req.UUID)
return depProfileResponse{Profile: profile, Err: err}, nil
}
}
type DeviceDTO struct {
SerialNumber string `json:"serial_number"`
UDID string `json:"udid"`
@@ -137,3 +191,32 @@ type profilesResponse struct {
}
func (r profilesResponse) error() error { return r.Err }
type depAccountInforequest struct{}
type depAccountInfoResponse struct {
*dep.Account
Err error `json:"err,omitempty"`
}
func (r depAccountInfoResponse) error() error { return r.Err }
type depDeviceDetailsRequest struct {
Serials []string `json:"serials"`
}
type depDeviceDetailsResponse struct {
*dep.DeviceDetailsResponse
Err error `json:"err,omitempty"`
}
func (r depDeviceDetailsResponse) error() error { return r.Err }
type depProfileRequest struct {
UUID string `json:"uuid"`
}
type depProfileResponse struct {
*dep.Profile
Err error `json:"err,omitempty"`
}
func (r depProfileResponse) error() error { return r.Err }

View File

@@ -10,6 +10,7 @@ import (
"github.com/boltdb/bolt"
"github.com/micromdm/dep"
"github.com/micromdm/micromdm/blueprint"
"github.com/micromdm/micromdm/crypto"
"github.com/micromdm/micromdm/device"
@@ -37,9 +38,11 @@ type Service interface {
GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error)
GetBlueprints(ctx context.Context, opt GetBlueprintsOption) ([]blueprint.Blueprint, error)
GetProfiles(ctx context.Context, opt GetProfilesOption) ([]profile.Profile, error)
DEPService
}
type ListService struct {
DEPClient dep.Client
Devices *device.DB
Blueprints *blueprint.DB
Profiles *profile.DB

25
core/list/service_dep.go Normal file
View File

@@ -0,0 +1,25 @@
package list
import (
"context"
"github.com/micromdm/dep"
)
type DEPService interface {
GetDEPAccountInfo(ctx context.Context) (*dep.Account, error)
GetDEPDevice(ctx context.Context, serials []string) (*dep.DeviceDetailsResponse, error)
GetDEPProfile(ctx context.Context, uuid string) (*dep.Profile, error)
}
func (svc *ListService) GetDEPAccountInfo(ctx context.Context) (*dep.Account, error) {
return svc.DEPClient.Account()
}
func (svc *ListService) GetDEPDevice(ctx context.Context, serials []string) (*dep.DeviceDetailsResponse, error) {
return svc.DEPClient.DeviceDetails(serials)
}
func (svc *ListService) GetDEPProfile(ctx context.Context, uuid string) (*dep.Profile, error) {
return svc.DEPClient.FetchProfile(uuid)
}

View File

@@ -12,10 +12,13 @@ import (
)
type HTTPHandlers struct {
ListDevicesHandler http.Handler
GetDEPTokensHandler http.Handler
GetBlueprintsHandler http.Handler
GetProfilesHandler http.Handler
ListDevicesHandler http.Handler
GetDEPTokensHandler http.Handler
GetBlueprintsHandler http.Handler
GetProfilesHandler http.Handler
GetDEPAccountInfoHandler http.Handler
GetDEPProfileHander http.Handler
GetDEPDeviceDetailsHandler http.Handler
}
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
@@ -41,6 +44,24 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
decodeGetProfilesRequest,
encodeResponse,
opts...),
GetDEPAccountInfoHandler: httptransport.NewServer(
endpoints.GetDEPAccountInfoEndpoint,
decodeDepAccountInfoRequest,
encodeResponse,
opts...,
),
GetDEPDeviceDetailsHandler: httptransport.NewServer(
endpoints.GetDEPDeviceEndpoint,
decodeDepDeviceDetailsRequest,
encodeResponse,
opts...,
),
GetDEPProfileHander: httptransport.NewServer(
endpoints.GetDEPProfileEndpoint,
decodeDEPProfileRequest,
encodeResponse,
opts...,
),
}
return h
}
@@ -78,6 +99,26 @@ func decodeGetProfilesRequest(ctx context.Context, r *http.Request) (interface{}
return req, nil
}
func decodeDepAccountInfoRequest(ctx context.Context, r *http.Request) (interface{}, error) {
return nil, nil
}
func decodeDepDeviceDetailsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var request depDeviceDetailsRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func decodeDEPProfileRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var request depProfileRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func errorDecoder(r *http.Response) error {
var w errorWrapper
if err := json.NewDecoder(r.Body).Decode(&w); err != nil {
@@ -158,3 +199,30 @@ func DecodeGetProfilesResponse(_ context.Context, r *http.Response) (interface{}
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeDEPAccountInfoResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp depAccountInfoResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeDEPDeviceDetailsReponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp depDeviceDetailsResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeDEPProfileResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp depProfileResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}

View File

@@ -210,9 +210,13 @@ func serve(args []string) error {
ConnectEndpoint: connectEndpoint,
}
dc, err := sm.depClient()
if err != nil {
stdlog.Fatalf("creating DEP client %s\n", err)
}
var listsvc list.Service
{
listsvc = &list.ListService{Devices: devDB, DB: sm.db, Blueprints: bpDB, Profiles: profDB}
listsvc = &list.ListService{DEPClient: dc, Devices: devDB, DB: sm.db, Blueprints: bpDB, Profiles: profDB}
}
var listDevicesEndpoint endpoint.Endpoint
{
@@ -220,15 +224,18 @@ func serve(args []string) error {
}
listEndpoints := list.Endpoints{
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPTokensEndpoint: list.MakeGetDEPTokensEndpoint(listsvc),
GetBlueprintsEndpoint: list.MakeGetBlueprintsEndpoint(listsvc),
GetProfilesEndpoint: list.MakeGetProfilesEndpoint(listsvc),
ListDevicesEndpoint: listDevicesEndpoint,
GetDEPTokensEndpoint: list.MakeGetDEPTokensEndpoint(listsvc),
GetBlueprintsEndpoint: list.MakeGetBlueprintsEndpoint(listsvc),
GetProfilesEndpoint: list.MakeGetProfilesEndpoint(listsvc),
GetDEPAccountInfoEndpoint: list.MakeGetDEPAccountInfoEndpoint(listsvc),
GetDEPProfileEndpoint: list.MakeGetDEPProfileEndpoint(listsvc),
GetDEPDeviceEndpoint: list.MakeGetDEPDeviceDetailsEndpoint(listsvc),
}
var applysvc apply.Service
{
applysvc = &apply.ApplyService{Blueprints: bpDB, DB: sm.db, Profiles: profDB}
applysvc = &apply.ApplyService{DEPClient: dc, Blueprints: bpDB, DB: sm.db, Profiles: profDB}
}
var applyBlueprintEndpoint endpoint.Endpoint
@@ -241,10 +248,16 @@ func serve(args []string) error {
applyProfileEndpoint = apply.MakeApplyProfileEndpoint(applysvc)
}
var defineDEPProfileEndpoint endpoint.Endpoint
{
defineDEPProfileEndpoint = apply.MakeDefineDEPProfile(applysvc)
}
applyEndpoints := apply.Endpoints{
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
ApplyDEPTokensEndpoint: apply.MakeApplyDEPTokensEndpoint(applysvc),
ApplyProfileEndpoint: applyProfileEndpoint,
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
ApplyDEPTokensEndpoint: apply.MakeApplyDEPTokensEndpoint(applysvc),
ApplyProfileEndpoint: applyProfileEndpoint,
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
}
applyAPIHandlers := apply.MakeHTTPHandlers(ctx, applyEndpoints, connectOpts...)
@@ -278,6 +291,10 @@ func serve(args []string) error {
r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.BlueprintHandler)).Methods("PUT")
r.Handle("/v1/profiles", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetProfilesHandler)).Methods("GET")
r.Handle("/v1/profiles", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.ProfileHandler)).Methods("PUT")
r.Handle("/v1/dep/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPDeviceDetailsHandler)).Methods("GET")
r.Handle("/v1/dep/account", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPAccountInfoHandler)).Methods("GET")
r.Handle("/v1/dep/profiles", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPProfileHander)).Methods("GET")
r.Handle("/v1/dep/profiles", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DefineDEPProfileHandler)).Methods("POST")
}
if *flRepoPath != "" {
@@ -315,7 +332,8 @@ func serve(args []string) error {
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig // block on signal then gracefully shutdown.
ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
errs <- srv.Shutdown(ctx)
}()