NOISSUE - Add new endpoint to retrieve configuration to be used as a template. (#1242)

* add provision service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix code style

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test for provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* extra line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* return map[string]interface instead of interface

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
This commit is contained in:
Mirko Teodorovic 2020-09-29 10:25:26 +02:00 committed by GitHub
parent 8ea26c5ab7
commit 9ed5f8334f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 107 additions and 14 deletions

View File

@ -9,7 +9,7 @@ import (
func doProvision(svc provision.Service) endpoint.Endpoint { func doProvision(svc provision.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) { return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(addThingReq) req := request.(provisionReq)
if err := req.validate(); err != nil { if err := req.validate(); err != nil {
return nil, err return nil, err
} }
@ -34,3 +34,13 @@ func doProvision(svc provision.Service) endpoint.Endpoint {
} }
} }
func getMapping(svc provision.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(mappingReq)
if err := req.validate(); err != nil {
return nil, err
}
return svc.Mapping(req.token)
}
}

View File

@ -24,10 +24,10 @@ func (lm *loggingMiddleware) Provision(token, name, externalID, externalKey stri
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method provision for token: %s and things: %v took %s to complete", token, res.Things, time.Since(begin)) message := fmt.Sprintf("Method provision for token: %s and things: %v took %s to complete", token, res.Things, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err))
return return
} }
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now()) }(time.Now())
return lm.svc.Provision(token, name, externalID, externalKey) return lm.svc.Provision(token, name, externalID, externalKey)
@ -37,11 +37,24 @@ func (lm *loggingMiddleware) Cert(token, thingID, duration string, keyBits int)
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method cert for token: %s and thing: %v took %s to complete", token, thingID, time.Since(begin)) message := fmt.Sprintf("Method cert for token: %s and thing: %v took %s to complete", token, thingID, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err))
return return
} }
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now()) }(time.Now())
return lm.svc.Cert(token, thingID, duration, keyBits) return lm.svc.Cert(token, thingID, duration, keyBits)
} }
func (lm *loggingMiddleware) Mapping(token string) (res map[string]interface{}, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method mapping for token: %s took %s to complete", token, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.Mapping(token)
}

View File

@ -1,14 +1,25 @@
package api package api
type addThingReq struct { type provisionReq struct {
token string token string
Name string `json:"name"` Name string `json:"name"`
ExternalID string `json:"external_id"` ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key"` ExternalKey string `json:"external_key"`
} }
func (req addThingReq) validate() error { func (req provisionReq) validate() error {
if req.ExternalID == "" || req.ExternalKey == "" { if req.ExternalID == "" || req.ExternalKey == "" {
return errMalformedEntity
}
return nil
}
type mappingReq struct {
token string
}
func (req mappingReq) validate() error {
if req.token == "" {
return errUnauthorized return errUnauthorized
} }
return nil return nil

View File

@ -21,17 +21,17 @@ func TestValidate(t *testing.T) {
err: nil, err: nil,
}, },
"external id for device empty": { "external id for device empty": {
err: errUnauthorized, err: errMalformedEntity,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
req := addThingReq{ req := provisionReq{
ExternalID: tc.ExternalID, ExternalID: tc.ExternalID,
ExternalKey: tc.ExternalKey, ExternalKey: tc.ExternalKey,
} }
err := req.validate() err := req.validate()
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", desc, err, tc.err)) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", desc, tc.err, err))
} }
} }

View File

@ -37,7 +37,14 @@ func MakeHandler(svc provision.Service) http.Handler {
r.Post("/mapping", kithttp.NewServer( r.Post("/mapping", kithttp.NewServer(
doProvision(svc), doProvision(svc),
decodeThingCreation, decodeProvisionRequest,
encodeResponse,
opts...,
))
r.Get("/mapping", kithttp.NewServer(
getMapping(svc),
decodeMappingRequest,
encodeResponse, encodeResponse,
opts..., opts...,
)) ))
@ -66,12 +73,12 @@ func encodeResponse(_ context.Context, w http.ResponseWriter, response interface
return json.NewEncoder(w).Encode(response) return json.NewEncoder(w).Encode(response)
} }
func decodeThingCreation(_ context.Context, r *http.Request) (interface{}, error) { func decodeProvisionRequest(_ context.Context, r *http.Request) (interface{}, error) {
if r.Header.Get("Content-Type") != contentType { if r.Header.Get("Content-Type") != contentType {
return nil, errUnsupportedContentType return nil, errUnsupportedContentType
} }
req := addThingReq{token: r.Header.Get("Authorization")} req := provisionReq{token: r.Header.Get("Authorization")}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err return nil, err
} }
@ -79,6 +86,16 @@ func decodeThingCreation(_ context.Context, r *http.Request) (interface{}, error
return req, nil return req, nil
} }
func decodeMappingRequest(_ context.Context, r *http.Request) (interface{}, error) {
if r.Header.Get("Content-Type") != contentType {
return nil, errUnsupportedContentType
}
req := mappingReq{token: r.Header.Get("Authorization")}
return req, nil
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) { func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)

View File

@ -20,6 +20,7 @@ const (
) )
var ( var (
ErrUnauthorized = errors.New("unauthorized access")
ErrFailedToCreateToken = errors.New("failed to create access token") ErrFailedToCreateToken = errors.New("failed to create access token")
ErrEmptyThingsList = errors.New("things list in configuration empty") ErrEmptyThingsList = errors.New("things list in configuration empty")
ErrEmptyChannelsList = errors.New("channels list in configuration is empty") ErrEmptyChannelsList = errors.New("channels list in configuration is empty")
@ -47,6 +48,11 @@ type Service interface {
// - whitelist Thing in Bootstrap configuration == connect Thing to Channels // - whitelist Thing in Bootstrap configuration == connect Thing to Channels
Provision(token, name, externalID, externalKey string) (Result, error) Provision(token, name, externalID, externalKey string) (Result, error)
// Mapping returns current configuration used for provision
// useful for using in ui to create configuration that matches
// one created with Provision method.
Mapping(token string) (map[string]interface{}, error)
// Certs creates certificate for things that communicate over mTLS // Certs creates certificate for things that communicate over mTLS
// A duration string is a possibly signed sequence of decimal numbers, // A duration string is a possibly signed sequence of decimal numbers,
// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". // each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
@ -81,6 +87,14 @@ func New(cfg Config, sdk SDK.SDK, logger logger.Logger) Service {
} }
} }
// Mapping retrieves current configuration
func (ps *provisionService) Mapping(token string) (map[string]interface{}, error) {
if _, err := ps.sdk.User(token); err != nil {
return map[string]interface{}{}, errors.Wrap(ErrUnauthorized, err)
}
return ps.conf.Bootstrap.Content, nil
}
// Provision is provision method for creating setup according to // Provision is provision method for creating setup according to
// provision layout specified in config.toml // provision layout specified in config.toml
func (ps *provisionService) Provision(token, name, externalID, externalKey string) (res Result, err error) { func (ps *provisionService) Provision(token, name, externalID, externalKey string) (res Result, err error) {
@ -118,6 +132,7 @@ func (ps *provisionService) Provision(token, name, externalID, externalKey strin
res.Error = err.Error() res.Error = err.Error()
return res, errors.Wrap(ErrFailedThingCreation, err) return res, errors.Wrap(ErrFailedThingCreation, err)
} }
// Get newly created thing (in order to get the key). // Get newly created thing (in order to get the key).
th, err = ps.sdk.Thing(thID, token) th, err = ps.sdk.Thing(thID, token)
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ paths:
summary: Adds new device to proxy summary: Adds new device to proxy
description: Adds new device to proxy description: Adds new device to proxy
tags: tags:
- Thing to proxy - provision
parameters: parameters:
- $ref: "#/parameters/Authorization" - $ref: "#/parameters/Authorization"
- in: body - in: body
@ -38,8 +38,35 @@ paths:
description: Created description: Created
400: 400:
description: Failed due to malformed JSON. description: Failed due to malformed JSON.
403:
description: Unauthorized.
500: 500:
description: Unexpected server-side error ocurred. description: Unexpected server-side error ocurred.
get:
consumes:
- application/json
summary: Gets current mapping.
description: Gets current mapping. This can be used in UI
so that when bootstrap config is created from UI matches
configuration created with provision service.
tags:
- provision
parameters:
- $ref: "#/parameters/Authorization"
responses:
200:
schema:
$ref: "#/definitions/Content"
description: retrieved
403:
description: Unauthorized.
500:
description: Unexpected server-side error ocurred.
definitions:
Content:
type: object
parameters: parameters:
Authorization: Authorization:
name: Authorization name: Authorization