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 {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(addThingReq)
req := request.(provisionReq)
if err := req.validate(); err != nil {
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) {
message := fmt.Sprintf("Method provision for token: %s and things: %v took %s to complete", token, res.Things, time.Since(begin))
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
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
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) {
message := fmt.Sprintf("Method cert for token: %s and thing: %v took %s to complete", token, thingID, time.Since(begin))
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
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
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
type addThingReq struct {
type provisionReq struct {
token string
Name string `json:"name"`
ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key"`
}
func (req addThingReq) validate() error {
func (req provisionReq) validate() error {
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 nil

View File

@ -21,17 +21,17 @@ func TestValidate(t *testing.T) {
err: nil,
},
"external id for device empty": {
err: errUnauthorized,
err: errMalformedEntity,
},
}
for desc, tc := range cases {
req := addThingReq{
req := provisionReq{
ExternalID: tc.ExternalID,
ExternalKey: tc.ExternalKey,
}
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(
doProvision(svc),
decodeThingCreation,
decodeProvisionRequest,
encodeResponse,
opts...,
))
r.Get("/mapping", kithttp.NewServer(
getMapping(svc),
decodeMappingRequest,
encodeResponse,
opts...,
))
@ -66,12 +73,12 @@ func encodeResponse(_ context.Context, w http.ResponseWriter, response interface
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 {
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 {
return nil, err
}
@ -79,6 +86,16 @@ func decodeThingCreation(_ context.Context, r *http.Request) (interface{}, error
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) {
w.Header().Set("Content-Type", contentType)

View File

@ -20,6 +20,7 @@ const (
)
var (
ErrUnauthorized = errors.New("unauthorized access")
ErrFailedToCreateToken = errors.New("failed to create access token")
ErrEmptyThingsList = errors.New("things list in configuration 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
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
// 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".
@ -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 layout specified in config.toml
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()
return res, errors.Wrap(ErrFailedThingCreation, err)
}
// Get newly created thing (in order to get the key).
th, err = ps.sdk.Thing(thID, token)
if err != nil {

View File

@ -15,7 +15,7 @@ paths:
summary: Adds new device to proxy
description: Adds new device to proxy
tags:
- Thing to proxy
- provision
parameters:
- $ref: "#/parameters/Authorization"
- in: body
@ -38,8 +38,35 @@ paths:
description: Created
400:
description: Failed due to malformed JSON.
403:
description: Unauthorized.
500:
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:
Authorization:
name: Authorization