Compare commits

..

186 Commits

Author SHA1 Message Date
Nikola Marčetić 2080ee8afa Added changelog for 0.10.0 release (#984)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2019-12-17 17:32:27 +01:00
Dušan Borovčanin 9c1743c080 Fix MQTT protobuf file name (#981)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-12-16 18:05:59 +01:00
Dušan Borovčanin 9f37927dec MF-932 - User API keys (#941)
* Add inital Auth implementation

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Extract IssuedAt on transport layer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add token type

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Auth service URL in Things service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add User Keys revocation check

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unused tracing methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Key retrival and parsing

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unused code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Increase test coverage

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix compose files

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos

Simplify tests.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos and remove useless comments

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename Auth to Authn

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename database.go to tracin.go

A new name (`tracing.go`) describes better the purpose of the file.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Increase test coverage

Fix typo.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Increase test coverage

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove token from Users service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix identify login keys

Rename token parsing method.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Extract tokenizer to interface

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove pointer time

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use pointer for expiration time in response

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use uppercase N

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unnecessary email check

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Cleanup unused code and env vars

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename tokenizer field

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use slices and named fields in test cases

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update AuthN keys naming

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove package-lock.json changes

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove Secret from issuing request

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-12-16 16:22:09 +01:00
Manuel Imperiale bdeb7711ce NOISSUE - Use opcua server timestamp in opcua-adapter messages (#980)
* NOISSUE - Use opcua server timestamp in opcua-adapter messages

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rename pubsub -> subscribe

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-12-16 16:06:37 +01:00
Dušan Borovčanin e960004b44 Simplify CI script (#979)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-12-10 18:53:14 +01:00
Manuel Imperiale 76b68e10a8 NOISSUE - Add opcua-adapter conn route-map, use ServerURI and NodeID (#975)
* NOISSUE - Add opcua-adapter conn route-map, use ServerURI and NodeID

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add dynamic subscription

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-12-09 23:16:18 +01:00
Drasko DRASKOVIC 5120a71595 Move docs to a separate repo (#976)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-12-04 13:31:23 +01:00
Dušan Borovčanin 8c4da85039 MF-950 - Runtime error in normalizer - CBOR SenML (#974)
* Add Transformer tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix readers and writers

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix README typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Tidy vendor

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove link field from docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-11-29 20:47:28 +01:00
Manuel Imperiale 237514ee33 NOISSUE - Support multiple types values in opcua-adapter (#973)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-28 18:08:43 +01:00
Aleksandar Novaković 263108cc85 Migrate from dep to go modules (#971)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-11-27 15:29:34 +01:00
Manuel Imperiale 277342b8ca NOISSUE - Add Node IdentifierType config in opcua-adapter (#967)
* NOISSUE - Add Node IdentifierType config in opcua-adapter

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm Read from subscribeToOpcServers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-26 20:07:04 +01:00
Manuel Imperiale 87dd91e328 NOISSUE - Remove messages limit in influxdb-reader (#968)
* NOISSUE - Remove messages limit in influxdb-reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm too large limit test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-26 18:58:45 +01:00
Manuel Imperiale 6da650645f NOISSUE - Fix opcua-adapter events warnings (#965)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-25 17:53:18 +01:00
Manuel Imperiale 8602185b74 NOISSUE - Fix opcua-adapter events decode (#951)
* NOISSUE - Enable multi OPC-UA Subscriptions

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Fix opcua-adapter events decode

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use a config file to subscribe to multiple nodes

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add subscription config file

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use Mainflux errors package

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-25 14:14:57 +01:00
Drasko DRASKOVIC b8ac6ca2bf Fix subtopic handling in VerneMQ (#962)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-11-24 18:21:00 +01:00
Nick Neisen 76908d7c8b MF-898 - Add bulk connect to CLI and SDK (#956)
* Add bulk connect to CLI and SDK

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add test for bulk connect

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update docs

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add file example

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add JSON example to CLI docs

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change some value datatypes back

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Use constants for file extensions

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-21 12:29:37 +01:00
Ivan Milošević 0ab627730f MF-538 - Improve logging and API errors (#866)
* user service - wraping errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* wrapping more errors
unwrap only wrapped errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add internal database error
Wrap internal database error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Wrap user not found error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Wrapping errors in idp and hasher

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Use error.Is for testing errors in Identify test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Get wraper from wrapped errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Switch order of wrapping errors
Remove dead code (comments)

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* assert true in tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change comparing errors in tests (assert.True)

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add errorRes structure to API responses in body in things service

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* resolve conflicts after rebasing

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Create errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* implement new errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* return copyright comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove changes from .gitignore

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Move logging to encode errors
Comment exported vars and methods
Formatting

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Login function returns errors.Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify login tests to meet login returning errors.Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change parameter in Wrapper to interface Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* implement new error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests to use new Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix Login

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove unnecessary errir casting

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* new error interface implementation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* check if Error is empty in registrationEndpoint

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add Empty factory function
Use new Empty factory function
Use isEmpty method

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Lose else in encodeError

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove *json.UnmarshalTypeError and *json.SyntaxError types from encodeError type switch

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix nil error value in jwtIdentityProvider

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix gprc to use new error package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* rename receiver in errors package
grpc errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove debugging code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Resolving conflicts after rebase

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove comment

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove Empty from custom error
Implement custom error on new methods

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* WIP tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove wrap from Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* password-change related tests
remove debug code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove dead code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Move all errors casting to errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix comment in error package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change struct pointer to interface in package methods

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* resolving reviews

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix return in database.go

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix copyright header

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix comment in hasher

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-11-20 14:43:41 +01:00
Manuel Imperiale 1b427f4396 NOISSUE - Fix Update User (#959)
* NOISSUE - Fix Update User

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm duplicated test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-20 14:03:17 +01:00
Manuel Imperiale 52c4d4a824 NOISSUE - Fix make dockers (#957)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-20 09:24:58 +01:00
Nick Neisen ced90b21a4 Add dev_ back to make dockers_dev (#955)
Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-19 17:21:13 +01:00
Manuel Imperiale ac3ff5221a NOISSUE - Remove Elm UI (#953)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-19 09:31:41 +01:00
Mirko Teodorovic 5e35cbe06b NOISSUE - Fix docs (#952)
* fix missing timeout

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

* upd tools readme.md

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

* upd tools readme.md

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

* add mising Referer header required for pass reset request

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-11-16 16:20:29 +01:00
Nick Neisen 894e1b83ec MF-898 - Add bulk connections endpoint (#948)
* Change connect endpoint to use bulk connections

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Add documentation for bulk connect

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Update service and postgres for multiple channels

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update bulk connect endpoint

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Fix missed test

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Improve test code coverage

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change methods to use shared data type

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Fix provisioning example

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change depreciation messages to specify v1.0.0

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Switch singluar log to plural

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-11-15 18:44:31 +01:00
Manuel Imperiale afeec81a5e MF-916 - Fix Things and Channels counters (#947)
* MF-916 - Fix Things and Channels counters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use NamedQueryContext to count rows

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Create single helper total() func

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm useless LOWER

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert previous commit

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-13 15:26:57 +01:00
Nick Neisen 67d518821a MF-898 - Change thing's service to use bulk connect (#946)
* Change thing's service to use bulk connect

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Move owner check outside of loop

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change invalid user to unauthorized access error

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-12 01:24:29 +01:00
Nick Neisen 5446d88abf MF-898 - Add transactions to postgres connect (#940)
* Add transactions to postgres connect

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change to use shorthand notation

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-11 14:22:30 +01:00
Mirko Teodorovic 9e8dd4b149 MF-942 - Fix email template logic (#944)
* fix email template

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

* rename resetPasswEmail.tmpl

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

* add env var

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

* rename resetPasswEmail.tmpl

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

* rename resetPasswEmail.tmpl

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

* upd docs

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

* upd docs

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

* upd docs

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

* remove not needed file

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

* move email tmpl

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

* move email tmpl

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

* move email tmpl

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

* variable for tmpl

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

* fix ident and caps

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-11-11 12:13:48 +01:00
Mirko Teodorovic e735e5c01b add missing user service tests (#945)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-11-10 02:47:14 +01:00
Manuel Imperiale 841b943718 NOISSUE - Fix HTTP header for Things and Channels creation (#939)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-07 13:57:47 +01:00
Manuel Imperiale 3bfdcfe83c NOISSUE - Fix docker ui image name (#938)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-06 13:44:58 +01:00
Dušan Borovčanin 66f2332674 Remove Normalizer service from compose (#937)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-11-05 12:34:36 +01:00
Dušan Borovčanin f50335ab33 MF-919 - Mainflux message updates (#924)
* Remove RawMessage

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove Normalizer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Replace normalizer with senml-transformer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename Transformer interface and package

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove SenML transformer service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove SenML Protobuf support

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix readers

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix writers tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Refactor tests and remove normalizer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Reanme Service interface to Transformer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use msg instead of rawmsg

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove rawMsg from Aedes code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix VerneMQ files

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove RawMessage code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix missing subtopic return

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove remaining RawMessage reference

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix formatting

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix readers and writers tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename SenML transformer variables

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix readers and writers tests constants

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-11-05 11:57:16 +01:00
Manuel Imperiale d7dc836714 NOISSUE - Fix lora-adapter (#936)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-05 11:49:39 +01:00
Manuel Imperiale 887542aab3 NOISSUE - Fix lora creation events (#933)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-05 10:23:30 +01:00
Drasko DRASKOVIC 1d2c8b25a1 NOISSUE - Remove ARM multi-arch images (#929)
* emove ARM multi-arch

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix tabs

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Remove arch tagging

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix MQTT build

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-11-05 10:12:49 +01:00
Nick Neisen 1a31ecd044 MF-906 - Change single creation endpoints to use bulk service calls (#927)
* Change single endpoints to use bulk creation

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Remove single creation from thing's service

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Remove single save from thing's postgres

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change BulkSave to Save

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change service calls to use variadic parameters

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change postgres things to use variadic functions

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add depreciation warnings

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-04 21:14:17 +01:00
Manuel Imperiale 28a176ac1d MF-922 - Add UpdateUser endpoint (#923)
* MF-922 - Add User Update endpoint

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert Update function

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix Things swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* update Things swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix users swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix method name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use User instead of metadata

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-31 16:36:19 +01:00
Dušan Borovčanin 8be2516321 MF-780 - Use Normalizer as a lib (#915)
* Use Normalizer as a lib

To normalize messages on the consumer side, Normalizer is moved
to the internal pkgs. Writers being message consumers are modified to
do message normalization instead of subscribing to normalized messages
subject.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix logging middleware for readers and writers

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove normalizer interface

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use Normalizer in writers

As we agreed on #919, we'll use normalizer as an interface and provide
the default SenML implementation. Because of that, Normalizer is removed
from `internal` and we'll use the project structure proposed in the
aforementioned issue.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unused batch settings from influxDB reader

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Move Normalizer service to `addons`.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename channels input topic

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Noramlizer docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove commented code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update readers logging

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update addons docker-compose files

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update topcis explanations

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-31 14:04:47 +01:00
Drasko DRASKOVIC 380af878d2 Fix doc for ENV vars in README (#920)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-29 21:28:59 +01:00
Drasko DRASKOVIC da4fc62440 NOISSUE - Switch to grpcbox for VerneMQ (#914)
* Switch to grpcbox

Use grpcbox as a gRPC client library, because
it is much better maintained.

Shows good performance and generation of protobuf
files is simple and automatic.

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix typo

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-29 20:35:09 +01:00
Nick Neisen 426813cec4 Change channels to chs (#918)
Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-29 19:06:48 +01:00
Nick Neisen 8f3dff837b MF-484 - Add bulk provisioning for things and channels (#889)
* Add provisioning to postgres

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add provisioning to things and channels service

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add JSON provisioning endpoint to things API

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add provisioning to SDK and CLI

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update docs

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Remove response location and adjust channelsFromFile

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change naming from 'provison' to 'bulkCreate'

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add gocsv to vendors folder

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update "bulk" naming

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Move CSV to CLI and remove gocsv dependancy

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update docs and responses

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change AddThings to be CreateThings

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Improve test coverage

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Fixes after review

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-29 12:59:54 +01:00
Nick Neisen 5f5d7e4cfa MF-899 - Update README and official docs (#910)
* Update root README.md

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Update official documents

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Move docker template version location

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Move template to dev guide and remove bold

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Remove double spaces after periods

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Change repo to repository

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-29 10:22:41 +01:00
Manuel Imperiale fec058c614 NOISSUE - Fix Redis envars (#903)
* NOISSUE - Fix Redis envars

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix url

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-28 09:49:34 +01:00
Drasko DRASKOVIC 613e75f7ee Add disconnect on gen_server terminate() (#913)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-27 17:57:43 +01:00
Drasko DRASKOVIC 7499f8deea Fix compilation (#911)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-26 17:46:23 +02:00
Dušan Borovčanin 02fd492105 Revert "NOISSUE - Make event sourcing optional (#907)" (#909)
This reverts commit d309973a23.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-25 13:57:41 +02:00
Aleksandar Novaković d309973a23 NOISSUE - Make event sourcing optional (#907)
* Add ES flat to the things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add env var for optional ES

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-10-25 00:12:31 +02:00
Manuel Imperiale e45e0ab307 NOISSUE - Fix InfluxDB env vars (#908)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-24 18:52:53 +02:00
Manuel Imperiale dea7c67ddb MF-890 - Add OPC-UA docs (#904)
* MF-890 - Add OPC-UA docs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add opcua.md in mkdocs.yml

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-24 09:49:11 +02:00
Dušan Borovčanin 2e664ffe10 Fix Elm version for ARM Docker images (#905)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-23 18:08:38 +02:00
Dušan Borovčanin cdf9347b01 NOISSUE - Update Protobuf version (#902)
* Update Protobuf version

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update gRPC version

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-22 20:33:44 +02:00
Manuel Imperiale e16a025fba MF-886 - Add OPC-UA adapter (#878)
* NOISSUE- Add OPC-UA adapter

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add opc-adapter PoC, docker and vendor

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Convert OPC messages to SenML

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add gopcua package

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* lora-adapter typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add OPC Reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update copyright headers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add opc config

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add all opc envars in the config

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Config typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add route map

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use opcua package instead of opc

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix OPCUA typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm MQTT sub

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Move interefaces to root

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix revieews and typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update Gopkg.toml

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add all envars into .env

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-22 17:44:19 +02:00
Dušan Borovčanin 8ab0b08c39 Fix Elm version in Dockerfile (#901)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-22 17:30:09 +02:00
Mirko Teodorovic f4f938a730 MF-532 - Password reset (#873)
* add users metadata

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

* add users metadata

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

* add users metadata

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

* add users metadata

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

* add users metadata

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

* add users metadata

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

* add users metadata

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

* add users metadata

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add metadata to users

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

* add default value for metadata

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

* add default value for metadata

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

* add default value for metadata

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

* add default value for metadata

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

* add default value for metadata

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

* add default value for metadata

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

* when metadata is not set dont save 'null' string

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

* when metadata is not set dont save 'null' string

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

* when metadata is not set dont save 'null' string

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

* change metadata type, add error handling

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

* change metadata type, add error handling

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

* change metadata type, add error handling

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

* remove extra char

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

* remove extra char

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

* remove extra char

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

* few small fixes

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

* few small fixes

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

* few small fixes

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

* fix identityRes

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

* fix identityRes

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

* fix identityRes

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

* add users metadata

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

* add users metadata

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

* add users metadata

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* mail

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

* mail

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

* reset request endpoint

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

* reset request endpoint

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

* reset request endpoint

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

* add token

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

* add token

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

* add token

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

* add token methods

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

* add token methods

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

* add token methods

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

* adding token endpoints

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

* adding token endpoints

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

* adding token endpoints

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

* adding reset passw endpoint

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

* adding reset passw endpoint

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

* adding reset passw endpoint

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

* add logic for token verifying

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

* add logic for token verifying

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

* add logic for token verifying

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

* remove mail from main

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

* remove mail from main

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

* remove mail from main

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

* token and passwd  update logic

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

* token and passwd  update logic

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

* token and passwd  update logic

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

* reorganize mailing code

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

* add comments for password reset flow

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

* add comments for password reset flow

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

* change struct members to private

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

* small changes

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

* small changes

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

* small changes

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

* add space

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

* add space

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

* add space

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

* revert back changes used for testing

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

* revert back changes used for testing

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

* revert back changes used for testing

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

* reorganize endpoints, extract host for link from Referer

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

* reorganize endpoints, extract host for link from Referer

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

* adding swagger docs for reset passw

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

* adding swagger docs for reset passw

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

* fix imports and some typos

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

* fix imports and some typos

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

* adding swagger docs for reset passw

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

* reorganize endpoints, extract host for link from Referer

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

* reorganize endpoints, extract host for link from Referer

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

* addint test and update swagger for pass reset

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

* addint test and update swagger for pass reset

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

* addint test and update swagger for pass reset

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

* adding test for endpoint

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

* adding test for endpoint

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

* adding test for endpoint

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

* adding test for endpoint

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

* adding endpoint test

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

* adding endpoint test

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

* adding endpoint test

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

* change token generation

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

* change token generation

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

* change token generation

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

* reorganize and change token gen

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

* reorganize and change token gen

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

* reorganize and change token gen

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

* addint token and mail

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

* adding token env

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

* adding token env

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

* fix error reporting

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

* fix some comments and update readme

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

* fix some comments and update readme

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

* fix some comments and update readme

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

* update readme

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

* fix problmes due merge

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

* fix problmes due merge

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

* fix typos

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

* fix typos

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

* add trusted certificates

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

* add endpoint for password change of currently authenticated user

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

* add env variable for conf reset endpoint

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

* add metadata to users

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

* add default value for metadata

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

* add default value for metadata

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

* when metadata is not set dont save 'null' string

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

* change metadata type, add error handling

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

* remove extra char

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

* few small fixes

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

* fix identityRes

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

* add users metadata

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* password reset, sketching

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

* reset request endpoint

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

* adding token endpoints

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

* adding reset passw endpoint

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

* add logic for token verifying

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

* remove mail from main

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

* small changes

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

* add space

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

* revert back changes used for testing

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

* reorganize endpoints, extract host for link from Referer

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

* reorganize endpoints, extract host for link from Referer

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

* addint test and update swagger for pass reset

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

* adding endpoint test

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

* fix some comments and update readme

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

* add env variable for conf reset endpoint

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

* fix tests

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

* resolving some style comments

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

* minor fixes due to bad merge

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

* fix problem with unsigned commits

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

* fix problem with unsigned commits

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

* fix call to users.New

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

* fix call to users.New

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

* rename file

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

* rename to email.go

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

* additional comments

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

* additional comments

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

* refactor email util

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

* refactor email util

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

* refactor email util

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

* refactor email util

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

* remove debug bin

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

* add driver conf for mail

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

* refactor email

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

* refactor email

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

* fix failing tests

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

* add testify

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

* small fix

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

* add token conf

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

* small fix

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

* small fix

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

* configurable  email template

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

* fix missing var

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

* add env for email template file

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

* add env for email template file

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

* fix tests

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

* fix test

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

* fix test

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

* revert to master

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

* remove dev container

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

* small fixes, typos, namings

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

* fix typo

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

* resolving comments

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

* resolving comments

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

* resolving comments

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

* resolving comments

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

* resolving comments

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

* add line

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

* return err from email New()

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

* change MF_TOKEN_RESET_ENDPOINT

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

* add and remove env vars

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

* separate password logic into two methods Change and Reset

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

* update comments

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

* remove blank line

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

* update docs

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

* dont use camel case

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

* small fix

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

* reorganize email template for passw reset

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

* reorganize email template for passw reset

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-10-22 14:53:14 +02:00
Nick Neisen 6687a738ce MF-785 - Change CanAccess to CanAccessByKey (#894)
* Change CanAccess to CanAccessByKey for things

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change CanAccess in remaining occurances

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Regenerate generated files

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Generate pb.go files with protoc 3.6.1

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-10-21 23:24:45 +02:00
Manuel Imperiale 1da48afc4c NOISSUE - fix security doc (#897)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-21 15:02:21 +02:00
Manuel Imperiale ddbf172ed5 NOISSUE - Fix typo in docs and README (#891)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-17 22:15:45 +02:00
Dušan Borovčanin df2cb80f12 Fix Nginx mTLS configuration (#885)
Sync mTLS and TLS config logs.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-15 16:39:46 +02:00
Drasko DRASKOVIC b3ce48aca6 NOISSUE - Add MQTT UserName check on register and InstanceId in Redis (#884)
* Add InstanceId and verify UserName

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix thingID check (compare with UserName)

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-14 15:09:53 +02:00
Jovan Kostovski 351b04cd2e Add MQTT troubleshooting section (#882)
Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-10-14 09:18:29 +02:00
Dušan Borovčanin 23b421bdc5 Fix provision tool connect error handling (#879)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-08 19:31:13 +02:00
Nick Neisen 93d939ea52 MF-875 - Add tracing to official documentation (#877)
* Tracing docs without example

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Add tracing example using provisioning traces

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add link to Jaeger offical website

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Remove extra http:// from UI URL

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-07 17:49:29 +02:00
Nick Neisen 66487eda42 MF-788 - Remove date and minimize copyright comments (#876)
* Update copyright comment for go files

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Update copyright in assortment of file types

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Remove missed copyright date

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-07 16:14:47 +02:00
Nick Neisen 23dff53a08 MF-787 - Add tags to user, thing, and channel spans (#869)
* Add span tags to tracing functions

Signed-off-by: nwneisen <nwneisen@gmail.com>
Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add span tags to database funcions

Signed-off-by: nwneisen <nwneisen@gmail.com>
Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add database interface for things tags

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add channels database interface for tags

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add database interface for users tags

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Fix borken tests due to database interface

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update database interface naming

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Adjust tests for name changes

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Return previous db variable name

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Add ctx after rebase

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-07 13:32:09 +02:00
Aleksandar Novaković 19f8922a9e Update docker-compose version for addons (#874)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-10-02 11:57:12 +02:00
Mirko Teodorovic ecc15b64b6 MF-859 - Channels metadata search (#867)
* add users metadata

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

* add users metadata

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

* add metadata to users

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

* add metadata to users

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

* run.sh

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

* add metadata to users

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

* add default value for metadata

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

* add default value for metadata

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

* when metadata is not set dont save 'null' string

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

* when metadata is not set dont save 'null' string

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

* add metadata to channels

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

* add metadata to channels

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

* change metadata type, add error handling

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

* add pause

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

* remove extra char

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

* retype from string to []byte

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

* add metadata to channels

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

* add metadata to channels

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

* add wait logic for gnatsd

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

* few small fixes

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

* fix identityRes

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

* add metadata to channels

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

* add metadata to channels

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

* add metadata to channels

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

* fix waiting for gnatsd

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

* fix waiting for gnatsd

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

* fix waiting for gnatsd

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

* changes to wait gnatsd logic

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

* changes to wait gnatsd logic

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

* testing query

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

* fix query

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

* fix query

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

* fix merge problem

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

* Update requests.go

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

* Delete pwdrecovery.go

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

* fix merge problem

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

* fix merge problem

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

* small change in comments

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

* remove unused struct

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

* fix comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-10-01 14:12:52 +02:00
Mirko Teodorovic 92a640f6fc MF-858 Users metadata (#861)
* add users metadata

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

* add users metadata

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

* add metadata to users

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

* add metadata to users

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

* run.sh

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

* add metadata to users

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

* add default value for metadata

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

* add default value for metadata

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

* when metadata is not set dont save 'null' string

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

* when metadata is not set dont save 'null' string

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

* change metadata type, add error handling

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

* add pause

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

* remove extra char

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

* retype from string to []byte

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

* add wait logic for gnatsd

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

* few small fixes

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

* fix identityRes

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

* add users metadata

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

* add users metadata

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

* revert run.sh for now as gnats availability check is solved in other PR

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

* revert changes

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

* change metadata database/sql handling

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

* fix commit issues

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

* small change to errors handling

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

* minor comment change

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-09-28 13:15:41 +02:00
Dušan Borovčanin 873ef4c96f NOISSUE - Simplify MQTT benchmarking tool (#852)
* Fix user creation

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Extract client creation to separate method

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove message generating in separate goroutine

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove runSub and runPub methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Refactor benchmark code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update client message handling

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix microseconds typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Simplify client

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Don't take zero-messages clients int count

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix token timeout condition

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add timeout for publisher

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Paho lib version

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Temporary drop SenML and subscribe support

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Calculate payload size

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove templates

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-09-25 19:02:29 +02:00
Ivan Milošević 5e3bb270a3 NOISSUE - Upgrade Go version to 1.13 in container images (#868)
* Change go version to 1.13 in docker images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* update go version on CI

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix and add debug code in ci.sh

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove other Go version from PATH on semaphoreci
Add new go installation to PATH

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Create dir for golang 1.13

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove debug code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add check for current go version before upgrade in ci script

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-09-23 21:43:21 +02:00
Parham Alvani c8cb2655c0 MF-820 - Fetch messages for a particular device (#843)
* feat: Add Support for Publisher Query in Postgres

Signed-off-by: Parham Alvani <parham.alvani@gmail.com>

* chore: Remove Redundant Case

Signed-off-by: Parham Alvani <parham.alvani@gmail.com>

* chore: Add Test for Postgres Query

Signed-off-by: Parham Alvani <parham.alvani@gmail.com>
2019-09-21 00:20:09 +02:00
Aleksandar Novaković b83439fcc2 Update gorilla websocket version (#865)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-09-18 18:25:18 +02:00
Aleksandar Novaković 08105205e1 NOISSUE - Update aedes version and fix Dockerfile (#863)
* Update MQTT adapter and add message TTL

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Fix clean command

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-09-17 20:25:38 +02:00
Mirko Teodorovic d20dfa84bc NOISSUE - Search by metadata (#849)
* add metadata search

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

* add metadata search

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

* add metadata search

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

* add metadata search

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

* add metadata search

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

* add metadata search

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

* add metadata search

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

* add space

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

* metadata test case

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

* add docs and update swagger

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

* add test for metadata

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

* add test for metadata

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

* remove commented out section

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

* small change to test

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

* remove debug printf

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

* small fix for metadata

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

* fix tests

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

* and => and/or

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

* add line

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

* fix mixed func params

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

* metadata will be added to channels later

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

* fix return type

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

* fix typings

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

* fix typings

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

* add migration

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

* remove var

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

* respecting the order of migrations

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-09-17 15:46:24 +02:00
Ivan Milošević a6cf76709d MF-846 - Install python in docker build for aedes mqtt image (#860)
* Multi-stage docker build for aedes mqtt image

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add copyright header

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-09-16 18:25:34 +02:00
Drasko DRASKOVIC 5ef6aa84de NOISSUE - Clean NginX files, move .gitignores to dirs (#853)
* Remove nginx entrypoint trash, move .gitignore

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add node_modules to Aedes .gitignore

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Move docker .gitignore to nginx

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-16 11:47:34 +02:00
Drasko DRASKOVIC 3b19292966 NOISSUE - Add docker-compose for MQTT cluster (#841)
* Add MQTT cluster configs

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Bump worker_connections value in nginx.conf

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Revert config.toml

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix cluster

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Move non-ssl server into the same directive

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix cluster

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Remove unnecessary comments

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Organize docker-compose and nginx conf files

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Correct nginx.conf for mTLS and aux changes

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix NginX conf trailing slash in proxy_pass

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix path for MQTT

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix minorstuff in gitignore and comments

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-12 16:55:24 +02:00
Aleksandar Novaković 4870119270 Add debug logs to the WS adapter (#848)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-09-11 15:34:04 +02:00
Mirko Teodorovic 97327ab05f NOISSUE - Add measuring time from pub to sub (#839)
* refactor code

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

* connect each thing with each channel

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

* reverting - structure fields must be exported

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

* reverting - structure fields must be exported

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

* revert some names

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

* move meausuring time start

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

* add pub-to-sub delivery time measure

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

* add pub-to-sub delivery time measure

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

* add pub-to-sub delivery time measure

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

* add pub-to-sub delivery time measure

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

* improve sync between pub and sub

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

* improve sync between pub and sub

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

* improve sync between pub and sub

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

* improve sync between pub and sub

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

* improve sync between pub and sub

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

* improve sync between pub and sub

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

* add random payload

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

* revert changes for config.toml

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

* add random payload

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

* remove printfs

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

* add logging

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

* add payload

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

* add payload

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

* rename variable

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

* add payload

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

* small changes

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

* refactor sync

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

* refactor sync

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

* refactor results

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

* change sync and result collecting for sub

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

* fix comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-09-06 14:59:35 +02:00
Parham Alvani 115d94bd1b fix: Correct 404 and Content-Type Issues in MQTT Version Endpoint (#837)
Signed-off-by: Parham Alvani <parham.alvani@gmail.com>
2019-09-05 15:10:18 +02:00
Mirko Teodorovic 640dfb7e19 NOISSUE - update mqtt prov tool and some refactor (#831)
* refactor code

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

* connect each thing with each channel

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

* add some comments

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

* connect each thing with each channel

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

* reverting - structure fields must be exported

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

* reverting - structure fields must be exported

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

* revert some names

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

* move meausuring time start

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

* revert changes to .gitignore

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

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-09-05 13:39:58 +02:00
Dušan Borovčanin e17a3de1d0 NOISSUE - Use Thing ID to update certs data (#827)
* Use ThingID to update certs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix logging typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update API tests and service mock

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-09-03 13:54:56 +02:00
Drasko DRASKOVIC ec4b46b669 NOISSUE - Improve VerneMQ plugin code, add configurable gRPC pool size (#836)
* Enable gRPC support

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add poolboy process pool

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add Verne docker-compose

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Improve code, add settings for pool size

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-03 01:05:36 +02:00
Drasko DRASKOVIC 38d85ae03b NOISSUE - Use gRPC for VerneMQ (#835)
* Enable gRPC support

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add poolboy process pool

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add Verne docker-compose

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Clean the code in genservers

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-02 23:22:46 +02:00
Drasko DRASKOVIC 655f421ca9 NOISSUE - Fix proto files in VerneMQ (#834)
* Add socket pool and fix pattern matching

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix hackney req

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Change docker-compose

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Correct protobuf and add MQTT loback ignore

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Bring back docker-compose

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-02 21:20:18 +02:00
Drasko DRASKOVIC 9ccc37c4b1 NOISSUE - Fix hackney HTTP request (#833)
* Add socket pool and fix pattern matching

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix hackney req

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-02 18:11:58 +02:00
Drasko DRASKOVIC a8836d67e0 Add socket pool and fix pattern matching (#830)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-01 21:53:04 +02:00
Ivan Milošević 2d1347eee3 Switch secure of WS connection according to secure of http connection of UI (#829)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-08-30 16:23:28 +02:00
Alexander Obukhov 1a4844cee1 NOISSUE - Use current hostname instead of localhost for a WebSocket connection in the UI (#826)
Signed-off-by: Alexander Obukhov <dev@sprql.space>
2019-08-30 14:21:29 +02:00
Drasko DRASKOVIC f090573567 NOISSUE - Improve MQTT benchmarking tools (#828)
* Refactor mqtt-bench

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Refactor mqtt-bench and provision tools

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Improve README

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Remove provision generated toml file

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Update README

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Replace `tcp` with `tcps` for mtls

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-08-30 14:09:12 +02:00
Mirko Teodorovic bfd12304ac NOISSUE - update mqtt benchmark (#824)
* update gitignore

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

* fix dates for cert

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

* reorganize code

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

* update docs

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

* print to stdout for creating result file from script

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

* add script for testing

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

* add time measeuring

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

* separate test in one file each

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

* move config.toml

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

* update docs

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

* add connection check, and prevent blocking of publisher when connection fails

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-08-27 14:21:39 +02:00
Dušan Borovčanin e7891ec6ab Add encryption key to env vars table (#823)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-08-15 16:26:31 +02:00
Dušan Borovčanin 5c924bd5a3 NOISSUE - Add version endpoint to MQTT adapter (#816)
* Add version endpoint to MQTT adapter

Expose CoAP version endpoint in the default docker-compose.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use /tcp insted of implicit protocol name

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-08-14 01:07:11 +02:00
Mirko Teodorovic 19834dfc51 MF-295 add mqtt benchmark tool (#817)
* adding mqtt benchmark tool

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

* adding mqtt benchmark tool - vendoring

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

* add mtls support

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

* update readme

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

* tool for channel provision, reorganize code

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

* adding config toml

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

* tool for channel provision, adding ssl cert gen

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

* add config toml

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

* add readme for provision

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

* update readme

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

* remove some printing

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

* add test configs

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

* remove some dead code, and sort comments

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

* replace statistics lib

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

* replace statistics lib

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

* add cobra and viper

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

* styling changes

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

* styling changes

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

* styling changes

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

* remove statistic lib

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

* change type visibility

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

* remove empty line

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

* update vendor

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

* update deps

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

* rename variable

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

* small changes, adding comments

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

* error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-08-13 12:56:31 +02:00
Mirko Teodorovic 9a78b1111f update mqtts commands (#815)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-08-05 11:43:05 +02:00
Mirko Teodorovic 9c52c3816a Fix typo (#814)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-08-02 15:22:28 +02:00
Dušan Borovčanin f03949a003 NOISSUE - Support encrypted bootstrap (#796)
* Fix API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Provide secured bootstrapping

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix test and mock methods signatures

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add encrypte bootstrap test

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove duplicated docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use secret key to encrypt bootstrap request

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use secret key for secure bootstrapping

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move encryption to ConfigReader

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove ConfigReader from Service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add reader tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Unset key env variable

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add endpoint test for secure bootstrap

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-08-02 14:35:31 +02:00
Aleksandar Novaković 68e8eda92d Add config to writers docs (#812)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-08-01 13:13:24 +02:00
Drasko DRASKOVIC c751d0e656 NOISSUE - Add VerneMQ support (#809)
* Add VerneMQ

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix Aedes build

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Update VerneMQ README

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Remove unused gRPC client proto files

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add topic rewrite for CT

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix CT links on NATS SUB

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix NATS PUB/SUB with spawned process

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix typos

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix README

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-08-01 12:40:49 +02:00
Aleksandar Novaković 55d03ecdda NOISSUE - Add content type as part of MQTT subscription topic (#810)
* Add content type to MQTT sub topic

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove default content type value

Remove default content type value (SenML JSON) and replace it with
an empty string.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update docs in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-29 17:42:04 +02:00
Nikola Marčetić 2996193a50 Updated Readme (#808)
* Updated Readme

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Fix typo

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Fix grammar

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-07-25 19:20:11 +02:00
Ivan Milošević 6d2aa71ccc NOISSUE - Add rungw command to Makefile (#805)
* Add `make rungw` command to run mainflux without jaeger
Add name to jaeger container

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add explanatory comment in Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add new line on end file

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-25 17:24:26 +02:00
Ivan Milošević 54823bed25 NOISSUE - Fix building UI docker image for arm (#806)
* Add entrypoint in arm Dockerfile
Switch from ash to dash in entrypoint script

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add new line on file end

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-25 17:15:06 +02:00
Aleksandar Novaković 649986b19f MF-757 - Add support for explicit content type (#771)
* Add format to raw message and content type mapping

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add format as part of content type header

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add format to content-type header for WS adapter

Add format as part of content-type and add support for
content-type header and query parameter.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add support for format and content-type in MQTT adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add format and content-type to CoAP adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add documentation for content type and format

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add comments to MQTT code

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Fix SenML JSON CoAP code type

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove format from the adapters

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove format from the docs

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove format field from MQTT adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update content type to be optional

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add support for CBOR content type in the normalizer

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update docs for MQTT content type

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-25 02:22:28 +02:00
Nikola Marčetić 99ced38229 Updated Readme (#807)
Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-07-24 15:02:21 +02:00
Mirko Teodorovic 8efcaee350 NOISSUE - make correct reference to MF_UI_PORT (#804)
* make correct reference to MF_UI_PORT

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

* remove echo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-07-24 12:44:39 +02:00
Aleksandar Novaković a3de6953f8 Add support for optional Jaeger (#801)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-23 14:31:02 +02:00
Dušan Borovčanin 748b7c1ee8 Fix CoAP adapter subtopics (#797)
Remove `messages` part from the subtopics.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-07-22 13:24:50 +02:00
Nikola Marčetić 6af65ea159 Added changelog for 0.9.0 release (#799)
Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-07-19 17:56:23 +02:00
Aleksandar Novaković b95bb82e0b Fix users service to work in offline mode (#795)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-19 12:28:45 +02:00
Ivan Milošević 8bda799ef9 Create and push docker manifest for new release from Makefile (#794)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-18 17:31:37 +02:00
Aleksandar Novaković 61f1c2e379 MF-399 - Add open tracing support (#782)
* Add open tracing dependencies

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to users service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the http adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the ws adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the CoAP adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update LoRa adapter in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update SDK tests in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update reader services with accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update .env and docker-compose file

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add jaeger and timeout env vars

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Fix broken test for can access by id endpoint

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update deps with proto empty package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-18 15:01:09 +02:00
Ivan Milošević 1ad14e7fd3 fix mainflux_id parameter in bootstrap swagger (#789)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-16 16:21:37 +02:00
Aleksandar Novaković a6a8648e4f MF-783 - Allow access checking by a thing ID (#784)
* Add can access by things ID endpoint to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add new auth endpoint to the swagger docs

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add test for the new endpoint of the things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-15 18:28:15 +02:00
Dušan Borovčanin 4a7af7c0b3 NOISSUE - Fix CoAP adapter (#779)
* Fix CoAP mux

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove prints and commented code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-07-15 17:49:02 +02:00
Ivan Milošević b0a79436b8 Fix MF_THINGS_AUTH_GRPC_PORT in addons' docker-compose files (#781)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-11 23:19:08 +02:00
Aleksandar Novaković 4ab2e396c2 NOISSUE - Add authorization HTTP API to things service (#772)
* Add authorization HTTP API to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add new tests and update existing ones

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update swagger documentation

Update swagger documentation for auth endpoints.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update README docs for things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update docker-compose and fix endpoint typo

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove commented code

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-04 17:06:55 +02:00
Manuel Imperiale 22a3b881fd NOISSUE - Typo fix (#777)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-07-04 09:57:03 +02:00
Aleksandar Novaković aeebf06799 Remove cli executable from repo (#776)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-04 05:19:49 +08:00
Mirko Teodorovic c396e6ded8 fix docker-compose env (#775)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-07-03 20:50:28 +02:00
Manuel Imperiale f5b6c63cca NOISSUE - Use .env vars in docker-compose (#770)
* NOISSUE - Use .enr vars in docker-compose

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add README and dupllicated .env in docker repo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Bootstrap Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Cassandra (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for InfluxDB (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for MongoDB (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Postgres (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for LoRa adapter Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix Grafana port in .env file

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update README.md

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

* remove docker/.env and update README.md

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-07-02 14:22:12 +02:00
Dušan Borovčanin 81cd73e277 Use errors and null packets in authorized pub/sub (#773)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-07-01 11:22:31 +02:00
Mirko Teodorovic 204aec6171 MF-663 - enable nginx port conf from docker env (#769)
* MF-722 - Change UUID lib (#746)

* Update uuid package and update things serivce

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing postgres writer tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* MF-732 - Add Postgres reader (#740)

* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* MF-732 - Add Postgres reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total count

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm commented code

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add Postgres reader tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix editor format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Change UUID lib

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* MF-742 - Things to support single user scenario (#749)

* Add single user mode to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add tests for things/users package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update init order in main

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Update grpc and protobuf deps in mqtt adapter (#751)

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Fix MQTT raw message deserialization (#753)

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Add certificate fields to the Bootstrap service (#752)

* Add cert fields to the BS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert fields when creating a config

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add update cert endpoint

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix key column name

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert fields to db converters

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Secure cert update endpoint

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Authroize cert update methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Bootstrap service tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert update service tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update endpoit tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update request tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix request tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update repository tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typo in repo tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Add searchable Things name (#750)

* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add name in ListThings loggins

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add invalidName var for tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Set maxNameSize to 1024

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix postgres test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total when filtering things by name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Add missing Websocket.js into docker ui image (#755)

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* MF-466 - ARM docker deployment (#756)

* Add arm Dockerfiles
Change version of postgres images in docker-compose to use versions with  multiarch

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* docker compose file

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove bin qemu file

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove unnecesarry comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add make dockers_arm32v7 in Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove docker/Dockerfile.arm

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add build for arm32v7 in ui Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add arm32v7 tags

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add docker-compose for arm
remove thing-db in docker compose (use same db as user)
fix dockerfile and makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix thing database env variable

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Switch back to separate database for things and users

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* rename arm32v7 to arm

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove arm32v7 tag
rename arm32v7 image names

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Rename command for making mqtt and ui individual images, to match other miscroservices

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Push arm docker images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix fucntion call in ci.sh

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* mergiing ui/Dockerfile

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

*  NOISSUE - Add searchable Channels name  (#754)

* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add searchable Channels name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix test description

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix bootstrap mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Remove installing non-existent package in ci (#758)

* Remove installing non-existent  package from ci

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove branch master condition in ci.sh to test it

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix syntax error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* update apt

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* build arm images for latest release
push ui and mqtt arm images
install only qemu-user-static

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* clean docker after pushing amd64 images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* installing all qemu app

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove docker images before building ui and mqtt arm-images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* prune dockers and test only arm build

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix syntax error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix moving qemu-arm-static

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* installing qemu with apt-get

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add another apt-get update

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* apt install one liner

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* testing ci script

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* After installing qemu, get appropriate version

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Test ci without amd64 dockers

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove tests from ci for test purpose

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Uncomment commands

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove whitespace and change order of building images

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* nginx

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

* change to openresty

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

* change to openresty and accept env from docker compose

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

* revert to master

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

* revert to master

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

* revert to master

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

* NOISSUE - Fix Docker for ARM (#760)

* NOISSUE - Fix Docker for ARM

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Correct ARGs

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix docker-compose for ARM

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add docker manifest

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* cp not mv qemu from mf root

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix amd64 docker names for ui and mqtt images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* edit ci.sh for testing purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove bachslash from make manifest
edits in ci for testing purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix manifest call

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add manifest on version realese
delete space before latest argument
fix for loop in manifest creation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* include tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* docker system prune
remove tests for testin purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add variant in manifest file for armv7

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove white space

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* paralelise the compilation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix place of -j$NPROC

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* adding env variable

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

* fix variant option for manifest annotate (#765)

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* enable port configure from env var in docker-compose

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

* enable port configure from env var in docker-compose

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

* enable port configure from env var in docker-compose

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

* use docker env to set port

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

* add env to conf port in nginx.conf

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

* replace string with docker env

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

* replace string with docker env

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

* Update docs (#766)

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* remove not needed comment

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

* adding .env file for default UI_PORT - if no enviroment UI_PORT is set value from .env file is used

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

* get default UI_PORT variable value from .env file

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

* set default port value to 3000

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>
2019-06-24 20:13:02 +02:00
Dušan Borovčanin aa219b6f99 NOISSUE - Fix count when search by name is performed (#767)
* Fix total field when search by name

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tests to check total count

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-06-24 14:03:48 +02:00
Dušan Borovčanin f27cb1d019 Update docs (#766)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-06-21 14:07:24 +02:00
Ivan Milošević 41897cee11 fix variant option for manifest annotate (#765)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-20 15:31:56 +02:00
Drasko DRASKOVIC 91274aca9b NOISSUE - Fix Docker for ARM (#760)
* NOISSUE - Fix Docker for ARM

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Correct ARGs

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix docker-compose for ARM

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add docker manifest

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* cp not mv qemu from mf root

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix amd64 docker names for ui and mqtt images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* edit ci.sh for testing purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove bachslash from make manifest
edits in ci for testing purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix manifest call

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add manifest on version realese
delete space before latest argument
fix for loop in manifest creation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* include tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* docker system prune
remove tests for testin purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add variant in manifest file for armv7

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove white space

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* paralelise the compilation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix place of -j$NPROC

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-20 14:14:06 +02:00
Ivan Milošević b53979104c NOISSUE - Remove installing non-existent package in ci (#758)
* Remove installing non-existent  package from ci

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove branch master condition in ci.sh to test it

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix syntax error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* update apt

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* build arm images for latest release
push ui and mqtt arm images
install only qemu-user-static

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* clean docker after pushing amd64 images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* installing all qemu app

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove docker images before building ui and mqtt arm-images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* prune dockers and test only arm build

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix syntax error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix moving qemu-arm-static

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* installing qemu with apt-get

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add another apt-get update

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* apt install one liner

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* testing ci script

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* After installing qemu, get appropriate version

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Test ci without amd64 dockers

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove tests from ci for test purpose

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Uncomment commands

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove whitespace and change order of building images

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-12 21:11:45 +02:00
Manuel Imperiale 63de955a7c NOISSUE - Add searchable Channels name (#754)
* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add searchable Channels name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix test description

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix bootstrap mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-06-11 10:37:25 +02:00
Ivan Milošević 44cc20b9ca MF-466 - ARM docker deployment (#756)
* Add arm Dockerfiles
Change version of postgres images in docker-compose to use versions with  multiarch

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* docker compose file

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove bin qemu file

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove unnecesarry comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add make dockers_arm32v7 in Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove docker/Dockerfile.arm

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add build for arm32v7 in ui Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add arm32v7 tags

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add docker-compose for arm
remove thing-db in docker compose (use same db as user)
fix dockerfile and makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix thing database env variable

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Switch back to separate database for things and users

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* rename arm32v7 to arm

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove arm32v7 tag
rename arm32v7 image names

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Rename command for making mqtt and ui individual images, to match other miscroservices

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Push arm docker images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix fucntion call in ci.sh

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* mergiing ui/Dockerfile

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-11 02:14:05 +02:00
Ivan Milošević 62e7d69f7b Add missing Websocket.js into docker ui image (#755)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-10 00:15:12 +02:00
Manuel Imperiale c8979ac297 NOISSUE - Add searchable Things name (#750)
* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add name in ListThings loggins

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add invalidName var for tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Set maxNameSize to 1024

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix postgres test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total when filtering things by name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-30 15:33:49 +02:00
Dušan Borovčanin cac1e35b58 NOISSUE - Add certificate fields to the Bootstrap service (#752)
* Add cert fields to the BS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert fields when creating a config

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add update cert endpoint

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix key column name

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert fields to db converters

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Secure cert update endpoint

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Authroize cert update methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Bootstrap service tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert update service tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update endpoit tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update request tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix request tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update repository tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typo in repo tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-22 23:22:19 +02:00
Aleksandar Novaković 6d6f3cc858 Fix MQTT raw message deserialization (#753)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-21 11:27:26 +02:00
Aleksandar Novaković 066057d914 Update grpc and protobuf deps in mqtt adapter (#751)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-17 16:51:22 +02:00
Aleksandar Novaković f9093413ee MF-742 - Things to support single user scenario (#749)
* Add single user mode to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add tests for things/users package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update init order in main

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-17 16:06:21 +02:00
Manuel Imperiale 5fec0a9e92 MF-732 - Add Postgres reader (#740)
* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* MF-732 - Add Postgres reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total count

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm commented code

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add Postgres reader tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix editor format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Change UUID lib

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-16 18:00:19 +02:00
Aleksandar Novaković 3125f0bbc2 MF-722 - Change UUID lib (#746)
* Update uuid package and update things serivce

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing postgres writer tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-16 13:35:13 +02:00
Aleksandar Novaković 0dc4d12e73 Add performance improvement to writer filtering (#744)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-16 13:19:05 +02:00
Dušan Borovčanin 9d30006d31 NOISSUE - Update nginx version (#748)
* Update version of the Nginx image

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add undefined check for HTTP and WS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-16 11:59:23 +02:00
Aleksandar Novaković 5202d734c9 MF-574 - Add missing environment variables to Cassandra writer (#745)
* Add username, password and port env var

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing readme files with new env vars

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-15 23:31:40 +02:00
Aleksandar Novaković cc5c66bfba Fix CI with fixed plugin versions (#747)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-15 17:41:18 +02:00
Dušan Borovčanin f5bb65d0e4 NOISSUE - Add compile test to CI (#743)
* Add compile tests to CI script

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix parallelization flag

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-13 14:48:54 +02:00
Mirko Teodorovic e46164f93f fix building problems (#741)
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>
2019-05-11 17:16:25 +02:00
Manuel Imperiale d2fc379e3f NOISSUE - Fix Postgres logs in Things service (#734)
* NOISSUE - Fix Postgres logs in Things service

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix uuid package

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-10 18:55:13 +02:00
Aleksandar Novaković 5e5fe88927 MF-708 - Assign Writer(s) to a channel (#737)
* Add support for channel filtering using config

Add support for channel filtering using yaml configuration files
for writers.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update writers documentation with new env var

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add info comment to configuration file

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix configuration and update yaml dep

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update config from yaml to toml

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-05-10 14:36:27 +02:00
Manuel Imperiale 886c8b2467 MF-732 - Add PostgreSQL writer (#733)
* MF-732 - Add PostgreSQL writer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add README

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Unify writers api

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix host and port

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 19:09:28 +02:00
Manuel Imperiale 138dae9277 NOISSUE - Fix Readers logs (#735)
* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 15:59:18 +02:00
Manuel Imperiale 9f1151b8dc NOISSUE - Add readers pagination in SDK (#736)
* NOISSUE - Add readers pagination in SDK

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 15:10:02 +02:00
Darko Draskovic 700cfdf6f3 Add UI websocket open/close and send/receive (#728)
* Add websocket open/close and send/receive

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket feedback/log to Elm

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Align WS open/close buttons

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket query for channel list

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket related encoders/decoders for port interops

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix .js indentation and simplify ws (dis)connect method

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use single quotes consistently

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add HTTP Send and WS Listen

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-26 19:17:04 +02:00
Aleksandar Novaković dc9333237f MF-707 - Allow custom Thing key (#726)
* Add support for setting up thing key manually

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix existing tests and add new ones

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update SQL schema for things entity

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add update thing key endpoint to swagger docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix response code when handling conflicting key

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-25 14:37:51 +02:00
Aleksandar Novaković aba7d909ed MF-525 - Add pagination response to the readers (#729)
* Add pagination response to the readers

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Remove println from influx reader

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-25 00:18:43 +02:00
Manuel Imperiale 04281f2388 NOISSUE - Rm Things type from lora-adapter (#727)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-04-22 21:31:08 +02:00
Milan Andric 08f2545df9 fix to makefile for OSX/Darwin (#724)
Signed-off-by: Milan Andric <mandric@gmail.com>
2019-04-22 21:03:05 +02:00
Darko Draskovic 64f154dcc9 Fix .dockerignore file by removing index.html (#725)
* Fix .dockerignore file by removing index.html

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add autogen main.js to .dockerignore

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-21 20:10:24 +02:00
Darko Draskovic 9688fc316a Fix things and channels metadata create and edit & remove thing type (#721)
* Add basic websocket support

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add separate cards for base and regular attribs

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add arbitrary json metadata enc/dec

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add JsonMF module for JSON val handling

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename json related func for naming consistency

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove Thing type from user interface

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add arbitrary metadata json to channels

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Change json string identation

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-21 02:40:14 +02:00
Jovan Kostovski 5b66b6e169 skip deleting of persistent volumes by default (#723)
This commit, intruduces changes to the current behavior of the "make cleandocker" command.
From now on, an extra parameter must be given in order to delete the persistent volumes,  which up until now was done by default. The new command which represents the old behavior (deleting all artifacts created from the docker composition) is "make pv=true cleandocker".

Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-04-20 17:56:51 +02:00
Manuel Imperiale b97deb50b2 MF-488 - Remove Thing type (app or device) (#718)
* MF-488 - Remove Thing type (app or device)

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-04-20 14:09:11 +02:00
Dušan Borovčanin 44615c5ff0 Remove empty channels check (#720)
Remove empty list of channels check from the default Bootstrap
reader implementation.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-18 18:02:20 +02:00
Jovan Kostovski a30666d999 MF-655 Proper usage of docker volumes (#657)
* added filter and a switch for volume deletion

Signed-off-by: Jovan Kostovski <chombium@gmail.com>

* added persistent volume configuration

Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-04-18 17:31:24 +02:00
Drasko DRASKOVIC baebe8abc1 NOISSUE - Improve UI styling (#719)
* Add styling

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix errors

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix env

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-04-18 13:29:38 +02:00
Dušan Borovčanin d9d9da49a8 MF-715 - Conflict on updating connection with a valid list of channels (#716)
* Add check if Channels already exist

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update streams tests to use time offset

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-17 19:41:37 +02:00
Aleksandar Novaković 2f17a05b3e MF-711 - Create separate Redis instance for ES (#717)
* Fix Redis connection error handling in MQTT adapter

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add separate env vars for ES in MQTT adapter

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Create dedicated redis service for ES

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update docs according to changes

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-17 12:39:47 +02:00
Dušan Borovčanin fe9f4dca8e NOISSUE - Update event fields naming (#713)
* Update event fields naming

Replace `id` with `thing_id`.
Fix typo in `success` field.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-16 18:32:05 +02:00
Aleksandar Novaković de736a5c47 MF-698 - Add missing info and docs about sys event sourcing (#712)
* Add event sourcing section in dev guide

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix spelling in the docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix notice formatting in docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix typos in docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add highlight for event type

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-16 17:28:43 +02:00
Aleksandar Novaković 5799356b14 MF-549 - Change metadata format from JSON string to JSON object (#706)
* Update metadata type in things service

Update things service so that metadata has map type. Update repo
implementation by adding sqlx lib.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add sqlx lib to bootstrap service

Add sqlx lib to bootstrap service and update metadata field type.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update metadata in redis streams consumer

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update tests for bootstrap service

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix mongo reader logging and driver version

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix mongo reader and writer

Fix mongo reader and writer by updating driver version.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update SDK with new metadata format

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update LoRa adapter with new metadata format

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update users service in order to use sqlx

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Replace anonymous struct with map

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update docs for LoRa adapter

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix LoRa application metadata format

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix metadata format in LoRa docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add metadata2 var to SDK things test

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-16 14:58:56 +02:00
Dušan Borovčanin 0bbc4f54ab Fix Bootstrap service event map keys (#705)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-11 13:55:21 +02:00
Darko Draskovic 0b89975947 NOISSUE - Replace repeating code by card gen func (#697)
* Replace repeating code by card gen func

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove absolute URL from Env.elm

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix md links in README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor appendIF helper func

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-09 16:21:26 +02:00
Dušan Borovčanin df11a20a32 Update Bootstrap service docker-compose.yml (#700)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-09 02:44:50 -07:00
Ivan Milošević 77ce0f9fab Remove Debug function (#699)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-09 01:50:23 -07:00
Dušan Borovčanin 6ebc758f89 MF-687 - Add event sourcing to Bootstrap service (#695)
* Add initial Bootstrap event producer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add operation name to events

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add ES path for BS events

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add BS event sourcing tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add connections update events

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Extract event XADD to separeate method

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add header comments

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix service docs typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update BS service README.md

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Service method tests

Tests for handler methods are not added.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-08 08:46:07 -07:00
Ivan Milošević 98d588e8ef NOISSUE - Remove debugging message from response of handle error function (#696)
* Remove debugging message from response of handle error function

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Print error in debug console in case of bad body HTTP response

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-08 13:44:11 +02:00
Aleksandar Novaković 12a85f565c Fix logging in publish event callback (#694)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-05 17:29:43 +02:00
Aleksandar Novaković 5fcf0aa229 Add event stream to MQTT adapter for conn status (#692)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-05 11:25:39 +02:00
Drasko DRASKOVIC 9b41c2db3a NOISSUE - Improve UI style (#691)
* Improve UI style

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Remove unused font

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-04-04 20:28:56 +02:00
Dušan Borovčanin e4d3b1d8c3 Fix InfluxDB time bug (#689)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 18:10:01 +02:00
Dušan Borovčanin 4f62f89b87 Update docs structure (#686)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 12:10:46 +02:00
Dušan Borovčanin 06f4c6e059 Use images instead of carousel (#685)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 11:19:43 +02:00
Dušan Borovčanin 25d9783514 NOISSUE - Update docs (#683)
* Add authorization section to the docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Slow down Bootstrap gif

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use heading 4 instead of italic

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Replace gif with carousel

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 10:22:09 +02:00
Ivan Milošević 56a50962b6 MF-662 - Change menu style (#678)
* env variable for local dev

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change menu style

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Revert "env variable for local dev"

This reverts commit 205e13be6cdde930f02e7bda2019bb615267ed14.

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Move font awesome class names to record in Helper

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-02 19:17:28 +02:00
Dušan Borovčanin f9b17d5f24 MF-651 - X509 Mutual TLS authentication (#676)
* Use NginX njs module for mutual authentication

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add Makefile for cert management

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move certificates make context to scripts dir

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move nginx.conf to separate directory

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Choose between two NginX configurations

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move certs Makefile to docker/ssl/

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use default key-based authentication

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add mTLS docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Makefile

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add check if Authorization is present

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add check if Will Flag is 1

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Return MQTT over WS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix docker-compose.yml volume mapping

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename security section in docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add message type check before message parsing

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove double comments

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove s.AGAIN in return

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Makefile

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove CSR and key from the root

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Drop TLS version below 1.2

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add comments for cert and key paths

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-02 17:54:24 +02:00
Ivan Milošević bc49f6a543 Fix offset calculation after deleting thing/channel, not to go to negative offset after deleting last thing/channel (#679)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-01 17:49:10 +02:00
Aleksandar Novaković 83cafeb6be Update Aedes version for MQTT adapter (#677)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-03-29 14:04:02 +01:00
Ivan Milošević c634bf8c7d MF-661 - Bootstrap pagination in UI (#672)
* Add bootstrap pagination

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix enviroment variable in readme

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change style
Remove pagination from channels and things lists if total less then limit

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Hide pagination when not needed on connection and message page
Code refacotoring

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove debug module and local dev variable.

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove debug logging

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add trailing slash note in README

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* proto

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-03-28 10:20:15 +01:00
Aleksandar Novaković 86a3115804 Update subtopics section in documentation (#670)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-03-22 16:47:30 +01:00
Darko Draskovic 4a31f11615 Remove default base URL value (#671)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-03-22 14:53:03 +01:00
2420 changed files with 356420 additions and 238313 deletions
-1
View File
@@ -1,7 +1,6 @@
.git
.github
build
ui
docker
docs
k8s
+193
View File
@@ -0,0 +1,193 @@
# Docker: Environment variables in Compose
## NginX
MF_NGINX_HTTP_PORT=80
MF_NGINX_SSL_PORT=443
MF_NGINX_MQTT_PORT=1883
MF_NGINX_MQTTS_PORT=8883
## NATS
MF_NATS_URL=nats://nats:4222
## Redis
MF_REDIS_TCP_PORT=6379
## UI
MF_UI_PORT=3000
## Grafana
MF_GRAFANA_PORT=3000
## Jaeger
MF_JAEGER_PORT=6831
MF_JAEGER_FRONTEND=16686
MF_JAEGER_COLLECTOR=14268
MF_JAEGER_CONFIGS=5778
MF_JAEGER_URL=jaeger:6831
## Core Services
### AuthN
MF_AUTHN_LOG_LEVEL=debug
MF_AUTHN_HTTP_PORT=8189
MF_AUTHN_GRPC_PORT=8181
MF_AUTHN_DB_PORT=5432
MF_AUTHN_DB_USER=mainflux
MF_AUTHN_DB_PASS=mainflux
MF_AUTHN_DB=authn
MF_AUTHN_SECRET=secret
### Users
MF_USERS_LOG_LEVEL=debug
MF_USERS_HTTP_PORT=8180
MF_USERS_DB_PORT=5432
MF_USERS_DB_USER=mainflux
MF_USERS_DB_PASS=mainflux
MF_USERS_DB=users
### Email utility
MF_EMAIL_DRIVER=smtp
MF_EMAIL_HOST=smtp.mailtrap.io
MF_EMAIL_PORT=2525
MF_EMAIL_USERNAME=18bf7f70705139
MF_EMAIL_PASSWORD=2b0d302e775b1e
MF_EMAIL_FROM_ADDRESS=from@example.com
MF_EMAIL_FROM_NAME=Example
MF_EMAIL_TEMPLATE=email.tmpl
### Token utility
MF_TOKEN_RESET_ENDPOINT=/reset-request
### Things
MF_THINGS_LOG_LEVEL=debug
MF_THINGS_HTTP_PORT=8182
MF_THINGS_AUTH_HTTP_PORT=8989
MF_THINGS_AUTH_GRPC_PORT=8183
MF_THINGS_DB_PORT=5432
MF_THINGS_DB_USER=mainflux
MF_THINGS_DB_PASS=mainflux
MF_THINGS_DB=things
MF_THINGS_SECRET=secret
MF_THINGS_ES_URL=localhost:6379
MF_THINGS_ES_PASS=
MF_THINGS_ES_DB=0
### WS
MF_WS_ADAPTER_LOG_LEVEL=debug
MF_WS_ADAPTER_PORT=8186
### HTTP
MF_HTTP_ADAPTER_PORT=8185
### MQTT
MF_MQTT_ADAPTER_LOG_LEVEL=debug
MF_MQTT_ADAPTER_PORT=1883
MF_MQTT_ADAPTER_WS_PORT=8080
### CoAP
MF_COAP_ADAPTER_LOG_LEVEL=debug
MF_COAP_ADAPTER_PORT=5683
## Addons Services
### Bootstrap
MF_BOOTSTRAP_LOG_LEVEL=debug
MF_BOOTSTRAP_PORT=8202
MF_BOOTSTRAP_DB_PORT=5432
MF_BOOTSTRAP_DB_USER=mainflux
MF_BOOTSTRAP_DB_PASS=mainflux
MF_BOOTSTRAP_DB=bootstrap
MF_BOOTSTRAP_DB_SSL_MODE=disable
### LoRa
MF_LORA_ADAPTER_LOG_LEVEL=debug
MF_LORA_ADAPTER_MESSAGES_URL=tcp://lora.mqtt.mainflux.io:1883
MF_LORA_ADAPTER_HTTP_PORT=8187
MF_LORA_ADAPTER_ROUTE_MAP_URL=localhost:6379
MF_LORA_ADAPTER_ROUTE_MAP_PASS=
MF_LORA_ADAPTER_ROUTE_MAP_DB=0
### OPC-UA
MF_OPCUA_ADAPTER_HTTP_PORT=8188
MF_OPCUA_ADAPTER_LOG_LEVEL=debug
MF_OPCUA_ADAPTER_SERVER_URI=opc.tcp://opcua.rocks:4840
MF_OPCUA_ADAPTER_NODE_NAMESPACE=0
MF_OPCUA_ADAPTER_NODE_IDENTIFIER=2256
MF_OPCUA_ADAPTER_POLICY=
MF_OPCUA_ADAPTER_MODE=
MF_OPCUA_ADAPTER_CERT_FILE=
MF_OPCUA_ADAPTER_KEY_FILE=
MF_OPCUA_ADAPTER_ROUTE_MAP_URL=localhost:6379
MF_OPCUA_ADAPTER_ROUTE_MAP_PASS=
MF_OPCUA_ADAPTER_ROUTE_MAP_DB=0
MF_OPCUA_ADAPTER_EVENT_CONSUMER=opcua
### Cassandra Writer
MF_CASSANDRA_WRITER_LOG_LEVEL=debug
MF_CASSANDRA_WRITER_PORT=8902
MF_CASSANDRA_WRITER_DB_PORT=9042
MF_CASSANDRA_WRITER_DB_CLUSTER=mainflux-cassandra
MF_CASSANDRA_WRITER_DB_KEYSPACE=mainflux
### Cassandra Reader
MF_CASSANDRA_READER_LOG_LEVEL=debug
MF_CASSANDRA_READER_PORT=8903
MF_CASSANDRA_READER_DB_PORT=9042
MF_CASSANDRA_READER_DB_CLUSTER=mainflux-cassandra
MF_CASSANDRA_READER_DB_KEYSPACE=mainflux
### InfluxDB Writer
MF_INFLUX_WRITER_LOG_LEVEL=debug
MF_INFLUX_WRITER_PORT=8900
MF_INFLUX_WRITER_BATCH_SIZE=5000
MF_INFLUX_WRITER_BATCH_TIMEOUT=5
MF_INFLUX_WRITER_DB_PORT=8086
MF_INFLUX_WRITER_DB_NAME=mainflux
MF_INFLUX_WRITER_DB_USER=mainflux
MF_INFLUX_WRITER_DB_PASS=mainflux
MF_INFLUX_WRITER_GRAFANA_PORT=3001
### InfluxDB Reader
MF_INFLUX_READER_LOG_LEVEL=debug
MF_INFLUX_READER_PORT=8905
MF_INFLUX_READER_DB_NAME=mainflux
MF_INFLUX_READER_DB_PORT=8086
MF_INFLUX_READER_DB_USER=mainflux
MF_INFLUX_READER_DB_PASS=mainflux
### MongoDB Writer
MF_MONGO_WRITER_LOG_LEVEL=debug
MF_MONGO_WRITER_PORT=8901
MF_MONGO_WRITER_DB_NAME=mainflux
MF_MONGO_WRITER_DB_PORT=27017
### MongoDB Reader
MF_MONGO_READER_LOG_LEVEL=debug
MF_MONGO_READER_PORT=8904
MF_MONGO_READER_DB_NAME=mainflux
MF_MONGO_READER_DB_PORT=27017
### Postgres Writer
MF_POSTGRES_WRITER_LOG_LEVEL=debug
MF_POSTGRES_WRITER_PORT=9104
MF_POSTGRES_WRITER_DB_PORT=5432
MF_POSTGRES_WRITER_DB_USER=mainflux
MF_POSTGRES_WRITER_DB_PASS=mainflux
MF_POSTGRES_WRITER_DB_NAME=messages
MF_POSTGRES_WRITER_DB_SSL_MODE=disable
MF_POSTGRES_WRITER_DB_SSL_CERT=""
MF_POSTGRES_WRITER_DB_SSL_KEY=""
MF_POSTGRES_WRITER_DB_SSL_ROOT_CERT=""
### Postgres Reader
MF_POSTGRES_READER_LOG_LEVEL=debug
MF_POSTGRES_READER_PORT=9204
MF_POSTGRES_READER_CLIENT_TLS=false
MF_POSTGRES_READER_CA_CERTS=""
MF_POSTGRES_READER_DB_PORT=5432
MF_POSTGRES_READER_DB_USER=mainflux
MF_POSTGRES_READER_DB_PASS=mainflux
MF_POSTGRES_READER_DB_NAME=messages
MF_POSTGRES_READER_DB_SSL_MODE=disable
MF_POSTGRES_READER_DB_SSL_CERT=""
MF_POSTGRES_READER_DB_SSL_KEY=""
MF_POSTGRES_READER_DB_SSL_ROOT_CERT=""
+6 -1
View File
@@ -1,3 +1,8 @@
# Copyright (c) Mainflux
# SPDX-License-Identifier: Apache-2.0
# Set your private global .gitignore:
# https://digitalfortress.tech/tricks/creating-a-global-gitignore/
build
site/
+192
View File
@@ -11,6 +11,198 @@ Otherwise, whole log in a similar format can be observed via:
git log --pretty=oneline --abbrev-commit
```
## 0.10.0 - 17. DEC 2019.
### Features
- MF-932 - User API keys (#941)
- NOISSUE - Use opcua server timestamp in opcua-adapter messages (#980)
- Simplify CI script (#979)
- NOISSUE - Add opcua-adapter conn route-map, use ServerURI and NodeID (#975)
- Move docs to a separate repo (#976)
- NOISSUE - Support multiple types values in opcua-adapter (#973)
- Migrate from dep to go modules (#971)
- NOISSUE - Add Node IdentifierType config in opcua-adapter (#967)
- NOISSUE - Remove messages limit in influxdb-reader (#968)
- MF-898 - Add bulk connect to CLI and SDK (#956)
- MF-538 - Improve logging and API errors (#866)
- NOISSUE - Remove Elm UI (#953)
- MF-898 - Add bulk connections endpoint (#948)
- MF-898 - Change thing's service to use bulk connect (#946)
- MF-898 - Add transactions to postgres connect (#940)
- Add missing user service tests (#945)
- Remove Normalizer service from compose (#937)
- MF-919 - Mainflux message updates (#924)
- NOISSUE - Remove ARM multi-arch images (#929)
- MF-906 - Change single creation endpoints to use bulk service calls (#927)
- MF-922 - Add UpdateUser endpoint (#923)
- MF-780 - Use Normalizer as a lib (#915)
- NOISSUE - Switch to grpcbox for VerneMQ (#914)
- Change channels to chs (#918)
- MF-484 - Add bulk provisioning for things and channels (#889)
- MF-899 - Update README and official docs (#910)
- NOISSUE - Fix Redis envars (#903)
- Add disconnect on gen_server terminate() (#913)
- MF-890 - Add OPC-UA docs (#904)
- NOISSUE - Update Protobuf version (#902)
- MF-886 - Add OPC-UA adapter (#878)
- MF-532 - Password reset (#873)
- MF-785 - Change CanAccess to CanAccessByKey (#894)
- NOISSUE - Add MQTT UserName check on register and InstanceId in Redis (#884)
- Add MQTT troubleshooting section (#882)
- MF-875 - Add tracing to official documentation (#877)
- MF-788 - Remove date and minimize copyright comments (#876)
- MF-787 - Add tags to user, thing, and channel spans (#869)
- Update docker-compose version for addons (#874)
- MF-859 - Channels metadata search (#867)
- MF-858 Users metadata (#861)
- NOISSUE - Simplify MQTT benchmarking tool (#852)
- NOISSUE - Upgrade Go version to 1.13 in container images (#868)
- MF-820 - Fetch messages for a particular device (#843)
- Update gorilla websocket version (#865)
- NOISSUE - Update aedes version and fix Dockerfile (#863)
- NOISSUE - Search by metadata (#849)
- MF-846 - Install python in docker build for aedes mqtt image (#860)
- NOISSUE - Clean NginX files, move .gitignores to dirs (#853)
- NOISSUE - Add docker-compose for MQTT cluster (#841)
- Add debug logs to the WS adapter (#848)
- NOISSUE - Add measuring time from pub to sub (#839)
- NOISSUE - update mqtt prov tool and some refactor (#831)
- NOISSUE - Use Thing ID to update certs data (#827)
- NOISSUE - Improve VerneMQ plugin code, add configurable gRPC pool size (#836)
- NOISSUE - Use gRPC for VerneMQ (#835)
- Switch secure of WS connection according to secure of http connection of UI (#829)
- NOISSUE - Use current hostname instead of localhost for a WebSocket connection in the UI (#826)
- NOISSUE - Improve MQTT benchmarking tools (#828)
- NOISSUE - update mqtt benchmark (#824)
- Add encryption key to env vars table (#823)
- NOISSUE - Add version endpoint to MQTT adapter (#816)
- MF-295 add mqtt benchmark tool (#817)
- update mqtts commands (#815)
- NOISSUE - Support encrypted bootstrap (#796)
- Add config to writers docs (#812)
- NOISSUE - Add VerneMQ support (#809)
- NOISSUE - Add content type as part of MQTT subscription topic (#810)
### Bugfixes
- Fix MQTT protobuf filename(#981)
- MF-950 - Runtime error in normalizer - CBOR SenML (#974)
- NOISSUE - Fix opcua-adapter events warnings (#965)
- NOISSUE - Fix opcua-adapter events decode (#951)
- Fix subtopic handling in VerneMQ (#962)
- NOISSUE - Fix Update User (#959)
- NOISSUE - Fix make dockers (#957)
- Add dev_ back to make dockers_dev (#955)
- NOISSUE - Fix docs (#952)
- MF-916 - Fix Things and Channels counters (#947)
- MF-942 - Fix email template logic (#944)
- NOISSUE - Fix HTTP header for Things and Channels creation (#939)
- NOISSUE - Fix docker ui image name (#938)
- NOISSUE - Fix lora-adapter (#936)
- NOISSUE - Fix lora creation events (#933)
- Fix doc for ENV vars in README (#920)
- Fix compilation (#911)
- Revert "NOISSUE - Make event sourcing optional (#907)" (#909)
- NOISSUE - Make event sourcing optional (#907)
- NOISSUE - Fix InfluxDB env vars (#908)
- Fix Elm version for ARM Docker images (#905)
- Fix Elm version in Dockerfile (#901)
- NOISSUE - fix security doc (#897)
- NOISSUE - Fix typo in docs and README (#891)
- Fix Nginx mTLS configuration (#885)
- Fix provision tool connect error handling (#879)
- Fix: Correct 404 and Content-Type Issues in MQTT Version Endpoint (#837)
- NOISSUE - Fix proto files in VerneMQ (#834)
- NOISSUE - Fix hackney HTTP request (#833)
- Add socket pool and fix pattern matching (#830)
- Fix typo (#814)
## 0.9.0 - 19. JUL 2019.
### Features
- Create and push docker manifest for new release from Makefile (#794)
- MF-399 - Add open tracing support (#782)
- MF-783 - Allow access checking by a thing ID (#784)
- NOISSUE - Add authorization HTTP API to things service (#772)
- Remove cli executable from repo (#776)
- NOISSUE - Use .env vars in docker-compose (#770)
- MF-663 - enable nginx port conf from docker env (#769)
- Update docs (#766)
- NOISSUE - Remove installing non-existent package in ci (#758)
- NOISSUE - Add searchable Channels name (#754)
- MF-466 - ARM docker deployment (#756)
- Add missing Websocket.js into docker ui image (#755)
- NOISSUE - Add searchable Things name (#750)
- NOISSUE - Add certificate fields to the Bootstrap service (#752)
- Update grpc and protobuf deps in mqtt adapter (#751)
- MF-742 - Things to support single user scenario (#749)
- MF-732 - Add Postgres reader (#740)
- MF-722 - Change UUID lib (#746)
- Add performance improvement to writer filtering (#744)
- NOISSUE - Update nginx version (#748)
- MF-574 - Add missing environment variables to Cassandra writer (#745)
- NOISSUE - Add compile test to CI (#743)
- MF-708 - Assign Writer(s) to a channel (#737)
- MF-732 - Add PostgreSQL writer (#733)
- NOISSUE - Add readers pagination in SDK (#736)
- Add UI websocket open/close and send/receive (#728)
- MF-707 - Allow custom Thing key (#726)
- MF-525 - Add pagination response to the readers (#729)
- NOISSUE - Rm Things type from lora-adapter (#727)
- skip deleting of persistent volumes by default (#723)
- MF-488 - Remove Thing type (app or device) (#718)
- Remove empty channels check (#720)
- MF-655 Proper usage of docker volumes (#657)
- NOISSUE - Improve UI styling (#719)
- MF-715 - Conflict on updating connection with a valid list of channels (#716)
- MF-711 - Create separate Redis instance for ES (#717)
- NOISSUE - Update event fields naming (#713)
- MF-698 - Add missing info and docs about sys event sourcing (#712)
- MF-549 - Change metadata format from JSON string to JSON object (#706)
- NOISSUE - Replace repeating code by card gen func (#697)
- Update Bootstrap service docker-compose.yml (#700)
- Remove Debug function (#699)
- MF-687 - Add event sourcing to Bootstrap service (#695)
- NOISSUE - Remove debugging message from response of handle error function (#696)
- Add event stream to MQTT adapter for conn status (#692)
- NOISSUE - Improve UI style (#691)
- Update docs structure (#686)
- Use images instead of carousel (#685)
- NOISSUE - Update docs (#683)
- MF-662 - Change menu style (#678)
- MF-651 - X509 Mutual TLS authentication (#676)
- Update Aedes version for MQTT adapter (#677)
- MF-661 - Bootstrap pagination in UI (#672)
- Update subtopics section in documentation (#670)
- Remove default base URL value (#671)
### Bugfixes
- NOISSUE - Fix Readers logs (#735)
- NOISSUE - Fix Docker for ARM (#760)
- NOISSUE - Fix count when search by name is performed (#767)
- NOISSUE - Typo fix (#777)
- NOISSUE - Fix Postgres logs in Things service (#734)
- Fix CI with fixed plugin versions (#747)
- fix building problems (#741)
- fix docker-compose env (#775)
- Fix MF_THINGS_AUTH_GRPC_PORT in addons' docker-compose files (#781)
- Fix MQTT raw message deserialization (#753)
- fix variant option for manifest annotate (#765)
- fix to makefile for OSX/Darwin (#724)
- Fix .dockerignore file by removing index.html (#725)
- Fix things and channels metadata create and edit & remove thing type (#721)
- Fix Bootstrap service event map keys (#705)
- Fix logging in publish event callback (#694)
- Fix InfluxDB time bug (#689)
- Fix users service to work in offline mode (#795)
- fix mainflux_id parameter in bootstrap swagger (#789)
- Fix offset calculation after deleting thing/channel, not to go to negative offset after deleting last thing/channel (#679)
- Use errors and null packets in authorized pub/sub (#773)
- NOISSUE - Fix CoAP adapter (#779)
### Summary
https://github.com/mainflux/mainflux/milestone/10?closed=1
## 0.8.0 - 20. MAR 2019.
### Features
- MF-571 - Add Env.elm to set custom base URL (#654)
Generated
-791
View File
@@ -1,791 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:6da51e5ec493ad2b44cb04129e2d0a068c8fb9bd6cb5739d199573558696bb94"
name = "github.com/Azure/go-ansiterm"
packages = [
".",
"winterm",
]
pruneopts = "UT"
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
[[projects]]
digest = "1:bf42be3cb1519bf8018dfd99720b1005ee028d947124cab3ccf965da59381df6"
name = "github.com/Microsoft/go-winio"
packages = ["."]
pruneopts = "UT"
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
version = "v0.4.7"
[[projects]]
branch = "master"
digest = "1:3721a10686511b80c052323423f0de17a8c06d417dbdd3b392b1578432a33aae"
name = "github.com/Nvveen/Gotty"
packages = ["."]
pruneopts = "UT"
revision = "cd527374f1e5bff4938207604a14f2e38a9cf512"
[[projects]]
digest = "1:320e7ead93de9fd2b0e59b50fd92a4d50c1f8ab455d96bc2eb083267453a9709"
name = "github.com/asaskevich/govalidator"
packages = ["."]
pruneopts = "UT"
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
version = "v9"
[[projects]]
branch = "master"
digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d"
name = "github.com/beorn7/perks"
packages = ["quantile"]
pruneopts = "UT"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
digest = "1:2209584c0f7c9b68c23374e659357ab546e1b70eec2761f03280f69a8fd23d77"
name = "github.com/cenkalti/backoff"
packages = ["."]
pruneopts = "UT"
revision = "2ea60e5f094469f9e65adb9cd103795b73ae743e"
version = "v2.0.0"
[[projects]]
branch = "master"
digest = "1:bb300c7f4b52deb1fcc65f6be624ae3dc289453824c2f1f184963dc41d941a68"
name = "github.com/cisco/senml"
packages = ["."]
pruneopts = "UT"
revision = "910a55054e168c1122b905e3f8acdc5ff97cbec1"
[[projects]]
branch = "master"
digest = "1:fc8dbcc2a5de7c093e167828ebbdf551641761d2ad75431d3a167d467a264115"
name = "github.com/containerd/continuity"
packages = ["pathdriver"]
pruneopts = "UT"
revision = "c6cef34830231743494fe2969284df7b82cc0ad0"
[[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:ca71cdedcb487a361fe2588afeb3d373c0dfe21feaac229270fd6de234bd3c5e"
name = "github.com/dereulenspiegel/coap-mux"
packages = ["."]
pruneopts = "UT"
revision = "e137e310a99955be01d60b2237da89153ea40987"
[[projects]]
digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55"
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
pruneopts = "UT"
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
digest = "1:a5036bc703c3994f70ced9b34e9941381aa6bd03e69a0ce538f43bf14dc8dc98"
name = "github.com/docker/docker"
packages = [
"pkg/namesgenerator",
"pkg/random",
]
pruneopts = "UT"
revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
version = "v1.13.1"
[[projects]]
digest = "1:87dcb59127512b84097086504c16595cf8fef35b9e0bfca565dfc06e198158d7"
name = "github.com/docker/go-connections"
packages = ["nat"]
pruneopts = "UT"
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
version = "v0.3.0"
[[projects]]
digest = "1:6f82cacd0af5921e99bf3f46748705239b36489464f4529a1589bc895764fb18"
name = "github.com/docker/go-units"
packages = ["."]
pruneopts = "UT"
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
version = "v0.3.3"
[[projects]]
branch = "master"
digest = "1:f77ee3cd73cbf60434b4f72ec46a66773c555d46764399735b5f9a28505f3ece"
name = "github.com/dustin/go-coap"
packages = ["."]
pruneopts = "UT"
revision = "ddcc80675fa42611359d91a6dfa5aa57fb90e72b"
[[projects]]
digest = "1:184008c955d6a3226b700c13ed0e875a7a051a759618f9bccba9b5c99f17faa5"
name = "github.com/eclipse/paho.mqtt.golang"
packages = [
".",
"packets",
]
pruneopts = "UT"
revision = "36d01c2b4cbeb3d2a12063e4880ce30800af9560"
version = "v1.1.1"
[[projects]]
digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a"
name = "github.com/fatih/color"
packages = ["."]
pruneopts = "UT"
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
digest = "1:90aaa664c659918dd0055e29eb5fd6b96a17a779d423db3dd3f5764c5dbbbd4f"
name = "github.com/go-kit/kit"
packages = [
"endpoint",
"log",
"metrics",
"metrics/internal/lv",
"metrics/prometheus",
"transport/grpc",
"transport/http",
]
pruneopts = "UT"
revision = "ca4112baa34cb55091301bdc13b1420a122b1b9e"
version = "v0.7.0"
[[projects]]
digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659"
name = "github.com/go-logfmt/logfmt"
packages = ["."]
pruneopts = "UT"
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
version = "v0.3.0"
[[projects]]
digest = "1:424f6593024cdf0f6f90cba81bc69ca98df3758525e6fb248198ef15ead603a9"
name = "github.com/go-redis/redis"
packages = [
".",
"internal",
"internal/consistenthash",
"internal/hashtag",
"internal/pool",
"internal/proto",
"internal/singleflight",
"internal/util",
]
pruneopts = "UT"
revision = "7f89fbac80bcc62ce920b6dbc6ca60238d7725d1"
version = "v6.15.0"
[[projects]]
digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406"
name = "github.com/go-stack/stack"
packages = ["."]
pruneopts = "UT"
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
version = "v1.7.0"
[[projects]]
digest = "1:8dfa0b1b7c4b7398d46340aef0f2a58aad5a75959eeeb79e023ab826f0c7f7f0"
name = "github.com/go-zoo/bone"
packages = ["."]
pruneopts = "UT"
revision = "fd0aebc74e908868b09ac140fb5a53cb363884c1"
version = "1.2"
[[projects]]
branch = "master"
digest = "1:348bbd07b4a983b2f811f81948c78142de09bb9fcf25284bee5e2ab3bdd0f6f9"
name = "github.com/gocql/gocql"
packages = [
".",
"internal/lru",
"internal/murmur",
"internal/streams",
]
pruneopts = "UT"
revision = "68ae1e384be4d7cd7df0f5b77f331759d806308e"
[[projects]]
digest = "1:bbadccf3d3317ea03c0dac0b45b673b4b397c8f91a1d2eff550a3c51c4ad770e"
name = "github.com/gogo/protobuf"
packages = ["proto"]
pruneopts = "UT"
revision = "636bf0302bc95575d69441b25a2603156ffdddf1"
version = "v1.1.1"
[[projects]]
digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf"
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp",
]
pruneopts = "UT"
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009"
name = "github.com/golang/snappy"
packages = ["."]
pruneopts = "UT"
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]]
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "UT"
revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d"
version = "v1.4.0"
[[projects]]
digest = "1:364b908b9b27b97ab838f2f6f1b1f46281fa29b978a037d72a9b1d4f6d940190"
name = "github.com/hailocab/go-hostpool"
packages = ["."]
pruneopts = "UT"
revision = "e80d13ce29ede4452c43dea11e79b9bc8a15b478"
[[projects]]
branch = "master"
digest = "1:6727359c2ab1efe714cc87acb36be6cf15a3aae078d8ae736a17fec787dab307"
name = "github.com/hokaccha/go-prettyjson"
packages = ["."]
pruneopts = "UT"
revision = "f579f869bbfea48a61fbea09207cadd1aaeceb83"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:65b0a07f85f7b5cc019c26775efc278a155a5dea0a8aa617c980e8308d16bc55"
name = "github.com/influxdata/influxdb"
packages = [
"client/v2",
"models",
"pkg/escape",
]
pruneopts = "UT"
revision = "a2ba6e7654fb078f8a3f5add1f8d935df38161bd"
version = "v1.6.4"
[[projects]]
branch = "master"
digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72"
name = "github.com/kr/logfmt"
packages = ["."]
pruneopts = "UT"
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
[[projects]]
branch = "master"
digest = "1:1e537dfb7b4d543e25efa2c812084dd65736f8010979c1b7e167a5b876563199"
name = "github.com/lib/pq"
packages = [
".",
"oid",
]
pruneopts = "UT"
revision = "d34b9ff171c21ad295489235aec8b6626023cd04"
[[projects]]
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "UT"
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6"
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
pruneopts = "UT"
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
digest = "1:27fa1b385644151ee1fc0c8b04f1f86963d9701335491b94488fd3912e370354"
name = "github.com/mongodb/mongo-go-driver"
packages = [
"bson",
"bson/bsoncodec",
"bson/bsonrw",
"bson/bsontype",
"bson/primitive",
"event",
"internal",
"mongo",
"mongo/options",
"mongo/readconcern",
"mongo/readpref",
"mongo/writeconcern",
"tag",
"version",
"x/bsonx",
"x/bsonx/bsoncore",
"x/mongo/driver",
"x/mongo/driver/auth",
"x/mongo/driver/auth/internal/gssapi",
"x/mongo/driver/session",
"x/mongo/driver/topology",
"x/mongo/driver/uuid",
"x/network/address",
"x/network/command",
"x/network/compressor",
"x/network/connection",
"x/network/connstring",
"x/network/description",
"x/network/result",
"x/network/wiremessage",
]
pruneopts = "UT"
revision = "3cfdc9ffcf780eede57d27ec26a3edad4c9ab6a9"
version = "v0.2.0"
[[projects]]
digest = "1:4ce94f3d2074662c04677db471456feb56078244ebcfe3789e6e95901c085bc5"
name = "github.com/nats-io/go-nats"
packages = [
".",
"encoders/builtin",
"util",
]
pruneopts = "UT"
revision = "fb0396ee0bdb8018b0fef30d6d1de798ce99cd05"
version = "v1.6.0"
[[projects]]
digest = "1:c3cd663f2f30b92536b9f290ac85c6310dae36a14cb8961553ae9ccf0d85ae41"
name = "github.com/nats-io/nuid"
packages = ["."]
pruneopts = "UT"
revision = "289cccf02c178dc782430d534e3c1f5b72af807f"
version = "v1.0.0"
[[projects]]
digest = "1:ee4d4af67d93cc7644157882329023ce9a7bcfce956a079069a9405521c7cc8d"
name = "github.com/opencontainers/go-digest"
packages = ["."]
pruneopts = "UT"
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
version = "v1.0.0-rc1"
[[projects]]
digest = "1:11db38d694c130c800d0aefb502fb02519e514dc53d9804ce51d1ad25ec27db6"
name = "github.com/opencontainers/image-spec"
packages = [
"specs-go",
"specs-go/v1",
]
pruneopts = "UT"
revision = "d60099175f88c47cd379c4738d158884749ed235"
version = "v1.0.1"
[[projects]]
digest = "1:1869683e323ebff2bdf8adcb560f82bf6f8d94019d35099e3403f7df12e9c07e"
name = "github.com/opencontainers/runc"
packages = [
"libcontainer/system",
"libcontainer/user",
]
pruneopts = "UT"
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
version = "v0.1.1"
[[projects]]
digest = "1:3b830e06971be8448e7c9e57c9cc15ce53ab7e8846e7b7c39110a177c7fae466"
name = "github.com/ory/dockertest"
packages = [
"docker",
"docker/opts",
"docker/pkg/archive",
"docker/pkg/fileutils",
"docker/pkg/homedir",
"docker/pkg/idtools",
"docker/pkg/ioutils",
"docker/pkg/jsonmessage",
"docker/pkg/longpath",
"docker/pkg/mount",
"docker/pkg/pools",
"docker/pkg/stdcopy",
"docker/pkg/system",
"docker/pkg/term",
"docker/pkg/term/windows",
"docker/types",
"docker/types/blkiodev",
"docker/types/container",
"docker/types/filters",
"docker/types/mount",
"docker/types/network",
"docker/types/registry",
"docker/types/strslice",
"docker/types/versions",
]
pruneopts = "UT"
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
version = "v3.3.0"
[[projects]]
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = "UT"
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:26663fafdea73a38075b07e8e9d82fc0056379d2be8bb4e13899e8fda7c7dd23"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/internal",
"prometheus/promhttp",
]
pruneopts = "UT"
revision = "abad2d1bd44235a26707c172eab6bca5bf2dbad3"
version = "v0.9.1"
[[projects]]
branch = "master"
digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b"
name = "github.com/prometheus/client_model"
packages = ["go"]
pruneopts = "UT"
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]]
branch = "master"
digest = "1:e469cd65badf7694aeb44874518606d93c1d59e7735d3754ad442782437d3cc3"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model",
]
pruneopts = "UT"
revision = "d811d2e9bf898806ecfb6ef6296774b13ffc314c"
[[projects]]
branch = "master"
digest = "1:570784e0ddbf67f14087c6d8cfb7b999b9c55e75518a755e88cb202566ea8a17"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfs",
"xfs",
]
pruneopts = "UT"
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
[[projects]]
branch = "master"
digest = "1:89cf776c0a621a34017c819b66b9e0f0bb076d3c5b9ca74763fb89570e140038"
name = "github.com/rubenv/sql-migrate"
packages = [
".",
"sqlparse",
]
pruneopts = "UT"
revision = "ba2c6a7295c59448dbc195cef2f41df5163b3892"
[[projects]]
digest = "1:274f67cb6fed9588ea2521ecdac05a6d62a8c51c074c1fccc6a49a40ba80e925"
name = "github.com/satori/go.uuid"
packages = ["."]
pruneopts = "UT"
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
[[projects]]
digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = "UT"
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
digest = "1:d3ffbc45dfe6929a4ba313464c2d761d96325d306eda29512da56c034db2a4dd"
name = "github.com/sony/gobreaker"
packages = ["."]
pruneopts = "UT"
revision = "b2a34562d02c4d5fd6645399c69e00141fb24e5a"
version = "0.4.0"
[[projects]]
digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = "UT"
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
version = "v0.0.3"
[[projects]]
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:c40d65817cdd41fac9aa7af8bed56927bb2d6d47e4fea566a74880f5c2b1c41e"
name = "github.com/stretchr/testify"
packages = [
"assert",
"require",
]
pruneopts = "UT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
digest = "1:03aa6e485e528acb119fb32901cf99582c380225fc7d5a02758e08b180cb56c3"
name = "github.com/ugorji/go"
packages = ["codec"]
pruneopts = "UT"
revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
version = "v1.1.1"
[[projects]]
branch = "master"
digest = "1:40fdfd6ab85ca32b6935853bbba35935dcb1d796c8135efd85947566c76e662e"
name = "github.com/xdg/scram"
packages = ["."]
pruneopts = "UT"
revision = "7eeb5667e42c09cb51bf7b7c28aea8c56767da90"
[[projects]]
branch = "master"
digest = "1:f5c1d04bc09c644c592b45b9f0bad4030521b1a7d11c7dadbb272d9439fa6e8e"
name = "github.com/xdg/stringprep"
packages = ["."]
pruneopts = "UT"
revision = "73f8eece6fdcd902c185bf651de50f3828bed5ed"
[[projects]]
branch = "master"
digest = "1:7310f5459b88177d24e26c5ec7deb100b78ec03c0437d7a7aaec3c7eac333a55"
name = "golang.org/x/crypto"
packages = [
"bcrypt",
"blowfish",
"pbkdf2",
"ssh/terminal",
]
pruneopts = "UT"
revision = "4d3f4d9ffa16a13f451c3b2999e9c49e9750bf06"
[[projects]]
branch = "master"
digest = "1:70e155783b8dfd4f7697243384e2f361930ff0751459580769ce44d511f38b05"
name = "golang.org/x/net"
packages = [
"context",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/socks",
"internal/timeseries",
"proxy",
"trace",
"websocket",
]
pruneopts = "UT"
revision = "f73e4c9ed3b7ebdd5f699a16a880c2b1994e50dd"
[[projects]]
branch = "master"
digest = "1:e0140c0c868c6e0f01c0380865194592c011fe521d6e12d78bfd33e756fe018a"
name = "golang.org/x/sync"
packages = ["semaphore"]
pruneopts = "UT"
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
[[projects]]
branch = "master"
digest = "1:66e34aa65b83061616603ba83517a4523c7261e18afd01c769dd215614bf7d70"
name = "golang.org/x/sys"
packages = [
"unix",
"windows",
]
pruneopts = "UT"
revision = "7dfd1290c7917b7ba22824b9d24954ab3002fe24"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:cd018653a358d4b743a9d3bee89e825521f2ab2f2ec0770164bf7632d8d73ab7"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "UT"
revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2"
[[projects]]
digest = "1:c3ad9841823db6da420a5625b367913b4ff54bbe60e8e3c98bd20e243e62e2d2"
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclog",
"internal",
"internal/backoff",
"internal/channelz",
"internal/envconfig",
"internal/grpcrand",
"internal/transport",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
]
pruneopts = "UT"
revision = "2e463a05d100327ca47ac218281906921038fd95"
version = "v1.16.0"
[[projects]]
digest = "1:fa9a7c0ef59217bd22f32eb7cc027894d73f340a5633258cc079dec025db6a7f"
name = "gopkg.in/gorp.v1"
packages = ["."]
pruneopts = "UT"
revision = "c87af80f3cc5036b55b83d77171e156791085e2e"
version = "v1.7.1"
[[projects]]
digest = "1:2d1fbdc6777e5408cabeb02bf336305e724b925ff4546ded0fa8715a7267922a"
name = "gopkg.in/inf.v0"
packages = ["."]
pruneopts = "UT"
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
version = "v0.9.1"
[[projects]]
digest = "1:de4bc452b049bde272d99e944aabcf218dc22df0a478a42a7107932f82430ae2"
name = "gopkg.in/ory-am/dockertest.v3"
packages = ["."]
pruneopts = "UT"
revision = "9f1141b78507d23603f6be530587664ebb63a8d2"
version = "v3.3.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/asaskevich/govalidator",
"github.com/cisco/senml",
"github.com/dereulenspiegel/coap-mux",
"github.com/dgrijalva/jwt-go",
"github.com/docker/docker/pkg/namesgenerator",
"github.com/dustin/go-coap",
"github.com/eclipse/paho.mqtt.golang",
"github.com/fatih/color",
"github.com/go-kit/kit/endpoint",
"github.com/go-kit/kit/log",
"github.com/go-kit/kit/metrics",
"github.com/go-kit/kit/metrics/prometheus",
"github.com/go-kit/kit/transport/grpc",
"github.com/go-kit/kit/transport/http",
"github.com/go-redis/redis",
"github.com/go-zoo/bone",
"github.com/gocql/gocql",
"github.com/gogo/protobuf/proto",
"github.com/golang/protobuf/proto",
"github.com/gorilla/websocket",
"github.com/hokaccha/go-prettyjson",
"github.com/influxdata/influxdb/client/v2",
"github.com/lib/pq",
"github.com/mongodb/mongo-go-driver/mongo",
"github.com/nats-io/go-nats",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/rubenv/sql-migrate",
"github.com/satori/go.uuid",
"github.com/sony/gobreaker",
"github.com/spf13/cobra",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"golang.org/x/crypto/bcrypt",
"golang.org/x/net/context",
"google.golang.org/grpc",
"google.golang.org/grpc/codes",
"google.golang.org/grpc/credentials",
"google.golang.org/grpc/status",
"gopkg.in/ory-am/dockertest.v3",
]
solver-name = "gps-cdcl"
solver-version = 1
-115
View File
@@ -1,115 +0,0 @@
[[constraint]]
name = "github.com/asaskevich/govalidator"
version = "9.0.0"
[[constraint]]
name = "github.com/cisco/senml"
branch = "master"
[[constraint]]
name = "github.com/dgrijalva/jwt-go"
version = "3.2.0"
[[constraint]]
name = "github.com/eclipse/paho.mqtt.golang"
version = "1.1.1"
[[constraint]]
name = "github.com/fatih/color"
version = "1.7.0"
[[constraint]]
name = "github.com/go-kit/kit"
version = "0.7.0"
[[constraint]]
name = "github.com/go-zoo/bone"
version = "1.2.0"
[[constraint]]
name = "github.com/golang/protobuf"
version = "1.2.0"
[[constraint]]
name = "github.com/hokaccha/go-prettyjson"
branch = "master"
[[constraint]]
name = "github.com/lib/pq"
branch = "master"
[[constraint]]
name = "github.com/nats-io/go-nats"
version = "1.6.0"
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.9.1"
[[constraint]]
name = "github.com/satori/go.uuid"
version = "1.2.0"
[[constraint]]
name = "github.com/sony/gobreaker"
version = "0.4.0"
[[constraint]]
name = "github.com/spf13/cobra"
version = "0.0.3"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.2"
[[constraint]]
name = "golang.org/x/crypto"
branch = "master"
[[constraint]]
name = "gopkg.in/ory-am/dockertest.v3"
version = "3.3.2"
[[constraint]]
name = "github.com/gorilla/websocket"
version = "1.4.0"
[[constraint]]
name = "github.com/rubenv/sql-migrate"
branch = "master"
[[constraint]]
name = "github.com/influxdata/influxdb"
version = "1.6.4"
[[constraint]]
name = "github.com/mongodb/mongo-go-driver"
version = "0.0.5"
[[constraint]]
name = "github.com/gocql/gocql"
branch = "master"
[[constraint]]
name = "github.com/dereulenspiegel/coap-mux"
branch = "master"
[[constraint]]
name = "github.com/dustin/go-coap"
branch = "master"
[[constraint]]
name = "github.com/gogo/protobuf"
version = "1.1.1"
[[constraint]]
name = "google.golang.org/grpc"
version = "1.16.0"
[[constraint]]
name = "github.com/go-redis/redis"
version = "v6.15.0"
[prune]
go-tests = true
unused-packages = true
+62 -42
View File
@@ -1,57 +1,74 @@
## Copyright (c) 2015-2019
## Mainflux
##
## SPDX-License-Identifier: Apache-2.0
# Copyright (c) Mainflux
# SPDX-License-Identifier: Apache-2.0
BUILD_DIR = build
SERVICES = users things http normalizer ws coap lora influxdb-writer influxdb-reader mongodb-writer mongodb-reader cassandra-writer cassandra-reader cli bootstrap
SERVICES = users things http ws coap lora influxdb-writer influxdb-reader mongodb-writer \
mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader cli \
bootstrap opcua authn
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
GOOS ?= linux
GOARCH ?= amd64
define compile_service
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -ldflags "-s -w" -o ${BUILD_DIR}/mainflux-$(1) cmd/$(1)/main.go
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -mod=vendor -ldflags "-s -w" -o ${BUILD_DIR}/mainflux-$(1) cmd/$(1)/main.go
endef
define make_docker
docker build --no-cache --build-arg SVC_NAME=$(subst docker_,,$(1)) --tag=mainflux/$(subst docker_,,$(1)) -f docker/Dockerfile .
$(eval svc=$(subst docker_,,$(1)))
docker build \
--no-cache \
--build-arg SVC=$(svc) \
--build-arg GOARCH=$(GOARCH) \
--build-arg GOARM=$(GOARM) \
--tag=mainflux/$(svc) \
-f docker/Dockerfile .
endef
define make_docker_dev
docker build --build-arg SVC_NAME=$(subst docker_dev_,,$(1)) --tag=mainflux/$(subst docker_dev_,,$(1)) -f docker/Dockerfile.dev ./build
$(eval svc=$(subst docker_dev_,,$(1)))
docker build \
--no-cache \
--build-arg SVC=$(svc) \
--tag=mainflux/$(svc) \
-f docker/Dockerfile.dev ./build
endef
all: $(SERVICES) mqtt
.PHONY: all $(SERVICES) dockers dockers_dev latest release mqtt ui
.PHONY: all $(SERVICES) dockers dockers_dev latest release mqtt
clean:
rm -rf ${BUILD_DIR}
rm -rf mqtt/node_modules
rm -rf mqtt/aedes/node_modules
cleandocker: cleanghost
cleandocker:
# Stop all containers (if running)
docker-compose -f docker/docker-compose.yml stop
# Remove mainflux containers
docker ps -f name=mainflux -aq | xargs -r docker rm
# Remove exited containers
docker ps -f name=mainflux -f status=dead -f status=exited -aq | xargs -r docker rm -v
# Remove unused images
docker images "mainflux\/*" -f dangling=true -q | xargs -r docker rmi
# Remove old mainflux images
docker images -q mainflux\/* | xargs -r docker rmi
# Clean ghost docker images
cleanghost:
# Remove exited containers
docker ps -f status=dead -f status=exited -aq | xargs -r docker rm -v
# Remove unused images
docker images -f dangling=true -q | xargs -r docker rmi
ifdef pv
# Remove unused volumes
docker volume ls -f dangling=true -q | xargs -r docker volume rm
docker volume ls -f name=mainflux -f dangling=true -q | xargs -r docker volume rm
endif
install:
cp ${BUILD_DIR}/* $(GOBIN)
test:
go test -v -race -count 1 -tags test $(shell go list ./... | grep -v 'vendor\|cmd')
go test -mod=vendor -v -race -count 1 -tags test $(shell go list ./... | grep -v 'vendor\|cmd')
proto:
protoc --gofast_out=plugins=grpc:. *.proto
@@ -60,33 +77,33 @@ $(SERVICES):
$(call compile_service,$(@))
$(DOCKERS):
$(call make_docker,$(@))
docker_ui:
$(MAKE) -C ui docker
docker_mqtt:
# MQTT Docker build must be done from root dir because it copies .proto files
docker build --tag=mainflux/mqtt -f mqtt/Dockerfile .
dockers: $(DOCKERS) docker_ui docker_mqtt
$(call make_docker,$(@),$(GOARCH))
$(DOCKERS_DEV):
$(call make_docker_dev,$(@))
docker_mqtt:
# MQTT Docker build must be done from root dir because it copies .proto files
ifeq ($(GOARCH), arm)
docker build --tag=mainflux/mqtt -f mqtt/aedes/Dockerfile.arm .
else
docker build --tag=mainflux/mqtt -f mqtt/aedes/Dockerfile .
endif
docker_mqtt_verne:
docker build --tag=mainflux/mqtt-verne -f mqtt/verne/Dockerfile .
dockers: $(DOCKERS) docker_mqtt
dockers_dev: $(DOCKERS_DEV)
ui:
$(MAKE) -C ui
mqtt:
cd mqtt && npm install
cd mqtt/aedes && npm install
define docker_push
for svc in $(SERVICES); do \
docker push mainflux/$$svc:$(1); \
done
docker push mainflux/ui:$(1)
docker push mainflux/mqtt:$(1)
endef
@@ -111,12 +128,15 @@ rundev:
cd scripts && ./run.sh
run:
docker-compose -f docker/docker-compose.yml up
runui:
$(MAKE) -C ui run
docker-compose -f docker/docker-compose.yml -f docker/aedes.yml up
runlora:
docker-compose -f docker/docker-compose.yml up -d
docker-compose -f docker/addons/influxdb-writer/docker-compose.yml up -d
docker-compose -f docker/addons/lora-adapter/docker-compose.yml up
docker-compose \
-f docker/docker-compose.yml \
-f docker/aedes.yml up \
-f docker/addons/influxdb-writer/docker-compose.yml \
-f docker/addons/lora-adapter/docker-compose.yml up \
# Run all Mainflux core services except distributed tracing system - Jaeger. Recommended on gateways:
rungw:
MF_JAEGER_URL= docker-compose -f docker/docker-compose.yml -f docker/aedes.yml up --scale jaeger=0
+57 -31
View File
@@ -8,24 +8,26 @@
![banner][banner]
Mainflux is modern, scalable, secure open source and patent-free IoT cloud platform written in Go.
Mainflux is modern, scalable, secure, open-source, and patent-free IoT cloud platform written in Go.
It accepts user, device, and application connections over various network protocols (i.e. HTTP,
It accepts user and thing connections over various network protocols (i.e. HTTP,
MQTT, WebSocket, CoAP), thus making a seamless bridge between them. It is used as the IoT middleware
for building complex IoT solutions.
For more details, check out the [official documentation][docs].
Mainflux is member of the [Linux Foundation][lf] and an active contributor
to the [EdgeX Foundry][edgex] project. It has been made with :heart: by [Mainflux company][company],
to the [EdgeX Foundry][edgex] project. It has been made with :heart: by [Mainflux Labs][company],
which maintains the project and offers professional services around it.
## Features
- Multi-protocol connectivity and protocol bridging (HTTP, MQTT, WebSocket and CoAP)
- Device management and provisioning
- Multi-protocol connectivity and bridging (HTTP, MQTT, WebSocket and CoAP)
- Device management and provisioning (Zero Touch provisioning)
- Mutual TLS Authentication (mTLS) using X.509 Certificates
- Fine-grained access control
- Storage support (Cassandra, InfluxDB and MongoDB)
- Platform logging and instrumentation support
- Message persistence (Cassandra, InfluxDB, MongoDB and PostgresSQL)
- Platform logging and instrumentation support (Grafana, Prometheus and OpenTracing)
- Event sourcing
- Container-based deployment using [Docker][docker] and [Kubernetes][kubernetes]
- [LoRaWAN][lora] network integration
@@ -34,78 +36,101 @@ which maintains the project and offers professional services around it.
- Small memory footprint and fast execution
- Domain-driven design architecture, high-quality code and test coverage
## Prerequisites
The following are needed to run Mainflux:
- [Docker](https://docs.docker.com/install/) (version 18.09)
- [Docker compose](https://docs.docker.com/compose/install/) (version 1.24.1)
Developing Mainflux will also require:
- [Go](https://golang.org/doc/install) (version 1.13.3)
- [Protobuf](https://github.com/protocolbuffers/protobuf#protocol-compiler-installation) (version 3.6.1)
## Install
Before proceeding, install the following prerequisites:
- [Docker](https://docs.docker.com/install/)
- [Docker compose](https://docs.docker.com/compose/install/)
Once everything is installed, execute the following commands from project root:
Once the prerequisites are installed, execute the following commands from the project's root:
```bash
docker-compose -f docker/docker-compose.yml up -d
docker-compose -f docker/docker-compose.yml -f docker/aedes.yml up
```
This will bring up all Mainflux dockers and inter-connect them in the composition.
This will bring up the Mainflux docker services and interconnect them. This command can also be executed using the project's included Makefile:
```bash
make run
```
## Usage
Best way to quickstart using Mainflux is via CLI:
```
The quickest way to start using Mainflux is via the CLI. The latest version can be downloaded from the [official releases page][rel].
It can also be built and used from the project's root directory:
```bash
make cli
./build/mainflux-cli version
```
> Mainflux CLI can also be downloaded as a tarball from [offical release page][rel]
If this works, head to [official documentation][docs] to understand Mainflux provisioning and messaging.
Additional details on using the CLI can be found in the [CLI documentation](https://mainflux.readthedocs.io/en/latest/cli/).
## Documentation
Official documentation is hosted at [Mainflux Read The Docs page][docs].
Documentation is auto-generated from Markdown files in `./docs` directory.
If you spot an error or need for corrections, please let us know - or even better: send us a PR.
Official documentation is hosted at [Mainflux Read The Docs page][docs]. Documentation is auto-generated from Markdown files in `./docs` directory. These can be viewed locally by [installing mkdocs](https://www.mkdocs.org/#installation) and running the command:
```bash
mkdocs serve
```
If you spot an error or a need for corrections, please let us know - or even better: send us a PR.
Additional practical information, news and tutorials can be found on the [Mainflux blog][blog].
## Authors
Main architect and BDFL of Mainflux project is [@drasko][drasko].
Additionally, [@nmarcetic][nikola] and [@janko-isidorovic][janko] assured
overall architecture and design, while [@manuio][manu] and [@darkodraskovic][darko]
helped with crafting initial implementation and continiusly work on the project evolutions.
helped with crafting initial implementation and continuously worked on the project evolutions.
Besides them, Mainflux is constantly improved and actively
developed by [@anovakovic01][alex], [@dusanb94][dusan], [@srados][sava],
[@gsaleh][george], [@blokovi][iva], [@chombium][kole] and a large set of contributors.
[@gsaleh][george], [@blokovi][iva], [@chombium][kole], [@mteodor][mirko] and a large set of contributors.
Maintainers are listed in [MAINTAINERS](MAINTAINERS) file.
Mainflux team would like to give special thanks to [@mijicd][dejan] for his monumental work
on designing and implementing highly improved and optimized version of the platform,
and [@malidukica][dusanm] for his effort on implementing initial user interface.
The Mainflux team would like to give special thanks to [@mijicd][dejan] for his monumental work
on designing and implementing a highly improved and optimized version of the platform,
and [@malidukica][dusanm] for his effort on implementing the initial user interface.
## Contributing
Thank you for your interest in Mainflux and wish to contribute!
1. Take a look at our [open issues](https://github.com/mainflux/mainflux/issues).
Thank you for your interest in Mainflux and the desire to contribute!
1. Take a look at our [open issues](https://github.com/mainflux/mainflux/issues). The [good-first-issue](https://github.com/mainflux/mainflux/labels/good-first-issue) label is specifically for issues that are great for getting started.
2. Checkout the [contribution guide](CONTRIBUTING.md) to learn more about our style and conventions.
3. Make your changes compatible to our workflow.
### We're Hiring
If you are interested in working professionally on Mainflux,
please head to company's [careers page][careers] or shoot us an e-mail at <careers@mainflux.com>.
Note that the best way to grab our attention is by sending PRs :sunglasses:.
>The best way to grab our attention is by sending PRs :sunglasses:.
## Community
- [Google group][forum]
- [Gitter][gitter]
- [Twitter][twitter]
## License
[Apache-2.0](LICENSE)
[banner]: https://github.com/mainflux/mainflux/blob/master/docs/img/gopherBanner.jpg
[banner]: https://github.com/mainflux/docs/blob/master/docs/img/gopherBanner.jpg
[ci-badge]: https://semaphoreci.com/api/v1/mainflux/mainflux/branches/master/badge.svg
[ci-url]: https://semaphoreci.com/mainflux/mainflux
[docs]: http://mainflux.readthedocs.io
@@ -140,3 +165,4 @@ Note that the best way to grab our attention is by sending PRs :sunglasses:.
[iva]: https://github.com/blokovi
[kole]: https://github.com/chombium
[dusanm]: https://github.com/malidukica
[mirko]: https://github.com/mteodor
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package mainflux
+1666
View File
File diff suppressed because it is too large Load Diff
+49
View File
@@ -0,0 +1,49 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
syntax = "proto3";
package mainflux;
import "google/protobuf/empty.proto";
service ThingsService {
rpc CanAccessByKey(AccessByKeyReq) returns (ThingID) {}
rpc CanAccessByID(AccessByIDReq) returns (google.protobuf.Empty) {}
rpc Identify(Token) returns (ThingID) {}
}
service AuthNService {
rpc Issue(IssueReq) returns (Token) {}
rpc Identify(Token) returns (UserID) {}
}
message AccessByKeyReq {
string token = 1;
string chanID = 2;
}
message ThingID {
string value = 1;
}
message AccessByIDReq {
string thingID = 1;
string chanID = 2;
}
// If a token is not carrying any information itself, the type
// field can be used to determine how to validate the token.
// Also, different tokens can be encoded in different ways.
message Token {
string value = 1;
}
message UserID {
string value = 1;
}
message IssueReq {
string issuer = 1;
uint32 type = 2;
}
+106
View File
@@ -0,0 +1,106 @@
# Authentication service
Authentication service provides an API for managing authentication keys.
There are *three types of authentication keys*:
- user key - keys issued to the user upon login request
- API key - keys issued upon the user request
- recovery key - password recovery key
User keys are issued when user logs in. Each user request (other than `registration` and `login`) contains user key that is used to authenticate the user. API keys are similar to the User keys. The main difference is that API keys have configurable expiration time. If no time is set, the key will never expire. For that reason, API keys are _the only key type that can be revoked_. Recovery key is the password recovery key. It's short-lived token used for password recovery process.
For in-depth explanation of the aforementioned scenarios, as well as thorough
understanding of Mainflux, please check out the [official documentation][doc].
The following actions are supported:
- create (all key types)
- verify (all key types)
- obtain (API keys only; secret is never obtained)
- revoke (API keys only)
## Configuration
The service is configured using the environment variables presented in the
following table. Note that any unset variables will be replaced with their
default values.
| Variable | Description | Default |
|---------------------------|--------------------------------------------------------------------------|---------------|
| MF_AUTHN_LOG_LEVEL | Service level (debug, info, warn, error) | error |
| MF_AUTHN_DB_HOST | Database host address | localhost |
| MF_AUTHN_DB_PORT | Database host port | 5432 |
| MF_AUTHN_DB_USER | Database user | mainflux |
| MF_AUTHN_DB_PASSWORD | Database password | mainflux |
| MF_AUTHN_DB | Name of the database used by the service | auth |
| MF_AUTHN_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MF_AUTHN_DB_SSL_CERT | Path to the PEM encoded certificate file | |
| MF_AUTHN_DB_SSL_KEY | Path to the PEM encoded key file | |
| MF_AUTHN_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
| MF_AUTHN_HTTP_PORT | Authn service HTTP port | 8180 |
| MF_AUTHN_GRPC_PORT | Authn service gRPC port | 8181 |
| MF_AUTHN_SERVER_CERT | Path to server certificate in pem format | |
| MF_AUTHN_SERVER_KEY | Path to server key in pem format | |
| MF_AUTHN_SECRET | String used for signing tokens | auth |
| MF_JAEGER_URL | Jaeger server URL | localhost:6831 |
## Deployment
The service itself is distributed as Docker container. The following snippet
provides a compose file template that can be used to deploy the service container
locally:
```yaml
version: "2"
services:
authn:
image: mainflux/authn:[version]
container_name: [instance name]
ports:
- [host machine port]:[configured HTTP port]
environment:
MF_AUTHN_LOG_LEVEL: [Service log level]
MF_AUTHN_DB_HOST: [Database host address]
MF_AUTHN_DB_PORT: [Database host port]
MF_AUTHN_DB_USER: [Database user]
MF_AUTHN_DB_PASS: [Database password]
MF_AUTHN_DB: [Name of the database used by the service]
MF_AUTHN_DB_SSL_MODE: [SSL mode to connect to the database with]
MF_AUTHN_DB_SSL_CERT: [Path to the PEM encoded certificate file]
MF_AUTHN_DB_SSL_KEY: [Path to the PEM encoded key file]
MF_AUTHN_DB_SSL_ROOT_CERT: [Path to the PEM encoded root certificate file]
MF_AUTHN_HTTP_PORT: [Service HTTP port]
MF_AUTHN_GRPC_PORT: [Service gRPC port]
MF_AUTHN_SECRET: [String used for signing tokens]
MF_AUTHN_SERVER_CERT: [String path to server certificate in pem format]
MF_AUTHN_SERVER_KEY: [String path to server key in pem format]
MF_JAEGER_URL: [Jaeger server URL]
```
To start the service outside of the container, execute the following shell script:
```bash
# download the latest version of the service
go get github.com/mainflux/mainflux
cd $GOPATH/src/github.com/mainflux/mainflux
# compile the service
make authn
# copy binary to bin
make install
# set the environment variables and run the service
MF_AUTHN_LOG_LEVEL=[Service log level] MF_AUTHN_DB_HOST=[Database host address] MF_AUTHN_DB_PORT=[Database host port] MF_AUTHN_DB_USER=[Database user] MF_AUTHN_DB_PASS=[Database password] MF_AUTHN_DB=[Name of the database used by the service] MF_AUTHN_DB_SSL_MODE=[SSL mode to connect to the database with] MF_AUTHN_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_AUTHN_DB_SSL_KEY=[Path to the PEM encoded key file] MF_AUTHN_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_AUTHN_HTTP_PORT=[Service HTTP port] MF_AUTHN_GRPC_PORT=[Service gRPC port] MF_AUTHN_SECRET=[String used for signing tokens] MF_AUTHN_SERVER_CERT=[Path to server certificate] MF_AUTHN_SERVER_KEY=[Path to server key] MF_JAEGER_URL=[Jaeger server URL] $GOBIN/mainflux-authn
```
If `MF_EMAIL_TEMPLATE` doesn't point to any file service will function but password reset functionality will not work.
## Usage
For more information about service capabilities and its usage, please check out
the [API documentation](swagger.yaml).
[doc]: http://mainflux.readthedocs.io
+5
View File
@@ -0,0 +1,5 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package api contains implementation of AuthN service HTTP API.
package api
+94
View File
@@ -0,0 +1,94 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"time"
"github.com/go-kit/kit/endpoint"
kitot "github.com/go-kit/kit/tracing/opentracing"
kitgrpc "github.com/go-kit/kit/transport/grpc"
opentracing "github.com/opentracing/opentracing-go"
"github.com/mainflux/mainflux"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
var _ mainflux.AuthNServiceClient = (*grpcClient)(nil)
type grpcClient struct {
issue endpoint.Endpoint
identify endpoint.Endpoint
timeout time.Duration
}
// NewClient returns new gRPC client instance.
func NewClient(tracer opentracing.Tracer, conn *grpc.ClientConn, timeout time.Duration) mainflux.AuthNServiceClient {
return &grpcClient{
issue: kitot.TraceClient(tracer, "issue")(kitgrpc.NewClient(
conn,
"mainflux.AuthNService",
"Issue",
encodeIssueRequest,
decodeIssueResponse,
mainflux.UserID{},
).Endpoint()),
identify: kitot.TraceClient(tracer, "identify")(kitgrpc.NewClient(
conn,
"mainflux.AuthNService",
"Identify",
encodeIdentifyRequest,
decodeIdentifyResponse,
mainflux.UserID{},
).Endpoint()),
timeout: timeout,
}
}
func (client grpcClient) Issue(ctx context.Context, req *mainflux.IssueReq, _ ...grpc.CallOption) (*mainflux.Token, error) {
ctx, close := context.WithTimeout(ctx, client.timeout)
defer close()
res, err := client.issue(ctx, issueReq{issuer: req.GetIssuer(), keyType: req.Type})
if err != nil {
return nil, err
}
ir := res.(identityRes)
return &mainflux.Token{Value: ir.id}, ir.err
}
func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(issueReq)
return &mainflux.IssueReq{Issuer: req.issuer, Type: req.keyType}, nil
}
func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*mainflux.UserID)
return identityRes{res.GetValue(), nil}, nil
}
func (client grpcClient) Identify(ctx context.Context, token *mainflux.Token, _ ...grpc.CallOption) (*mainflux.UserID, error) {
ctx, close := context.WithTimeout(ctx, client.timeout)
defer close()
res, err := client.identify(ctx, identityReq{token: token.GetValue()})
if err != nil {
return nil, err
}
ir := res.(identityRes)
return &mainflux.UserID{Value: ir.id}, ir.err
}
func encodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(identityReq)
return &mainflux.Token{Value: req.token}, nil
}
func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*mainflux.UserID)
return identityRes{res.GetValue(), nil}, nil
}
+5
View File
@@ -0,0 +1,5 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package grpc contains implementation of AuthN service gRPC API.
package grpc
+50
View File
@@ -0,0 +1,50 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"time"
"github.com/go-kit/kit/endpoint"
"github.com/mainflux/mainflux/authn"
context "golang.org/x/net/context"
)
func issueEndpoint(svc authn.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(issueReq)
if err := req.validate(); err != nil {
return nil, err
}
now := time.Now().UTC()
key := authn.Key{
Type: req.keyType,
IssuedAt: now,
}
k, err := svc.Issue(ctx, req.issuer, key)
if err != nil {
return identityRes{}, err
}
return identityRes{k.Secret, nil}, nil
}
}
func identifyEndpoint(svc authn.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(identityReq)
if err := req.validate(); err != nil {
return nil, err
}
id, err := svc.Identify(ctx, req.token)
if err != nil {
return identityRes{}, err
}
return identityRes{id, nil}, nil
}
}
+123
View File
@@ -0,0 +1,123 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc_test
import (
"fmt"
"net"
"testing"
"time"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/authn"
grpcapi "github.com/mainflux/mainflux/authn/api/grpc"
"github.com/mainflux/mainflux/authn/jwt"
"github.com/mainflux/mainflux/authn/mocks"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
port = 8081
secret = "secret"
email = "test@example.com"
)
var svc authn.Service
func newService() authn.Service {
repo := mocks.NewKeyRepository()
idp := mocks.NewIdentityProvider()
t := jwt.New(secret)
return authn.New(repo, idp, t)
}
func startGRPCServer(svc authn.Service, port int) {
listener, _ := net.Listen("tcp", fmt.Sprintf(":%d", port))
server := grpc.NewServer()
mainflux.RegisterAuthNServiceServer(server, grpcapi.NewServer(mocktracer.New(), svc))
go server.Serve(listener)
}
func TestIssue(t *testing.T) {
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
authAddr := fmt.Sprintf("localhost:%d", port)
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
cases := map[string]struct {
token string
id string
kind uint32
err error
}{
"issue for user with valid token": {"", email, authn.UserKey, nil},
"issue for user that doesn't exist": {"", loginKey.Secret, 32, status.Error(codes.InvalidArgument, "received invalid token request")},
}
for desc, tc := range cases {
_, err := client.Issue(context.Background(), &mainflux.IssueReq{Issuer: tc.id, Type: tc.kind})
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s", desc, tc.err, err))
}
}
func TestIdentify(t *testing.T) {
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
resetKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
userKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
authAddr := fmt.Sprintf("localhost:%d", port)
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
cases := []struct {
desc string
token string
id string
err error
}{
{
desc: "identify user with reset token",
token: resetKey.Secret,
id: email,
err: nil,
},
{
desc: "identify user with user token",
token: userKey.Secret,
id: email,
err: nil,
},
{
desc: "identify user with invalid login token",
token: "invalid",
id: "",
err: status.Error(codes.Unauthenticated, "unauthorized access"),
},
{
desc: "identify user that doesn't exist",
token: "",
id: "",
err: status.Error(codes.InvalidArgument, "received invalid token request"),
},
}
for _, tc := range cases {
id, err := client.Identify(context.Background(), &mainflux.Token{Value: tc.token})
assert.Equal(t, tc.id, id.GetValue(), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.id, id.GetValue()))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err))
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
import "github.com/mainflux/mainflux/authn"
type identityReq struct {
token string
kind uint32
}
func (req identityReq) validate() error {
if req.token == "" {
return authn.ErrMalformedEntity
}
if req.kind != authn.UserKey &&
req.kind != authn.APIKey &&
req.kind != authn.RecoveryKey {
return authn.ErrMalformedEntity
}
return nil
}
type issueReq struct {
issuer string
keyType uint32
}
func (req issueReq) validate() error {
if req.issuer == "" {
return authn.ErrUnauthorizedAccess
}
if req.keyType != authn.UserKey &&
req.keyType != authn.APIKey &&
req.keyType != authn.RecoveryKey {
return authn.ErrMalformedEntity
}
return nil
}
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package grpc
+87
View File
@@ -0,0 +1,87 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
kitot "github.com/go-kit/kit/tracing/opentracing"
kitgrpc "github.com/go-kit/kit/transport/grpc"
mainflux "github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/authn"
opentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var _ mainflux.AuthNServiceServer = (*grpcServer)(nil)
type grpcServer struct {
issue kitgrpc.Handler
identify kitgrpc.Handler
}
// NewServer returns new AuthnServiceServer instance.
func NewServer(tracer opentracing.Tracer, svc authn.Service) mainflux.AuthNServiceServer {
return &grpcServer{
issue: kitgrpc.NewServer(
kitot.TraceServer(tracer, "issue")(issueEndpoint(svc)),
decodeIssueRequest,
encodeIssueResponse,
),
identify: kitgrpc.NewServer(
kitot.TraceServer(tracer, "identify")(identifyEndpoint(svc)),
decodeIdentifyRequest,
encodeIdentifyResponse,
),
}
}
func (s *grpcServer) Issue(ctx context.Context, req *mainflux.IssueReq) (*mainflux.Token, error) {
_, res, err := s.issue.ServeGRPC(ctx, req)
if err != nil {
return nil, encodeError(err)
}
return res.(*mainflux.Token), nil
}
func (s *grpcServer) Identify(ctx context.Context, token *mainflux.Token) (*mainflux.UserID, error) {
_, res, err := s.identify.ServeGRPC(ctx, token)
if err != nil {
return nil, encodeError(err)
}
return res.(*mainflux.UserID), nil
}
func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*mainflux.IssueReq)
return issueReq{issuer: req.GetIssuer(), keyType: req.GetType()}, nil
}
func encodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(identityRes)
return &mainflux.Token{Value: res.id}, encodeError(res.err)
}
func decodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*mainflux.Token)
return identityReq{token: req.GetValue()}, nil
}
func encodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(identityRes)
return &mainflux.UserID{Value: res.id}, encodeError(res.err)
}
func encodeError(err error) error {
switch err {
case nil:
return nil
case authn.ErrMalformedEntity:
return status.Error(codes.InvalidArgument, "received invalid token request")
case authn.ErrUnauthorizedAccess, authn.ErrKeyExpired:
return status.Error(codes.Unauthenticated, err.Error())
default:
return status.Error(codes.Internal, "internal server error")
}
}
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package grpc_test
+83
View File
@@ -0,0 +1,83 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package http
import (
"context"
"time"
"github.com/go-kit/kit/endpoint"
"github.com/mainflux/mainflux/authn"
)
func issueEndpoint(svc authn.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(issueKeyReq)
if err := req.validate(); err != nil {
return nil, err
}
now := time.Now().UTC()
newKey := authn.Key{
Issuer: req.issuer,
IssuedAt: now,
Type: req.Type,
}
duration := time.Duration(req.Duration * time.Second)
if duration != 0 {
exp := now.Add(duration)
newKey.ExpiresAt = exp
}
key, err := svc.Issue(ctx, req.issuer, newKey)
if err != nil {
return nil, err
}
res := issueKeyRes{
ID: key.ID,
Value: key.Secret,
IssuedAt: key.IssuedAt,
}
if !key.ExpiresAt.IsZero() {
res.ExpiresAt = &key.ExpiresAt
}
return res, nil
}
}
func revokeEndpoint(svc authn.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(keyReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.Revoke(ctx, req.issuer, req.id); err != nil {
return nil, err
}
return revokeKeyRes{}, nil
}
}
func retrieveEndpoint(svc authn.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(keyReq)
if err := req.validate(); err != nil {
return nil, err
}
key, err := svc.Retrieve(ctx, req.issuer, req.id)
if err != nil {
return nil, err
}
return key, nil
}
}
+288
View File
@@ -0,0 +1,288 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package http_test
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
authn "github.com/mainflux/mainflux/authn"
httpapi "github.com/mainflux/mainflux/authn/api/http"
"github.com/mainflux/mainflux/authn/jwt"
"github.com/mainflux/mainflux/authn/mocks"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/stretchr/testify/assert"
)
const (
secret = "secret"
contentType = "application/json"
invalidEmail = "userexample.com"
wrongID = "123e4567-e89b-12d3-a456-000000000042"
id = "123e4567-e89b-12d3-a456-000000000001"
email = "user@example.com"
)
type issueRequest struct {
Duration time.Duration `json:"duration,omitempty"`
Type uint32 `json:"type,omitempty"`
}
type testRequest struct {
client *http.Client
method string
url string
contentType string
token string
body io.Reader
}
func (tr testRequest) make() (*http.Response, error) {
req, err := http.NewRequest(tr.method, tr.url, tr.body)
if err != nil {
return nil, err
}
if tr.token != "" {
req.Header.Set("Authorization", tr.token)
}
if tr.contentType != "" {
req.Header.Set("Content-Type", tr.contentType)
}
req.Header.Set("Referer", "http://localhost")
return tr.client.Do(req)
}
func newService() authn.Service {
repo := mocks.NewKeyRepository()
idp := mocks.NewIdentityProvider()
t := jwt.New(secret)
return authn.New(repo, idp, t)
}
func newServer(svc authn.Service) *httptest.Server {
mux := httpapi.MakeHandler(svc, mocktracer.New())
return httptest.NewServer(mux)
}
func toJSON(data interface{}) string {
jsonData, _ := json.Marshal(data)
return string(jsonData)
}
func TestIssue(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
ts := newServer(svc)
defer ts.Close()
client := ts.Client()
lk := issueRequest{Type: authn.UserKey}
rk := issueRequest{Type: authn.RecoveryKey}
uk := issueRequest{Type: authn.APIKey, Duration: time.Hour}
cases := []struct {
desc string
req string
ct string
token string
status int
}{
{
desc: "issue login key",
req: toJSON(lk),
ct: contentType,
token: "",
status: http.StatusCreated,
},
{
desc: "issue user key",
req: toJSON(uk),
ct: contentType,
token: loginKey.Secret,
status: http.StatusCreated,
},
{
desc: "issue reset key",
req: toJSON(rk),
ct: contentType,
token: loginKey.Secret,
status: http.StatusBadRequest,
},
{
desc: "issue login key wrong content type",
req: toJSON(lk),
ct: "", token: loginKey.Secret,
status: http.StatusUnsupportedMediaType,
},
{
desc: "issue key wrong content type",
req: toJSON(rk),
ct: "",
token: loginKey.Secret,
status: http.StatusUnsupportedMediaType,
},
{
desc: "issue key unauthorized",
req: toJSON(uk),
ct: contentType,
token: "wrong",
status: http.StatusForbidden,
},
{
desc: "issue reset key with empty token",
req: toJSON(rk),
ct: contentType,
token: "",
status: http.StatusBadRequest,
},
{
desc: "issue key with invalid request",
req: "{",
ct: contentType,
token: "",
status: http.StatusBadRequest,
},
{
desc: "issue key with invalid JSON",
req: "{invalid}",
ct: contentType,
token: "",
status: http.StatusBadRequest,
},
{
desc: "issue key with invalid JSON content",
req: `{"Type":{"key":"value"}}`,
ct: contentType,
token: "",
status: http.StatusBadRequest,
},
}
for _, tc := range cases {
req := testRequest{
client: client,
method: http.MethodPost,
url: fmt.Sprintf("%s/keys", ts.URL),
contentType: tc.ct,
token: tc.token,
body: strings.NewReader(tc.req),
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestRetrieve(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := authn.Key{Type: authn.APIKey, IssuedAt: time.Now()}
k, err := svc.Issue(context.Background(), loginKey.Secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
ts := newServer(svc)
defer ts.Close()
client := ts.Client()
cases := []struct {
desc string
id string
token string
status int
}{
{
desc: "retrieve an existing key",
id: k.ID,
token: loginKey.Secret,
status: http.StatusOK,
},
{
desc: "retrieve a non-existing key",
id: "non-existing",
token: loginKey.Secret,
status: http.StatusNotFound,
},
{
desc: "retrieve a key unauthorized",
id: k.ID,
token: "wrong",
status: http.StatusForbidden,
},
}
for _, tc := range cases {
req := testRequest{
client: client,
method: http.MethodGet,
url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id),
token: tc.token,
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestRevoke(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := authn.Key{Type: authn.APIKey, IssuedAt: time.Now()}
k, err := svc.Issue(context.Background(), loginKey.Secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
ts := newServer(svc)
defer ts.Close()
client := ts.Client()
cases := []struct {
desc string
id string
token string
status int
}{
{
desc: "revoke an existing key",
id: k.ID,
token: loginKey.Secret,
status: http.StatusNoContent,
},
{
desc: "revoke a non-existing key",
id: "non-existing",
token: loginKey.Secret,
status: http.StatusNoContent,
},
{
desc: "revoke a key unauthorized",
id: k.ID,
token: "wrong",
status: http.StatusForbidden},
}
for _, tc := range cases {
req := testRequest{
client: client,
method: http.MethodDelete,
url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id),
token: tc.token,
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package http
import (
"time"
"github.com/mainflux/mainflux/authn"
)
type issueKeyReq struct {
issuer string
Type uint32 `json:"type,omitempty"`
Duration time.Duration `json:"duration,omitempty"`
}
// It is not possible to issue Reset key using HTTP API.
func (req issueKeyReq) validate() error {
if req.Type == authn.UserKey {
return nil
}
if req.issuer == "" || (req.Type != authn.APIKey) {
return authn.ErrMalformedEntity
}
return nil
}
type keyReq struct {
issuer string
id string
}
func (req keyReq) validate() error {
if req.issuer == "" || req.id == "" {
return authn.ErrMalformedEntity
}
return nil
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package http
import (
"net/http"
"time"
"github.com/mainflux/mainflux"
)
var (
_ mainflux.Response = (*issueKeyRes)(nil)
_ mainflux.Response = (*revokeKeyRes)(nil)
)
type issueKeyRes struct {
ID string `json:"id,omitempty"`
Value string `json:"value,omitempty"`
IssuedAt time.Time `json:"issued_at,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
func (res issueKeyRes) Code() int {
return http.StatusCreated
}
func (res issueKeyRes) Headers() map[string]string {
return map[string]string{}
}
func (res issueKeyRes) Empty() bool {
return res.Value == ""
}
type revokeKeyRes struct {
}
func (res revokeKeyRes) Code() int {
return http.StatusNoContent
}
func (res revokeKeyRes) Headers() map[string]string {
return map[string]string{}
}
func (res revokeKeyRes) Empty() bool {
return true
}
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package http
@@ -11,69 +7,78 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
kitot "github.com/go-kit/kit/tracing/opentracing"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux"
log "github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/users"
"github.com/mainflux/mainflux/authn"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const contentType = "application/json"
var (
errUnsupportedContentType = errors.New("unsupported content type")
logger log.Logger
)
var errUnsupportedContentType = errors.New("unsupported content type")
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc users.Service, l log.Logger) http.Handler {
logger = l
func MakeHandler(svc authn.Service, tracer opentracing.Tracer) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(encodeError),
}
mux := bone.New()
mux.Post("/users", kithttp.NewServer(
registrationEndpoint(svc),
decodeCredentials,
mux.Post("/keys", kithttp.NewServer(
kitot.TraceServer(tracer, "issue")(issueEndpoint(svc)),
decodeIssue,
encodeResponse,
opts...,
))
mux.Post("/tokens", kithttp.NewServer(
loginEndpoint(svc),
decodeCredentials,
mux.Get("/keys/:id", kithttp.NewServer(
kitot.TraceServer(tracer, "retrieve")(retrieveEndpoint(svc)),
decodeKeyReq,
encodeResponse,
opts...,
))
mux.GetFunc("/version", mainflux.Version("users"))
mux.Delete("/keys/:id", kithttp.NewServer(
kitot.TraceServer(tracer, "revoke")(revokeEndpoint(svc)),
decodeKeyReq,
encodeResponse,
opts...,
))
mux.GetFunc("/version", mainflux.Version("auth"))
mux.Handle("/metrics", promhttp.Handler())
return mux
}
func decodeCredentials(_ context.Context, r *http.Request) (interface{}, error) {
func decodeIssue(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
logger.Warn("Invalid or missing content type.")
return nil, errUnsupportedContentType
}
var user users.User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
logger.Warn(fmt.Sprintf("Failed to decode user credentials: %s", err))
req := issueKeyReq{
issuer: r.Header.Get("Authorization"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return userReq{user}, nil
return req, nil
}
func decodeKeyReq(_ context.Context, r *http.Request) (interface{}, error) {
req := keyReq{
issuer: r.Header.Get("Authorization"),
id: bone.GetValue(r, "id"),
}
return req, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
@@ -98,18 +103,18 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.Header().Set("Content-Type", contentType)
switch err {
case users.ErrMalformedEntity:
case authn.ErrMalformedEntity:
w.WriteHeader(http.StatusBadRequest)
case users.ErrUnauthorizedAccess:
case authn.ErrUnauthorizedAccess:
w.WriteHeader(http.StatusForbidden)
case users.ErrConflict:
case authn.ErrNotFound:
w.WriteHeader(http.StatusNotFound)
case authn.ErrConflict:
w.WriteHeader(http.StatusConflict)
case io.EOF, io.ErrUnexpectedEOF:
w.WriteHeader(http.StatusBadRequest)
case errUnsupportedContentType:
w.WriteHeader(http.StatusUnsupportedMediaType)
case io.ErrUnexpectedEOF:
w.WriteHeader(http.StatusBadRequest)
case io.EOF:
w.WriteHeader(http.StatusBadRequest)
default:
switch err.(type) {
case *json.SyntaxError:
+83
View File
@@ -0,0 +1,83 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// +build !test
package api
import (
"context"
"fmt"
"time"
"github.com/mainflux/mainflux/authn"
log "github.com/mainflux/mainflux/logger"
)
var _ authn.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger log.Logger
svc authn.Service
}
// LoggingMiddleware adds logging facilities to the core service.
func LoggingMiddleware(svc authn.Service, logger log.Logger) authn.Service {
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) Issue(ctx context.Context, issuer string, newKey authn.Key) (key authn.Key, err error) {
defer func(begin time.Time) {
d := "infinite duration"
if !key.ExpiresAt.IsZero() {
d = fmt.Sprintf("the key with expiration date %v", key.ExpiresAt)
}
message := fmt.Sprintf("Method issue for %s took %s to complete", d, 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.Issue(ctx, issuer, newKey)
}
func (lm *loggingMiddleware) Revoke(ctx context.Context, owner, id string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method revoke for key %s took %s to complete", id, 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.Revoke(ctx, owner, id)
}
func (lm *loggingMiddleware) Retrieve(ctx context.Context, owner, id string) (key authn.Key, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method retrieve for key %s took %s to complete", id, 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.Retrieve(ctx, owner, id)
}
func (lm *loggingMiddleware) Identify(ctx context.Context, key string) (id string, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method identify took %s to complete", 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.Identify(ctx, key)
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"time"
"github.com/go-kit/kit/metrics"
"github.com/mainflux/mainflux/authn"
)
var _ authn.Service = (*metricsMiddleware)(nil)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
svc authn.Service
}
// MetricsMiddleware instruments core service by tracking request count and
// latency.
func MetricsMiddleware(svc authn.Service, counter metrics.Counter, latency metrics.Histogram) authn.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
svc: svc,
}
}
func (ms *metricsMiddleware) Issue(ctx context.Context, issuer string, key authn.Key) (authn.Key, error) {
defer func(begin time.Time) {
ms.counter.With("method", "issue").Add(1)
ms.latency.With("method", "issue").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Issue(ctx, issuer, key)
}
func (ms *metricsMiddleware) Revoke(ctx context.Context, issuer, id string) error {
defer func(begin time.Time) {
ms.counter.With("method", "revoke").Add(1)
ms.latency.With("method", "revoke").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Revoke(ctx, issuer, id)
}
func (ms *metricsMiddleware) Retrieve(ctx context.Context, issuer, id string) (authn.Key, error) {
defer func(begin time.Time) {
ms.counter.With("method", "retrieve").Add(1)
ms.latency.With("method", "retrieve").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Retrieve(ctx, issuer, id)
}
func (ms *metricsMiddleware) Identify(ctx context.Context, key string) (string, error) {
defer func(begin time.Time) {
ms.counter.With("method", "identify").Add(1)
ms.latency.With("method", "identify").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Identify(ctx, key)
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package authn
// IdentityProvider specifies an API for generating unique identifiers.
type IdentityProvider interface {
// ID generates the unique identifier.
ID() (string, error)
}
+110
View File
@@ -0,0 +1,110 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package jwt_test
import (
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/authn"
"github.com/mainflux/mainflux/authn/jwt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const secret = "test"
func key() authn.Key {
exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second)
return authn.Key{
ID: "id",
Type: authn.UserKey,
Issuer: "user@email.com",
Secret: "",
IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second),
ExpiresAt: exp,
}
}
func TestIssue(t *testing.T) {
tokenizer := jwt.New(secret)
emptyIssuer := key()
emptyIssuer.Issuer = ""
cases := []struct {
desc string
key authn.Key
err error
}{
{
desc: "issue new token",
key: key(),
err: nil,
},
}
for _, tc := range cases {
_, err := tokenizer.Issue(tc.key)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err))
}
}
func TestParse(t *testing.T) {
tokenizer := jwt.New(secret)
token, err := tokenizer.Issue(key())
require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err))
userKey := key()
userKey.Type = authn.APIKey
userKey.ExpiresAt = time.Now().UTC().Add(-1 * time.Minute).Round(time.Second)
userToken, err := tokenizer.Issue(userKey)
require.Nil(t, err, fmt.Sprintf("issuing user key expected to succeed: %s", err))
expKey := key()
expKey.ExpiresAt = time.Now().UTC().Add(-1 * time.Minute).Round(time.Second)
expToken, err := tokenizer.Issue(expKey)
require.Nil(t, err, fmt.Sprintf("issuing expired key expected to succeed: %s", err))
cases := []struct {
desc string
key authn.Key
token string
err error
}{
{
desc: "parse valid key",
key: key(),
token: token,
err: nil,
},
{
desc: "parse ivalid key",
key: authn.Key{},
token: "invalid",
err: authn.ErrUnauthorizedAccess,
},
{
desc: "parse expired key",
key: authn.Key{},
token: expToken,
err: authn.ErrKeyExpired,
},
{
desc: "parse expired user key",
key: userKey,
token: userToken,
err: nil,
},
}
for _, tc := range cases {
key, err := tokenizer.Parse(tc.token)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err))
assert.Equal(t, tc.key, key, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.key, key))
}
}
+96
View File
@@ -0,0 +1,96 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package jwt
import (
"time"
"github.com/dgrijalva/jwt-go"
"github.com/mainflux/mainflux/authn"
)
type claims struct {
jwt.StandardClaims
Type *uint32 `json:"type,omitempty"`
}
func (c claims) Valid() error {
if c.Type == nil || *c.Type > authn.APIKey {
return authn.ErrMalformedEntity
}
return c.StandardClaims.Valid()
}
type tokenizer struct {
secret string
}
// New returns new JWT Tokenizer.
func New(secret string) authn.Tokenizer {
return tokenizer{secret: secret}
}
func (svc tokenizer) Issue(key authn.Key) (string, error) {
claims := claims{
StandardClaims: jwt.StandardClaims{
Issuer: key.Issuer,
Subject: key.Secret,
IssuedAt: key.IssuedAt.UTC().Unix(),
},
Type: &key.Type,
}
if !key.ExpiresAt.IsZero() {
claims.ExpiresAt = key.ExpiresAt.UTC().Unix()
}
if key.ID != "" {
claims.Id = key.ID
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(svc.secret))
}
func (svc tokenizer) Parse(token string) (authn.Key, error) {
c := claims{}
_, err := jwt.ParseWithClaims(token, &c, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, authn.ErrUnauthorizedAccess
}
return []byte(svc.secret), nil
})
if err != nil {
if e, ok := err.(*jwt.ValidationError); ok && e.Errors == jwt.ValidationErrorExpired {
// Expired User key needs to be revoked.
if c.Type != nil && *c.Type == authn.APIKey {
return c.toKey(), nil
}
return authn.Key{}, authn.ErrKeyExpired
}
return authn.Key{}, authn.ErrUnauthorizedAccess
}
return c.toKey(), nil
}
func (c claims) toKey() authn.Key {
key := authn.Key{
ID: c.Id,
Issuer: c.Issuer,
Secret: c.Subject,
IssuedAt: time.Unix(c.IssuedAt, 0).UTC(),
}
if c.ExpiresAt != 0 {
key.ExpiresAt = time.Unix(c.ExpiresAt, 0).UTC()
}
// Default type is 0.
if c.Type != nil {
key.Type = *c.Type
}
return key
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package authn
import (
"context"
"errors"
"time"
)
var (
// ErrInvalidKeyIssuedAt indicates that the Key is being used before it's issued.
ErrInvalidKeyIssuedAt = errors.New("invalid issue time")
// ErrKeyExpired indicates that the Key is expired.
ErrKeyExpired = errors.New("use of expired key")
)
const (
// UserKey is temporary User key received on successfull login.
UserKey uint32 = iota
// RecoveryKey represents a key for resseting password.
RecoveryKey
// APIKey enables the one to act on behalf of the user.
APIKey
)
// Key represents API key.
type Key struct {
ID string
Type uint32
Issuer string
Secret string
IssuedAt time.Time
ExpiresAt time.Time
}
// Expired verifies if the key is expired.
func (k Key) Expired() bool {
return k.ExpiresAt.UTC().Before(time.Now().UTC())
}
// KeyRepository specifies Key persistence API.
type KeyRepository interface {
// Save persists the Key. A non-nil error is returned to indicate
// operation failure
Save(context.Context, Key) (string, error)
// Retrieve retrieves Key by its unique identifier.
Retrieve(context.Context, string, string) (Key, error)
// Remove removes Key with provided ID.
Remove(context.Context, string, string) error
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package authn_test
import (
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/authn"
"github.com/stretchr/testify/assert"
)
func TestExpired(t *testing.T) {
exp := time.Now().Add(5 * time.Minute)
exp1 := time.Now()
cases := []struct {
desc string
key authn.Key
expired bool
}{
{
desc: "not expired key",
key: authn.Key{
IssuedAt: time.Now(),
ExpiresAt: exp,
},
expired: false,
},
{
desc: "expired key",
key: authn.Key{
IssuedAt: time.Now().UTC().Add(2 * time.Minute),
ExpiresAt: exp1,
},
expired: true,
},
{
desc: "key with no expiration date",
key: authn.Key{
IssuedAt: time.Now(),
},
expired: true,
},
}
for _, tc := range cases {
res := tc.key.Expired()
assert.Equal(t, tc.expired, res, fmt.Sprintf("%s: expected %t got %t\n", tc.desc, tc.expired, res))
}
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"fmt"
"sync"
"github.com/mainflux/mainflux/authn"
)
var _ authn.IdentityProvider = (*identityProviderMock)(nil)
type identityProviderMock struct {
mu sync.Mutex
counter int
}
func (idp *identityProviderMock) ID() (string, error) {
idp.mu.Lock()
defer idp.mu.Unlock()
idp.counter++
return fmt.Sprintf("%s%012d", "123e4567-e89b-12d3-a456-", idp.counter), nil
}
// NewIdentityProvider creates "mirror" identity provider, i.e. generated
// token will hold value provided by the caller.
func NewIdentityProvider() authn.IdentityProvider {
return &identityProviderMock{}
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"context"
"sync"
"github.com/mainflux/mainflux/authn"
)
var _ authn.KeyRepository = (*keyRepositoryMock)(nil)
type keyRepositoryMock struct {
mu sync.Mutex
keys map[string]authn.Key
}
// NewKeyRepository creates in-memory user repository
func NewKeyRepository() authn.KeyRepository {
return &keyRepositoryMock{
keys: make(map[string]authn.Key),
}
}
func (krm *keyRepositoryMock) Save(ctx context.Context, key authn.Key) (string, error) {
krm.mu.Lock()
defer krm.mu.Unlock()
if _, ok := krm.keys[key.ID]; ok {
return "", authn.ErrConflict
}
krm.keys[key.ID] = key
return key.ID, nil
}
func (krm *keyRepositoryMock) Retrieve(ctx context.Context, issuer, id string) (authn.Key, error) {
krm.mu.Lock()
defer krm.mu.Unlock()
if key, ok := krm.keys[id]; ok && key.Issuer == issuer {
return key, nil
}
return authn.Key{}, authn.ErrNotFound
}
func (krm *keyRepositoryMock) Remove(ctx context.Context, issuer, id string) error {
krm.mu.Lock()
defer krm.mu.Unlock()
if key, ok := krm.keys[id]; ok && key.Issuer == issuer {
delete(krm.keys, id)
}
return nil
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package postgres contains Key repository implementations using
// PostgreSQL as the underlying database.
package postgres
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"fmt"
"github.com/jmoiron/sqlx"
migrate "github.com/rubenv/sql-migrate"
)
// Config defines the options that are used when connecting to a PostgreSQL instance
type Config struct {
Host string
Port string
User string
Pass string
Name string
SSLMode string
SSLCert string
SSLKey string
SSLRootCert string
}
// Connect creates a connection to the PostgreSQL instance and applies any
// unapplied database migrations. A non-nil error is returned to indicate
// failure.
func Connect(cfg Config) (*sqlx.DB, error) {
url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
db, err := sqlx.Open("postgres", url)
if err != nil {
return nil, err
}
if err := migrateDB(db); err != nil {
return nil, err
}
return db, nil
}
func migrateDB(db *sqlx.DB) error {
migrations := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "authn",
Up: []string{
`CREATE TABLE IF NOT EXISTS keys (
id UUID NOT NULL,
type SMALLINT,
issuer VARCHAR(254) NOT NULL,
issued_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP,
PRIMARY KEY (id, issuer)
)`,
},
Down: []string{"DROP TABLE IF EXISTS keys"},
},
},
}
_, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
return err
}
+113
View File
@@ -0,0 +1,113 @@
package postgres
import (
"context"
"database/sql"
"time"
"github.com/lib/pq"
"github.com/mainflux/mainflux/authn"
)
var _ authn.KeyRepository = (*repo)(nil)
const (
errDuplicate = "unique_violation"
errInvalid = "invalid_text_representation"
)
type repo struct {
db Database
}
// New instantiates a PostgreSQL implementation of key repository.
func New(db Database) authn.KeyRepository {
return &repo{
db: db,
}
}
func (kr repo) Save(ctx context.Context, key authn.Key) (string, error) {
q := `INSERT INTO keys (id, type, issuer, issued_at, expires_at)
VALUES (:id, :type, :issuer, :issued_at, :expires_at)`
dbKey := toDBKey(key)
if _, err := kr.db.NamedExecContext(ctx, q, dbKey); err != nil {
pqErr, ok := err.(*pq.Error)
if ok {
if pqErr.Code.Name() == errDuplicate {
return "", authn.ErrConflict
}
}
return "", err
}
return dbKey.ID, nil
}
func (kr repo) Retrieve(ctx context.Context, issuer, id string) (authn.Key, error) {
q := `SELECT id, type, issuer, issued_at, expires_at FROM keys WHERE issuer = $1 AND id = $2`
key := dbKey{}
if err := kr.db.QueryRowxContext(ctx, q, issuer, id).StructScan(&key); err != nil {
pqErr, ok := err.(*pq.Error)
if err == sql.ErrNoRows || ok && errInvalid == pqErr.Code.Name() {
return authn.Key{}, authn.ErrNotFound
}
return authn.Key{}, err
}
return toKey(key), nil
}
func (kr repo) Remove(ctx context.Context, issuer, id string) error {
q := `DELETE FROM keys WHERE issuer = :issuer AND id = :id`
key := dbKey{
ID: id,
Issuer: issuer,
}
if _, err := kr.db.NamedExecContext(ctx, q, key); err != nil {
return err
}
return nil
}
type dbKey struct {
ID string `db:"id"`
Type uint32 `db:"type"`
Issuer string `db:"issuer"`
Revoked bool `db:"revoked"`
IssuedAt time.Time `db:"issued_at"`
ExpiresAt sql.NullTime `db:"expires_at"`
}
func toDBKey(key authn.Key) dbKey {
ret := dbKey{
ID: key.ID,
Type: key.Type,
Issuer: key.Issuer,
IssuedAt: key.IssuedAt,
}
if !key.ExpiresAt.IsZero() {
ret.ExpiresAt = sql.NullTime{Time: key.ExpiresAt, Valid: true}
}
return ret
}
func toKey(key dbKey) authn.Key {
ret := authn.Key{
ID: key.ID,
Type: key.Type,
Issuer: key.Issuer,
IssuedAt: key.IssuedAt,
}
if key.ExpiresAt.Valid {
ret.ExpiresAt = key.ExpiresAt.Time
}
return ret
}
+148
View File
@@ -0,0 +1,148 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/authn"
"github.com/mainflux/mainflux/authn/postgres"
"github.com/mainflux/mainflux/authn/uuid"
"github.com/opentracing/opentracing-go"
"github.com/stretchr/testify/assert"
)
func TestKeySave(t *testing.T) {
dbMiddleware := postgres.NewDatabase(db)
repo := postgres.New(dbMiddleware)
email := "user-save@example.com"
expTime := time.Now().Add(5 * time.Minute)
idp := uuid.New()
id, _ := idp.ID()
cases := []struct {
desc string
key authn.Key
err error
}{
{
desc: "save a new key",
key: authn.Key{
Issuer: email,
IssuedAt: time.Now(),
ExpiresAt: expTime,
ID: id,
},
err: nil,
},
{
desc: "save with duplicate id",
key: authn.Key{
Issuer: email,
IssuedAt: time.Now(),
ExpiresAt: expTime,
ID: id,
},
err: authn.ErrConflict,
},
}
for _, tc := range cases {
_, err := repo.Save(context.Background(), tc.key)
assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestKeyRetrieve(t *testing.T) {
dbMiddleware := postgres.NewDatabase(db)
repo := postgres.New(dbMiddleware)
email := "user-save@example.com"
expTime := time.Now().Add(5 * time.Minute)
idp := uuid.New()
id, _ := idp.ID()
key := authn.Key{
Issuer: email,
IssuedAt: time.Now(),
ExpiresAt: expTime,
ID: id,
}
_, err := repo.Save(context.Background(), key)
assert.Nil(t, err, fmt.Sprintf("Storing Key expected to succeed: %s", err))
cases := []struct {
desc string
id string
issuer string
err error
}{
{
desc: "retrieve an existing key",
id: key.ID,
issuer: key.Issuer,
err: nil,
},
{
desc: "retrieve unauthorized",
id: key.ID,
issuer: "",
err: authn.ErrNotFound,
},
{
desc: "retrieve unknown key",
id: "",
issuer: key.Issuer,
err: authn.ErrNotFound,
},
}
for _, tc := range cases {
_, err := repo.Retrieve(context.Background(), tc.issuer, tc.id)
assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestKeyRemove(t *testing.T) {
dbMiddleware := postgres.NewDatabase(db)
repo := postgres.New(dbMiddleware)
email := "user-save@example.com"
expTime := time.Now().Add(5 * time.Minute)
idp := uuid.New()
id, _ := idp.ID()
key := authn.Key{
Issuer: email,
IssuedAt: time.Now(),
ExpiresAt: expTime,
ID: id,
}
_, err := repo.Save(opentracing.ContextWithSpan(context.Background(), opentracing.StartSpan("")), key)
assert.Nil(t, err, fmt.Sprintf("Storing Key expected to succeed: %s", err))
cases := []struct {
desc string
id string
issuer string
err error
}{
{
desc: "remove an existing key",
id: key.ID,
issuer: key.Issuer,
err: nil,
},
{
desc: "remove key that does not exist",
id: key.ID,
issuer: key.Issuer,
err: nil,
},
}
for _, tc := range cases {
err := repo.Remove(context.Background(), tc.issuer, tc.id)
assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package postgres_test contains tests for PostgreSQL repository
// implementations.
package postgres_test
import (
"database/sql"
"fmt"
"log"
"os"
"testing"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux/authn/postgres"
dockertest "gopkg.in/ory-am/dockertest.v3"
)
const wrong string = "wrong-value"
var db *sqlx.DB
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
cfg := []string{
"POSTGRES_USER=test",
"POSTGRES_PASSWORD=test",
"POSTGRES_DB=test",
}
container, err := pool.Run("postgres", "10.2-alpine", cfg)
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
port := container.GetPort("5432/tcp")
if err := pool.Retry(func() error {
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
db, err := sql.Open("postgres", url)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
dbConfig := postgres.Config{
Host: "localhost",
Port: port,
User: "test",
Pass: "test",
Name: "test",
SSLMode: "disable",
SSLCert: "",
SSLKey: "",
SSLRootCert: "",
}
if db, err = postgres.Connect(dbConfig); err != nil {
log.Fatalf("Could not setup test DB connection: %s", err)
}
defer db.Close()
code := m.Run()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
os.Exit(code)
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"context"
"database/sql"
"github.com/jmoiron/sqlx"
"github.com/opentracing/opentracing-go"
)
var _ Database = (*database)(nil)
type database struct {
db *sqlx.DB
}
// Database provides a database interface
type Database interface {
NamedExecContext(context.Context, string, interface{}) (sql.Result, error)
QueryRowxContext(context.Context, string, ...interface{}) *sqlx.Row
}
// NewDatabase creates a ThingDatabase instance
func NewDatabase(db *sqlx.DB) Database {
return &database{
db: db,
}
}
func (d database) NamedExecContext(ctx context.Context, query string, args interface{}) (sql.Result, error) {
addSpanTags(ctx, query)
return d.db.NamedExecContext(ctx, query, args)
}
func (d database) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row {
addSpanTags(ctx, query)
return d.db.QueryRowxContext(ctx, query, args...)
}
func addSpanTags(ctx context.Context, query string) {
span := opentracing.SpanFromContext(ctx)
if span != nil {
span.SetTag("sql.statement", query)
span.SetTag("span.kind", "client")
span.SetTag("peer.service", "postgres")
span.SetTag("db.type", "sql")
}
}
+197
View File
@@ -0,0 +1,197 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package authn
import (
"context"
"errors"
"time"
)
const (
loginDuration = 10 * time.Hour
resetDuration = 5 * time.Minute
issuerName = "mainflux.authn"
)
var (
// ErrUnauthorizedAccess represents unauthorized access.
ErrUnauthorizedAccess = errors.New("unauthorized access")
// ErrMalformedEntity indicates malformed entity specification (e.g.
// invalid owner or ID).
ErrMalformedEntity = errors.New("malformed entity specification")
// ErrNotFound indicates a non-existing entity request.
ErrNotFound = errors.New("entity not found")
// ErrConflict indicates that entity already exists.
ErrConflict = errors.New("entity already exists")
)
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
// Issue issues a new Key.
Issue(context.Context, string, Key) (Key, error)
// Revoke removes the Key with the provided id that is
// issued by the user identified by the provided key.
Revoke(context.Context, string, string) error
// Retrieve retrieves data for the Key identified by the provided
// ID, that is issued by the user identified by the provided key.
Retrieve(context.Context, string, string) (Key, error)
// Identify validates token token. If token is valid, content
// is returned. If token is invalid, or invocation failed for some
// other reason, non-nil error value is returned in response.
Identify(context.Context, string) (string, error)
}
var _ Service = (*service)(nil)
type service struct {
keys KeyRepository
idp IdentityProvider
tokenizer Tokenizer
}
// New instantiates the auth service implementation.
func New(keys KeyRepository, idp IdentityProvider, tokenizer Tokenizer) Service {
return &service{
tokenizer: tokenizer,
keys: keys,
idp: idp,
}
}
func (svc service) Issue(ctx context.Context, issuer string, key Key) (Key, error) {
if key.IssuedAt.IsZero() {
return Key{}, ErrInvalidKeyIssuedAt
}
switch key.Type {
case APIKey:
return svc.userKey(ctx, issuer, key)
case RecoveryKey:
return svc.resetKey(ctx, issuer, key)
default:
return svc.loginKey(issuer, key)
}
}
func (svc service) Revoke(ctx context.Context, issuer, id string) error {
email, err := svc.login(issuer)
if err != nil {
return err
}
return svc.keys.Remove(ctx, email, id)
}
func (svc service) Retrieve(ctx context.Context, issuer, id string) (Key, error) {
email, err := svc.login(issuer)
if err != nil {
return Key{}, err
}
return svc.keys.Retrieve(ctx, email, id)
}
func (svc service) Identify(ctx context.Context, token string) (string, error) {
c, err := svc.tokenizer.Parse(token)
if err != nil {
return "", err
}
switch c.Type {
case APIKey:
k, err := svc.keys.Retrieve(ctx, c.Issuer, c.ID)
if err != nil {
return "", err
}
// Auto revoke expired key.
if k.Expired() {
svc.keys.Remove(ctx, c.Issuer, c.ID)
return "", ErrKeyExpired
}
return c.Issuer, nil
case RecoveryKey, UserKey:
if c.Issuer != issuerName {
return "", ErrUnauthorizedAccess
}
return c.Secret, nil
default:
return "", ErrUnauthorizedAccess
}
}
func (svc service) loginKey(issuer string, key Key) (Key, error) {
key.Secret = issuer
return svc.tempKey(loginDuration, key)
}
func (svc service) resetKey(ctx context.Context, issuer string, key Key) (Key, error) {
issuer, err := svc.login(issuer)
if err != nil {
return Key{}, err
}
key.Secret = issuer
return svc.tempKey(resetDuration, key)
}
func (svc service) tempKey(duration time.Duration, key Key) (Key, error) {
key.Issuer = issuerName
key.ExpiresAt = key.IssuedAt.Add(duration)
val, err := svc.tokenizer.Issue(key)
if err != nil {
return Key{}, err
}
key.Secret = val
return key, nil
}
func (svc service) userKey(ctx context.Context, issuer string, key Key) (Key, error) {
email, err := svc.login(issuer)
if err != nil {
return Key{}, err
}
key.Issuer = email
id, err := svc.idp.ID()
if err != nil {
return Key{}, err
}
key.ID = id
value, err := svc.tokenizer.Issue(key)
if err != nil {
return Key{}, err
}
key.Secret = value
if _, err := svc.keys.Save(ctx, key); err != nil {
return Key{}, err
}
return key, nil
}
func (svc service) login(token string) (string, error) {
c, err := svc.tokenizer.Parse(token)
if err != nil {
return "", err
}
// Only user key token is valid for login.
if c.Type != UserKey {
return "", ErrUnauthorizedAccess
}
if c.Secret == "" {
return "", ErrUnauthorizedAccess
}
return c.Secret, nil
}
+278
View File
@@ -0,0 +1,278 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package authn_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/authn"
"github.com/mainflux/mainflux/authn/jwt"
"github.com/mainflux/mainflux/authn/mocks"
"github.com/stretchr/testify/assert"
)
const (
secret = "secret"
email = "test@example.com"
)
func newService() authn.Service {
repo := mocks.NewKeyRepository()
idp := mocks.NewIdentityProvider()
t := jwt.New(secret)
return authn.New(repo, idp, t)
}
func TestIssue(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
cases := []struct {
desc string
key authn.Key
issuer string
err error
}{
{
desc: "issue login key",
key: authn.Key{
Type: authn.UserKey,
IssuedAt: time.Now(),
},
issuer: email,
err: nil,
},
{
desc: "issue login key no issue time",
key: authn.Key{
Type: authn.UserKey,
},
issuer: email,
err: authn.ErrInvalidKeyIssuedAt,
},
{
desc: "issue user key",
key: authn.Key{
Type: authn.APIKey,
IssuedAt: time.Now(),
},
issuer: loginKey.Secret,
err: nil,
},
{
desc: "issue user key unauthorized",
key: authn.Key{
Type: authn.APIKey,
IssuedAt: time.Now(),
},
issuer: "",
err: authn.ErrUnauthorizedAccess,
},
{
desc: "issue user key no issue time",
key: authn.Key{
Type: authn.APIKey,
},
issuer: loginKey.Secret,
err: authn.ErrInvalidKeyIssuedAt,
},
{
desc: "issue reset key",
key: authn.Key{
Type: authn.RecoveryKey,
IssuedAt: time.Now(),
},
issuer: loginKey.Secret,
err: nil,
},
{
desc: "issue reset key no issue time",
key: authn.Key{
Type: authn.RecoveryKey,
},
issuer: loginKey.Secret,
err: authn.ErrInvalidKeyIssuedAt,
},
}
for _, tc := range cases {
_, err := svc.Issue(context.Background(), tc.issuer, tc.key)
assert.Equal(t, err, tc.err, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRevoke(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := authn.Key{
Type: authn.APIKey,
IssuedAt: time.Now(),
}
newKey, err := svc.Issue(context.Background(), loginKey.Secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
cases := []struct {
desc string
id string
issuer string
err error
}{
{
desc: "revoke user key",
id: newKey.ID,
issuer: loginKey.Secret,
err: nil,
},
{
desc: "revoke non-existing user key",
id: newKey.ID,
issuer: loginKey.Secret,
err: nil,
},
{
desc: "revoke unauthorized",
id: newKey.ID,
issuer: "",
err: authn.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
err := svc.Revoke(context.Background(), tc.issuer, tc.id)
assert.Equal(t, err, tc.err, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRetrieve(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := authn.Key{
ID: "id",
Type: authn.APIKey,
IssuedAt: time.Now(),
}
newKey, err := svc.Issue(context.Background(), loginKey.Secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
resetKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
userKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
cases := []struct {
desc string
id string
issuer string
err error
}{
{
desc: "retrieve user key",
id: newKey.ID,
issuer: loginKey.Secret,
err: nil,
},
{
desc: "retrieve non-existing user key",
id: "invalid",
issuer: loginKey.Secret,
err: authn.ErrNotFound,
},
{
desc: "retrieve unauthorized",
id: newKey.ID,
issuer: "wrong",
err: authn.ErrUnauthorizedAccess,
},
{
desc: "retrieve with user key",
id: newKey.ID,
issuer: userKey.Secret,
err: authn.ErrUnauthorizedAccess,
},
{
desc: "retrieve with reset key",
id: newKey.ID,
issuer: resetKey.Secret,
err: authn.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
_, err := svc.Retrieve(context.Background(), tc.issuer, tc.id)
assert.Equal(t, err, tc.err, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestIdentify(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
resetKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
userKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
exp1 := time.Now().Add(-2 * time.Second)
expKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: exp1})
assert.Nil(t, err, fmt.Sprintf("Issuing expired user key expected to succeed: %s", err))
invalidKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: 22, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
cases := []struct {
desc string
key string
id string
err error
}{
{
desc: "identify login key",
key: loginKey.Secret,
id: email,
err: nil,
},
{
desc: "identify reset key",
key: resetKey.Secret,
id: email,
err: nil,
},
{
desc: "identify user key",
key: userKey.Secret,
id: email,
err: nil,
},
{
desc: "identify expired user key",
key: expKey.Secret,
id: "",
err: authn.ErrKeyExpired,
},
{
desc: "identify expired key",
key: invalidKey.Secret,
id: "",
err: authn.ErrUnauthorizedAccess,
},
{
desc: "identify invalid key",
key: "invalid",
id: "",
err: authn.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
id, err := svc.Identify(context.Background(), tc.key)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.id, id, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.id, id))
}
}
+127
View File
@@ -0,0 +1,127 @@
swagger: "2.0"
info:
title: Mainflux authentication service
description: HTTP API for managing platform API keys.
version: "1.0.0"
consumes:
- "application/json"
produces:
- "application/json"
paths:
/keys:
post:
summary: Issue API key
description: |
Generates a new API key. Thew new API key will
be uniquely identified by its ID.
tags:
- authn
parameters:
- name: key
description: JSON-formatted document describing the new key.
in: body
schema:
$ref: "#/definitions/Key"
required: true
responses:
201:
description: Issued new key.
400:
description: Failed due to malformed JSON.
409:
description: Failed due to using already existing ID.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
get:
summary: Gets API key details.
description: |
Gets API key details for the given key.
tags:
- authn
parameters:
- $ref: "#/parameters/Authorization"
- name: id
description: API Key id.
in: path
type: string
required: true
responses:
200:
description: Data retrieved.
schema:
$ref: "#/definitions/Key"
400:
description: Failed due to malformed query parameters.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
delete:
summary: Revoke API key
description: |
Revoke API key identified by the given ID.
tags:
- authn
parameters:
- $ref: "#/parameters/Authorization"
- name: id
description: API Key id.
in: path
type: string
required: true
responses:
204:
description: Key revoked.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
definitions:
Key:
type: object
properties:
id:
type: string
format: uuid
example: "c5747f2f-2a7c-4fe1-b41a-51a5ae290945"
description: API key unique identifier
type:
type: integer
example: 0
description: API key type. Keys of different type are processed differently
issuer:
type: string
format: string
example: "test@example.com"
description: User's email or service identifier of API key issuer
secret:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZXhhbXBsZSIsImlhdCI6MTUxNjIzOTAyMn0.9UYAFWmPIn4ojss36LpIGSqABZHfADQmVuKQ4PJBMdI
description: API Key value.
issued_at:
type: string
format: date-time
example: "2019-11-26 13:31:52"
description: Time when the key is generated
expires_at:
type: string
format: date-time
example: "2019-11-26 13:31:52"
description: Time when the Key expires
required:
- type
parameters:
Authorization:
name: Authorization
description: Login key secret (User's access token).
in: header
type: string
required: true
responses:
ServiceError:
description: Unexpected server-side error occurred.
+13
View File
@@ -0,0 +1,13 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package authn
// Tokenizer specifies API for encoding and decoding between string and Key.
type Tokenizer interface {
// Issue converts API Key to its string representation.
Issue(Key) (string, error)
// Parse extracts API Key data from string token.
Parse(string) (Key, error)
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package tracing contains middlewares that will add spans
// to existing traces.
package tracing
import (
"context"
"github.com/mainflux/mainflux/authn"
opentracing "github.com/opentracing/opentracing-go"
)
const (
saveOp = "save"
retrieveOp = "retrieve_by_id"
revokeOp = "remove"
)
var _ authn.KeyRepository = (*keyRepositoryMiddleware)(nil)
// keyRepositoryMiddleware tracks request and their latency, and adds spans
// to context.
type keyRepositoryMiddleware struct {
tracer opentracing.Tracer
repo authn.KeyRepository
}
// New tracks request and their latency, and adds spans
// to context.
func New(repo authn.KeyRepository, tracer opentracing.Tracer) authn.KeyRepository {
return keyRepositoryMiddleware{
tracer: tracer,
repo: repo,
}
}
func (krm keyRepositoryMiddleware) Save(ctx context.Context, key authn.Key) (string, error) {
span := createSpan(ctx, krm.tracer, saveOp)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return krm.repo.Save(ctx, key)
}
func (krm keyRepositoryMiddleware) Retrieve(ctx context.Context, owner, id string) (authn.Key, error) {
span := createSpan(ctx, krm.tracer, retrieveOp)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return krm.repo.Retrieve(ctx, owner, id)
}
func (krm keyRepositoryMiddleware) Remove(ctx context.Context, owner, id string) error {
span := createSpan(ctx, krm.tracer, revokeOp)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return krm.repo.Remove(ctx, owner, id)
}
func createSpan(ctx context.Context, tracer opentracing.Tracer, opName string) opentracing.Span {
if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil {
return tracer.StartSpan(
opName,
opentracing.ChildOf(parentSpan.Context()),
)
}
return tracer.StartSpan(opName)
}
+28
View File
@@ -0,0 +1,28 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package uuid provides a UUID identity provider.
package uuid
import (
"github.com/gofrs/uuid"
"github.com/mainflux/mainflux/authn"
)
var _ authn.IdentityProvider = (*uuidIdentityProvider)(nil)
type uuidIdentityProvider struct{}
// New instantiates a UUID identity provider.
func New() authn.IdentityProvider {
return &uuidIdentityProvider{}
}
func (idp *uuidIdentityProvider) ID() (string, error) {
id, err := uuid.NewV4()
if err != nil {
return "", err
}
return id.String(), nil
}
+47 -31
View File
@@ -1,6 +1,7 @@
# BOOTSTRAP SERVICE
New devices need to be configured properly and connected to the Mainflux. Bootstrap service is used in order to accomplish that. This service provides the following features:
1) Creating new Mainflux Things
2) Providing basic configuration for the newly created Things
3) Enabling/disabling Things
@@ -9,9 +10,10 @@ Pre-provisioning a new Thing is as simple as sending Configuration data to the B
In order to bootstrap successfully, the Thing needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during the manufacturing process. If the Thing is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Thing will be saved so that it can be provisioned later.
***Thing Configuration***
## Thing Configuration Entity
Thing Configuration consists of two logical parts: the custom configuration that can be interpreted by the Thing itself and Mainflux-related configuration. Mainflux config contains:
1) corresponding Mainflux Thing ID
2) corresponding Mainflux Thing key
3) list of the Mainflux channels the Thing is connected to
@@ -27,34 +29,42 @@ Enabling and disabling Thing (adding Thing to/from whitelist) is as simple as co
Switching between states `Active` and `Inactive` enables and disables Thing, respectively.
Thing configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Thing. For example, a device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure.
## Configuration
The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values.
| Variable | Description | Default |
|-------------------------------|-------------------------------------------------------------------------|-----------------------|
| MF_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | error |
| MF_BOOTSTRAP_DB_HOST | Database host address | localhost |
| MF_BOOTSTRAP_DB_PORT | Database host port | 5432 |
| MF_BOOTSTRAP_DB_USER | Database user | mainflux |
| MF_BOOTSTRAP_DB_PASS | Database password | mainflux |
| MF_BOOTSTRAP_DB | Name of the database used by the service | bootstrap |
| MF_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MF_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | |
| MF_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | |
| MF_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
| MF_BOOTSTRAP_CLIENT_TLS | Flag that indicates if TLS should be turned on | false |
| MF_BOOTSTRAP_CA_CERTS | Path to trusted CAs in PEM format | |
| MF_BOOTSTRAP_PORT | Bootstrap service HTTP port | 8180 |
| MF_BOOTSTRAP_SERVER_CERT | Path to server certificate in pem format | |
| MF_BOOTSTRAP_SERVER_KEY | Path to server key in pem format | |
| MF_SDK_BASE_URL | Base url for Mainflux SDK | http://localhost |
| MF_SDK_THINGS_PREFIX | SDK prefix for Things service | |
| MF_USERS_URL | Users service URL | localhost:8181 |
| MF_THINGS_ES_URL | Things service event source URL | localhost:6379 |
| MF_THINGS_ES_PASS | Things service event source password | |
| MF_THINGS_ES_DB | Things service event source database | 0 |
| MF_BOOTSTRAP_INSTANCE_NAME | Bootstrap service instance name | bootstrap |
| Variable | Description | Default |
|-------------------------------|-------------------------------------------------------------------------|----------------------- |
| MF_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | error |
| MF_BOOTSTRAP_DB_HOST | Database host address | localhost |
| MF_BOOTSTRAP_DB_PORT | Database host port | 5432 |
| MF_BOOTSTRAP_DB_USER | Database user | mainflux |
| MF_BOOTSTRAP_DB_PASS | Database password | mainflux |
| MF_BOOTSTRAP_DB | Name of the database used by the service | bootstrap |
| MF_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MF_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | |
| MF_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | |
| MF_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
| MF_BOOTSTRAP_ENCRYPT_KEY | Secret key for secure bootstrapping encryption | 12345678910111213141516171819202 |
| MF_BOOTSTRAP_CLIENT_TLS | Flag that indicates if TLS should be turned on | false |
| MF_BOOTSTRAP_CA_CERTS | Path to trusted CAs in PEM format | |
| MF_BOOTSTRAP_PORT | Bootstrap service HTTP port | 8180 |
| MF_BOOTSTRAP_SERVER_CERT | Path to server certificate in pem format | |
| MF_BOOTSTRAP_SERVER_KEY | Path to server key in pem format | |
| MF_SDK_BASE_URL | Base url for Mainflux SDK | http://localhost |
| MF_SDK_THINGS_PREFIX | SDK prefix for Things service | |
| MF_USERS_URL | Users service URL | localhost:8181 |
| MF_THINGS_ES_URL | Things service event source URL | localhost:6379 |
| MF_THINGS_ES_PASS | Things service event source password | |
| MF_THINGS_ES_DB | Things service event source database | 0 |
| MF_BOOTSTRAP_ES_URL | Bootstrap service event source URL | localhost:6379 |
| MF_BOOTSTRAP_ES_PASS | Bootstrap service event source password | |
| MF_BOOTSTRAP_ES_DB | Bootstrap service event source database | 0 |
| MF_BOOTSTRAP_EVENT_CONSUMER | Bootstrap service event source consumer name | bootstrap |
| MF_JAEGER_URL | Jaeger server URL | localhost:6831 |
| MF_BOOTSTRAP_THINGS_TIMEOUT | Things gRPC request timeout in seconds | 1 |
## Deployment
@@ -83,6 +93,7 @@ version: "2"
MF_BOOTSTRAP_DB_SSL_CERT: [Path to the PEM encoded certificate file]
MF_BOOTSTRAP_DB_SSL_KEY: [Path to the PEM encoded key file]
MF_BOOTSTRAP_DB_SSL_ROOT_CERT: [Path to the PEM encoded root certificate file]
MF_BOOTSTRAP_ENCRYPT_KEY: [Hex-encoded encryption key used for secure bootstrap]
MF_BOOTSTRAP_CLIENT_TLS: [Boolean value to enable/disable client TLS]
MF_BOOTSTRAP_CA_CERTS: [Path to trusted CAs in PEM format]
MF_BOOTSTRAP_PORT: 8200
@@ -91,19 +102,24 @@ version: "2"
MF_SDK_BASE_URL: [Base SDK URL for the Mainflux services]
MF_SDK_THINGS_PREFIX: [SDK prefix for Things service]
MF_USERS_URL: [Users service URL]
MF_THINGS_ES_URL: [Things service event source URL]
MF_THINGS_ES_URL: [Things service event source URL]
MF_THINGS_ES_PASS: [Things service event source password]
MF_THINGS_ES_DB: [Things service event source database]
MF_BOOTSTRAP_INSTANCE_NAME: [Bootstrap service instance name]
```
MF_BOOTSTRAP_ES_URL: [Bootstrap service event source URL]
MF_BOOTSTRAP_ES_PASS: [Bootstrap service event source password]
MF_BOOTSTRAP_ES_DB: [Bootstrap service event source database]
MF_BOOTSTRAP_EVENT_CONSUMER: [Bootstrap service event source consumer name]
MF_JAEGER_URL: [Jaeger server URL]
MF_BOOTSTRAP_THINGS_TIMEOUT: [Things gRPC request timeout in seconds]
```
To start the service outside of the container, execute the following shell script:
```bash
# download the latest version of the service
go get github.com/mainflux/mainflux
git clone https://github.com/mainflux/mainflux
cd $GOPATH/src/github.com/mainflux/mainflux
cd mainflux
# compile the service
make bootstrap
@@ -112,7 +128,7 @@ make bootstrap
make install
# set the environment variables and run the service
MF_BOOTSTRAP_LOG_LEVEL=[Bootstrap log level] MF_BOOTSTRAP_DB_HOST=[Database host address] MF_BOOTSTRAP_DB_PORT=[Database host port] MF_BOOTSTRAP_DB_USER=[Database user] MF_BOOTSTRAP_DB_PASS=[Database password] MF_BOOTSTRAP_DB=[Name of the database used by the service] MF_BOOTSTRAP_DB_SSL_MODE=[SSL mode to connect to the database with] MF_BOOTSTRAP_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_BOOTSTRAP_DB_SSL_KEY=[Path to the PEM encoded key file] MF_BOOTSTRAP_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_BOOTSTRAP_CLIENT_TLS=[Boolean value to enable/disable client TLS] MF_BOOTSTRAP_CA_CERTS=[Path to trusted CAs in PEM format] MF_BOOTSTRAP_PORT=[Service HTTP port] MF_BOOTSTRAP_SERVER_CERT=[Path to server certificate] MF_BOOTSTRAP_SERVER_KEY=[Path to server key] MF_SDK_BASE_URL=[Base SDK URL for the Mainflux services] MF_SDK_THINGS_PREFIX=[SDK prefix for Things service] MF_USERS_URL=[Users service URL] $GOBIN/mainflux-bootstrap
MF_BOOTSTRAP_LOG_LEVEL=[Bootstrap log level] MF_BOOTSTRAP_DB_HOST=[Database host address] MF_BOOTSTRAP_DB_PORT=[Database host port] MF_BOOTSTRAP_DB_USER=[Database user] MF_BOOTSTRAP_DB_PASS=[Database password] MF_BOOTSTRAP_DB=[Name of the database used by the service] MF_BOOTSTRAP_DB_SSL_MODE=[SSL mode to connect to the database with] MF_BOOTSTRAP_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_BOOTSTRAP_DB_SSL_KEY=[Path to the PEM encoded key file] MF_BOOTSTRAP_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_BOOTSTRAP_ENCRYPT_KEY=[Hex-encoded encryption key used for secure bootstrap] MF_BOOTSTRAP_CLIENT_TLS=[Boolean value to enable/disable client TLS] MF_BOOTSTRAP_CA_CERTS=[Path to trusted CAs in PEM format] MF_BOOTSTRAP_PORT=[Service HTTP port] MF_BOOTSTRAP_SERVER_CERT=[Path to server certificate] MF_BOOTSTRAP_SERVER_KEY=[Path to server key] MF_SDK_BASE_URL=[Base SDK URL for the Mainflux services] MF_SDK_THINGS_PREFIX=[SDK prefix for Things service] MF_USERS_URL=[Users service URL] MF_JAEGER_URL=[Jaeger server URL] MF_BOOTSTRAP_THINGS_TIMEOUT=[Things gRPC request timeout in seconds] $GOBIN/mainflux-bootstrap
```
Setting `MF_BOOTSTRAP_CA_CERTS` expects a file in PEM format of trusted CAs. This will enable TLS against the Users gRPC endpoint trusting only those CAs that are provided.
+2 -6
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// Package api contains implementation of things service HTTP API.
// Package api contains implementation of bootstrap service HTTP API.
package api
+24 -8
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api
@@ -32,6 +28,9 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
ExternalKey: req.ExternalKey,
MFChannels: channels,
Name: req.Name,
ClientCert: req.ClientCert,
ClientKey: req.ClientKey,
CACert: req.CACert,
Content: req.Content,
}
@@ -49,6 +48,23 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
}
func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(updateCertReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.UpdateCert(req.key, req.thingID, req.ClientCert, req.ClientKey, req.CACert); err != nil {
return nil, err
}
res := configRes{}
return res, nil
}
}
func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(entityReq)
@@ -208,19 +224,19 @@ func removeEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
}
func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader) endpoint.Endpoint {
func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader, secure bool) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(bootstrapReq)
if err := req.validate(); err != nil {
return nil, err
}
cfg, err := svc.Bootstrap(req.key, req.id)
cfg, err := svc.Bootstrap(req.key, req.id, secure)
if err != nil {
return nil, err
}
return reader.ReadConfig(cfg)
return reader.ReadConfig(cfg, secure)
}
}
+190 -21
View File
@@ -1,13 +1,13 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api_test
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -24,7 +24,8 @@ import (
"github.com/mainflux/mainflux/bootstrap/mocks"
mfsdk "github.com/mainflux/mainflux/sdk/go"
"github.com/mainflux/mainflux/things"
thingsapi "github.com/mainflux/mainflux/things/api/http"
thingsapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -37,7 +38,6 @@ const (
channelsNum = 3
contentType = "application/json"
wrongID = "wrong_id"
metadata = `{"meta": "data"}`
addExternalID = "external-id"
addExternalKey = "external-key"
addName = "name"
@@ -45,7 +45,9 @@ const (
)
var (
encKey = []byte("1234567891011121")
addChannels = []string{"1"}
metadata = map[string]interface{}{"meta": "data"}
addReq = struct {
ThingID string `json:"thing_id"`
ExternalID string `json:"external_id"`
@@ -62,13 +64,19 @@ var (
}
updateReq = struct {
Channels []string `json:"channels"`
Content string `json:"content"`
State bootstrap.State `json:"state"`
Channels []string `json:"channels,omitempty"`
Content string `json:"content,omitempty"`
State bootstrap.State `json:"state,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
ClientKey string `json:"client_key,omitempty"`
CACert string `json:"ca_cert,omitempty"`
}{
Channels: []string{"2", "3"},
Content: "config update",
State: 1,
Channels: []string{"2", "3"},
Content: "config update",
State: 1,
ClientCert: "newcert",
ClientKey: "newkey",
CACert: "newca",
}
)
@@ -88,6 +96,9 @@ func newConfig(channels []bootstrap.Channel) bootstrap.Config {
MFChannels: channels,
Name: addName,
Content: addContent,
ClientCert: "newcert",
ClientKey: "newkey",
CACert: "newca",
}
}
@@ -109,14 +120,44 @@ func (tr testRequest) make() (*http.Response, error) {
return tr.client.Do(req)
}
func newService(users mainflux.UsersServiceClient, unknown map[string]string, url string) bootstrap.Service {
func enc(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(in))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], in)
return ciphertext, nil
}
func dec(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
if len(in) < aes.BlockSize {
return nil, bootstrap.ErrMalformedEntity
}
iv := in[:aes.BlockSize]
in = in[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(in, in)
return in, nil
}
func newService(authn mainflux.AuthNServiceClient, unknown map[string]string, url string) bootstrap.Service {
things := mocks.NewConfigsRepository(unknown)
config := mfsdk.Config{
BaseURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(users, things, sdk)
return bootstrap.New(authn, things, sdk, encKey)
}
func generateChannels() map[string]things.Channel {
@@ -132,17 +173,17 @@ func generateChannels() map[string]things.Channel {
return channels
}
func newThingsService(users mainflux.UsersServiceClient) things.Service {
return mocks.NewThingsService(map[string]things.Thing{}, generateChannels(), users)
func newThingsService(authn mainflux.AuthNServiceClient) things.Service {
return mocks.NewThingsService(map[string]things.Thing{}, generateChannels(), authn)
}
func newThingsServer(svc things.Service) *httptest.Server {
mux := thingsapi.MakeHandler(svc)
mux := thingsapi.MakeHandler(mocktracer.New(), svc)
return httptest.NewServer(mux)
}
func newBootstrapServer(svc bootstrap.Service) *httptest.Server {
mux := bsapi.MakeHandler(svc, bootstrap.NewConfigReader())
mux := bsapi.MakeHandler(svc, bootstrap.NewConfigReader(encKey))
return httptest.NewServer(mux)
}
@@ -288,7 +329,7 @@ func TestView(t *testing.T) {
c.MFChannels = append(c.MFChannels, bootstrap.Channel{
ID: ch.ID,
Name: fmt.Sprintf("%s%s", "name ", id),
Metadata: fmt.Sprintf("{\"type\":\"some type %s\"}", id),
Metadata: map[string]interface{}{"type": fmt.Sprintf("some type %s", id)},
})
}
@@ -361,7 +402,7 @@ func TestView(t *testing.T) {
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
var view config
if err := json.NewDecoder(res.Body).Decode(&view); err != io.EOF {
assert.Nil(t, err, fmt.Sprintf("Decoding expeceted to succeed %s: %s", tc.desc, err))
assert.Nil(t, err, fmt.Sprintf("Decoding expected to succeed %s: %s", tc.desc, err))
}
assert.ElementsMatch(t, tc.res.Channels, view.Channels, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res.Channels, view.Channels))
@@ -466,6 +507,100 @@ func TestUpdate(t *testing.T) {
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestUpdateCert(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, nil, ts.URL)
bs := newBootstrapServer(svc)
c := newConfig([]bootstrap.Channel{bootstrap.Channel{ID: "1"}})
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
data := toJSON(updateReq)
cases := []struct {
desc string
req string
id string
auth string
contentType string
status int
}{
{
desc: "update unauthorized",
req: data,
id: saved.MFThing,
auth: invalidToken,
contentType: contentType,
status: http.StatusForbidden,
},
{
desc: "update with an empty token",
req: data,
id: saved.MFThing,
auth: "",
contentType: contentType,
status: http.StatusForbidden,
},
{
desc: "update a valid config",
req: data,
id: saved.MFThing,
auth: validToken,
contentType: contentType,
status: http.StatusOK,
},
{
desc: "update a config with wrong content type",
req: data,
id: saved.MFThing,
auth: validToken,
contentType: "",
status: http.StatusUnsupportedMediaType,
},
{
desc: "update a non-existing config",
req: data,
id: wrongID,
auth: validToken,
contentType: contentType,
status: http.StatusNotFound,
},
{
desc: "update a config with invalid request format",
req: "}",
id: saved.MFKey,
auth: validToken,
contentType: contentType,
status: http.StatusBadRequest,
},
{
desc: "update a config with an empty request",
id: saved.MFThing,
req: "",
auth: validToken,
contentType: contentType,
status: http.StatusBadRequest,
},
}
for _, tc := range cases {
req := testRequest{
client: bs.Client(),
method: http.MethodPatch,
url: fmt.Sprintf("%s/things/configs/certs/%s", bs.URL, tc.id),
contentType: tc.contentType,
token: tc.auth,
body: strings.NewReader(tc.req),
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestUpdateConnections(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
@@ -999,6 +1134,9 @@ func TestBootstrap(t *testing.T) {
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
encExternKey, err := enc([]byte(c.ExternalKey))
require.Nil(t, err, fmt.Sprintf("Encrypting config expected to succeed: %s.\n", err))
var channels []channel
for _, ch := range saved.MFChannels {
channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
@@ -1009,11 +1147,17 @@ func TestBootstrap(t *testing.T) {
MFKey string `json:"mainflux_key"`
MFChannels []channel `json:"mainflux_channels"`
Content string `json:"content"`
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
CACert string `json:"ca_cert"`
}{
MFThing: saved.MFThing,
MFKey: saved.MFKey,
MFChannels: channels,
Content: saved.Content,
ClientCert: saved.ClientCert,
ClientKey: saved.ClientKey,
CACert: saved.CACert,
}
data := toJSON(s)
@@ -1024,6 +1168,7 @@ func TestBootstrap(t *testing.T) {
external_key string
status int
res string
secure bool
}{
{
desc: "bootstrap a Thing with unknown ID",
@@ -1031,6 +1176,7 @@ func TestBootstrap(t *testing.T) {
external_key: c.ExternalKey,
status: http.StatusNotFound,
res: "",
secure: false,
},
{
desc: "bootstrap a Thing with an empty ID",
@@ -1038,6 +1184,7 @@ func TestBootstrap(t *testing.T) {
external_key: c.ExternalKey,
status: http.StatusBadRequest,
res: "",
secure: false,
},
{
desc: "bootstrap a Thing with unknown key",
@@ -1045,6 +1192,7 @@ func TestBootstrap(t *testing.T) {
external_key: unknown,
status: http.StatusNotFound,
res: "",
secure: false,
},
{
desc: "bootstrap a Thing with an empty key",
@@ -1052,6 +1200,7 @@ func TestBootstrap(t *testing.T) {
external_key: "",
status: http.StatusForbidden,
res: "",
secure: false,
},
{
desc: "bootstrap known Thing",
@@ -1059,6 +1208,23 @@ func TestBootstrap(t *testing.T) {
external_key: c.ExternalKey,
status: http.StatusOK,
res: data,
secure: false,
},
{
desc: "bootstrap secure",
external_id: fmt.Sprintf("secure/%s", c.ExternalID),
external_key: hex.EncodeToString(encExternKey),
status: http.StatusOK,
res: data,
secure: true,
},
{
desc: "bootstrap secure with unencrypted key",
external_id: fmt.Sprintf("secure/%s", c.ExternalID),
external_key: c.ExternalKey,
status: http.StatusNotFound,
res: "",
secure: true,
},
}
@@ -1075,6 +1241,9 @@ func TestBootstrap(t *testing.T) {
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
body, err := ioutil.ReadAll(res.Body)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
if tc.secure {
body, err = dec(body)
}
data := strings.Trim(string(body), "\n")
assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, data))
+21 -12
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
@@ -29,7 +25,7 @@ func NewLoggingMiddleware(svc bootstrap.Service, logger log.Logger) bootstrap.Se
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) Add(key string, thing bootstrap.Config) (saved bootstrap.Config, err error) {
func (lm *loggingMiddleware) Add(key string, cfg bootstrap.Config) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method add for key %s and thing %s took %s to complete", key, saved.MFThing, time.Since(begin))
if err != nil {
@@ -39,7 +35,7 @@ func (lm *loggingMiddleware) Add(key string, thing bootstrap.Config) (saved boot
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Add(key, thing)
return lm.svc.Add(key, cfg)
}
func (lm *loggingMiddleware) View(key, id string) (saved bootstrap.Config, err error) {
@@ -55,9 +51,9 @@ func (lm *loggingMiddleware) View(key, id string) (saved bootstrap.Config, err e
return lm.svc.View(key, id)
}
func (lm *loggingMiddleware) Update(key string, thing bootstrap.Config) (err error) {
func (lm *loggingMiddleware) Update(key string, cfg bootstrap.Config) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update for key %s and thing %s took %s to complete", key, thing.MFThing, time.Since(begin))
message := fmt.Sprintf("Method update for key %s and thing %s took %s to complete", key, cfg.MFThing, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@@ -65,7 +61,20 @@ func (lm *loggingMiddleware) Update(key string, thing bootstrap.Config) (err err
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Update(key, thing)
return lm.svc.Update(key, cfg)
}
func (lm *loggingMiddleware) UpdateCert(key, thingID, clientCert, clientKey, caCert string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update_cert for thing with id %s took %s to complete", thingID, 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.UpdateCert(key, thingID, clientCert, clientKey, caCert)
}
func (lm *loggingMiddleware) UpdateConnections(key, id string, connections []string) (err error) {
@@ -107,7 +116,7 @@ func (lm *loggingMiddleware) Remove(key, id string) (err error) {
return lm.svc.Remove(key, id)
}
func (lm *loggingMiddleware) Bootstrap(externalKey, externalID string) (cfg bootstrap.Config, err error) {
func (lm *loggingMiddleware) Bootstrap(externalKey, externalID string, secure bool) (cfg bootstrap.Config, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method bootstrap for thing with external id %s took %s to complete", externalID, time.Since(begin))
if err != nil {
@@ -117,7 +126,7 @@ func (lm *loggingMiddleware) Bootstrap(externalKey, externalID string) (cfg boot
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Bootstrap(externalKey, externalID)
return lm.svc.Bootstrap(externalKey, externalID, secure)
}
func (lm *loggingMiddleware) ChangeState(key, id string, state bootstrap.State) (err error) {
+16 -11
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
@@ -34,13 +30,13 @@ func MetricsMiddleware(svc bootstrap.Service, counter metrics.Counter, latency m
}
}
func (mm *metricsMiddleware) Add(key string, thing bootstrap.Config) (saved bootstrap.Config, err error) {
func (mm *metricsMiddleware) Add(key string, cfg bootstrap.Config) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "add").Add(1)
mm.latency.With("method", "add").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Add(key, thing)
return mm.svc.Add(key, cfg)
}
func (mm *metricsMiddleware) View(id, key string) (saved bootstrap.Config, err error) {
@@ -52,13 +48,22 @@ func (mm *metricsMiddleware) View(id, key string) (saved bootstrap.Config, err e
return mm.svc.View(id, key)
}
func (mm *metricsMiddleware) Update(key string, thing bootstrap.Config) (err error) {
func (mm *metricsMiddleware) Update(key string, cfg bootstrap.Config) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update").Add(1)
mm.latency.With("method", "update").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Update(key, thing)
return mm.svc.Update(key, cfg)
}
func (mm *metricsMiddleware) UpdateCert(key, thingKey, clientCert, clientKey, caCert string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_cert").Add(1)
mm.latency.With("method", "update_cert").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.UpdateCert(key, thingKey, clientCert, clientKey, caCert)
}
func (mm *metricsMiddleware) UpdateConnections(key, id string, connections []string) (err error) {
@@ -88,13 +93,13 @@ func (mm *metricsMiddleware) Remove(id, key string) (err error) {
return mm.svc.Remove(id, key)
}
func (mm *metricsMiddleware) Bootstrap(externalKey, externalID string) (cfg bootstrap.Config, err error) {
func (mm *metricsMiddleware) Bootstrap(externalKey, externalID string, secure bool) (cfg bootstrap.Config, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "bootstrap").Add(1)
mm.latency.With("method", "bootstrap").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Bootstrap(externalKey, externalID)
return mm.svc.Bootstrap(externalKey, externalID, secure)
}
func (mm *metricsMiddleware) ChangeState(id, key string, state bootstrap.State) (err error) {
+24 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api
@@ -21,6 +17,9 @@ type addReq struct {
Channels []string `json:"channels"`
Name string `json:"name"`
Content string `json:"content"`
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
CACert string `json:"ca_cert"`
}
func (req addReq) validate() error {
@@ -71,6 +70,26 @@ func (req updateReq) validate() error {
return nil
}
type updateCertReq struct {
key string
thingID string
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
CACert string `json:"ca_cert"`
}
func (req updateCertReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.thingID == "" {
return bootstrap.ErrNotFound
}
return nil
}
type updateConnReq struct {
key string
id string
+32
View File
@@ -114,6 +114,38 @@ func TestUpdateReqValidation(t *testing.T) {
}
}
func TestUpdateCertReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
thingID string
err error
}{
{
desc: "empty key",
key: "",
thingID: "thingID",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty thing key",
key: "key",
thingID: "",
err: bootstrap.ErrNotFound,
},
}
for _, tc := range cases {
req := updateCertReq{
key: tc.key,
thingID: tc.thingID,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateConnReqValidation(t *testing.T) {
cases := []struct {
desc string
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api
+42 -6
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api
@@ -62,6 +58,12 @@ func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader) http.Hand
encodeResponse,
opts...))
r.Patch("/things/configs/certs/:id", kithttp.NewServer(
updateCertEndpoint(svc),
decodeUpdateCertRequest,
encodeResponse,
opts...))
r.Put("/things/configs/connections/:id", kithttp.NewServer(
updateConnEndpoint(svc),
decodeUpdateConnRequest,
@@ -81,11 +83,17 @@ func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader) http.Hand
opts...))
r.Get("/things/bootstrap/:external_id", kithttp.NewServer(
bootstrapEndpoint(svc, reader),
bootstrapEndpoint(svc, reader, false),
decodeBootstrapRequest,
encodeResponse,
opts...))
r.Get("/things/bootstrap/secure/:external_id", kithttp.NewServer(
bootstrapEndpoint(svc, reader, true),
decodeBootstrapRequest,
encodeSecureRes,
opts...))
r.Put("/things/state/:id", kithttp.NewServer(
stateEndpoint(svc),
decodeStateRequest,
@@ -131,6 +139,23 @@ func decodeUpdateRequest(_ context.Context, r *http.Request) (interface{}, error
return req, nil
}
func decodeUpdateCertRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
}
req := updateCertReq{
key: r.Header.Get("Authorization"),
thingID: bone.GetValue(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeUpdateConnRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
@@ -238,6 +263,17 @@ func encodeResponse(_ context.Context, w http.ResponseWriter, response interface
return json.NewEncoder(w).Encode(response)
}
func encodeSecureRes(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", contentType)
w.WriteHeader(http.StatusOK)
if b, ok := response.([]byte); ok {
if _, err := w.Write(b); err != nil {
return err
}
}
return nil
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.Header().Set("Content-Type", contentType)
+11 -8
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
@@ -16,6 +12,9 @@ type Config struct {
MFThing string
Owner string
Name string
ClientCert string
ClientKey string
CACert string
MFKey string
MFChannels []Channel
ExternalID string
@@ -28,7 +27,7 @@ type Config struct {
type Channel struct {
ID string
Name string
Metadata interface{}
Metadata map[string]interface{}
}
// Filter is used for the search filters.
@@ -62,12 +61,16 @@ type ConfigRepository interface {
RetrieveAll(string, Filter, uint64, uint64) ConfigsPage
// RetrieveByExternalID returns Config for given external ID.
RetrieveByExternalID(string, string) (Config, error)
RetrieveByExternalID(string) (Config, error)
// Update performs and update to an existing Config. A non-nil error is returned
// Update updates an existing Config. A non-nil error is returned
// to indicate operation failure.
Update(Config) error
// UpdateCerts updates an existing Config certificate and key.
// A non-nil error is returned to indicate operation failure.
UpdateCert(string, string, string, string, string) error
// UpdateConnections updates a list of Channels the Config is connected to
// adding new Channels if needed.
UpdateConnections(string, string, []Channel, []string) error
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// Package bootstrap contains the domain concept definitions needed to support
// Mainflux bootstrap service functionality.
+28 -10
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package mocks
@@ -31,7 +27,7 @@ type configRepositoryMock struct {
unknown map[string]string
}
// NewConfigsRepository creates in-memory thing repository.
// NewConfigsRepository creates in-memory config repository.
func NewConfigsRepository(unknown map[string]string) bootstrap.ConfigRepository {
return &configRepositoryMock{
configs: make(map[string]bootstrap.Config),
@@ -134,13 +130,13 @@ func (crm *configRepositoryMock) RetrieveAll(key string, filter bootstrap.Filter
}
}
func (crm *configRepositoryMock) RetrieveByExternalID(externalKey, externalID string) (bootstrap.Config, error) {
func (crm *configRepositoryMock) RetrieveByExternalID(externalID string) (bootstrap.Config, error) {
crm.mu.Lock()
defer crm.mu.Unlock()
for _, thing := range crm.configs {
if thing.ExternalID == externalID && thing.ExternalKey == externalKey {
return thing, nil
for _, cfg := range crm.configs {
if cfg.ExternalID == externalID {
return cfg, nil
}
}
@@ -163,6 +159,27 @@ func (crm *configRepositoryMock) Update(config bootstrap.Config) error {
return nil
}
func (crm *configRepositoryMock) UpdateCert(owner, thingID, clientCert, clientKey, caCert string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
var forUpdate bootstrap.Config
for _, v := range crm.configs {
if v.MFThing == thingID && v.Owner == owner {
forUpdate = v
break
}
}
if _, ok := crm.configs[forUpdate.MFThing]; !ok {
return bootstrap.ErrNotFound
}
forUpdate.ClientCert = clientCert
forUpdate.ClientKey = clientKey
forUpdate.CACert = caCert
crm.configs[forUpdate.MFThing] = forUpdate
return nil
}
func (crm *configRepositoryMock) UpdateConnections(key, id string, channels []bootstrap.Channel, connections []string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
@@ -171,6 +188,7 @@ func (crm *configRepositoryMock) UpdateConnections(key, id string, channels []bo
if !ok {
return bootstrap.ErrNotFound
}
for _, ch := range channels {
crm.channels[ch.ID] = ch
}
+68 -45
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package mocks
@@ -23,43 +19,45 @@ type mainfluxThings struct {
counter uint64
things map[string]things.Thing
channels map[string]things.Channel
users mainflux.UsersServiceClient
auth mainflux.AuthNServiceClient
connections map[string][]string
}
// NewThingsService returns Mainflux Things service mock.
// Only methods used by SDK are mocked.
func NewThingsService(things map[string]things.Thing, channels map[string]things.Channel, users mainflux.UsersServiceClient) things.Service {
func NewThingsService(things map[string]things.Thing, channels map[string]things.Channel, authn mainflux.AuthNServiceClient) things.Service {
return &mainfluxThings{
things: things,
channels: channels,
users: users,
auth: authn,
connections: make(map[string][]string),
}
}
func (svc *mainfluxThings) AddThing(owner string, thing things.Thing) (things.Thing, error) {
func (svc *mainfluxThings) CreateThings(_ context.Context, owner string, ths ...things.Thing) ([]things.Thing, error) {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.Thing{}, things.ErrUnauthorizedAccess
return []things.Thing{}, things.ErrUnauthorizedAccess
}
for i := range ths {
svc.counter++
ths[i].Owner = userID.Value
ths[i].ID = strconv.FormatUint(svc.counter, 10)
ths[i].Key = ths[i].ID
svc.things[ths[i].ID] = ths[i]
}
svc.counter++
thing.Owner = userID.Value
thing.ID = strconv.FormatUint(svc.counter, 10)
thing.Key = thing.ID
svc.things[thing.ID] = thing
return thing, nil
return ths, nil
}
func (svc *mainfluxThings) ViewThing(owner, id string) (things.Thing, error) {
func (svc *mainfluxThings) ViewThing(_ context.Context, owner, id string) (things.Thing, error) {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.Thing{}, things.ErrUnauthorizedAccess
}
@@ -72,28 +70,31 @@ func (svc *mainfluxThings) ViewThing(owner, id string) (things.Thing, error) {
return things.Thing{}, things.ErrNotFound
}
func (svc *mainfluxThings) Connect(owner, chanID, thingID string) error {
func (svc *mainfluxThings) Connect(_ context.Context, owner string, chIDs, thIDs []string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.ErrUnauthorizedAccess
}
if svc.channels[chanID].Owner != userID.Value {
return things.ErrNotFound
for _, chID := range chIDs {
if svc.channels[chID].Owner != userID.Value {
return things.ErrUnauthorizedAccess
}
for _, thID := range thIDs {
svc.connections[chID] = append(svc.connections[chID], thID)
}
}
svc.connections[chanID] = append(svc.connections[chanID], thingID)
return nil
}
func (svc *mainfluxThings) Disconnect(owner, chanID, thingID string) error {
func (svc *mainfluxThings) Disconnect(_ context.Context, owner, chanID, thingID string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil || svc.channels[chanID].Owner != userID.Value {
return things.ErrUnauthorizedAccess
}
@@ -121,11 +122,11 @@ func (svc *mainfluxThings) Disconnect(owner, chanID, thingID string) error {
return nil
}
func (svc *mainfluxThings) RemoveThing(owner, id string) error {
func (svc *mainfluxThings) RemoveThing(_ context.Context, owner, id string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.ErrUnauthorizedAccess
}
@@ -137,13 +138,13 @@ func (svc *mainfluxThings) RemoveThing(owner, id string) error {
delete(svc.things, id)
conns := make(map[string][]string)
for k, v := range svc.connections {
idx := findIndex(v, id)
if idx != -1 {
i := findIndex(v, id)
if i != -1 {
var tmp []string
if idx != len(v)-2 {
tmp = v[idx+1:]
if i != len(v)-2 {
tmp = v[i+1:]
}
conns[k] = append(v[:idx], tmp...)
conns[k] = append(v[:i], tmp...)
}
}
@@ -151,50 +152,72 @@ func (svc *mainfluxThings) RemoveThing(owner, id string) error {
return nil
}
func (svc *mainfluxThings) ViewChannel(owner, id string) (things.Channel, error) {
func (svc *mainfluxThings) ViewChannel(_ context.Context, owner, id string) (things.Channel, error) {
if c, ok := svc.channels[id]; ok {
return c, nil
}
return things.Channel{}, things.ErrNotFound
}
func (svc *mainfluxThings) UpdateThing(string, things.Thing) error {
func (svc *mainfluxThings) UpdateThing(context.Context, string, things.Thing) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListThings(string, uint64, uint64) (things.ThingsPage, error) {
func (svc *mainfluxThings) UpdateKey(context.Context, string, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListChannelsByThing(string, string, uint64, uint64) (things.ChannelsPage, error) {
func (svc *mainfluxThings) ListThings(context.Context, string, uint64, uint64, string, things.Metadata) (things.ThingsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) ListThingsByChannel(string, string, uint64, uint64) (things.ThingsPage, error) {
func (svc *mainfluxThings) ListChannelsByThing(context.Context, string, string, uint64, uint64) (things.ChannelsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) CreateChannel(string, things.Channel) (things.Channel, error) {
func (svc *mainfluxThings) ListThingsByChannel(context.Context, string, string, uint64, uint64) (things.ThingsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) UpdateChannel(string, things.Channel) error {
func (svc *mainfluxThings) CreateChannels(_ context.Context, owner string, chs ...things.Channel) ([]things.Channel, error) {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return []things.Channel{}, things.ErrUnauthorizedAccess
}
for i := range chs {
svc.counter++
chs[i].Owner = userID.Value
chs[i].ID = strconv.FormatUint(svc.counter, 10)
svc.channels[chs[i].ID] = chs[i]
}
return chs, nil
}
func (svc *mainfluxThings) UpdateChannel(context.Context, string, things.Channel) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListChannels(string, uint64, uint64) (things.ChannelsPage, error) {
func (svc *mainfluxThings) ListChannels(context.Context, string, uint64, uint64, string, things.Metadata) (things.ChannelsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) RemoveChannel(string, string) error {
func (svc *mainfluxThings) RemoveChannel(context.Context, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) CanAccess(string, string) (string, error) {
func (svc *mainfluxThings) CanAccessByKey(context.Context, string, string) (string, error) {
panic("not implemented")
}
func (svc *mainfluxThings) Identify(string) (string, error) {
func (svc *mainfluxThings) CanAccessByID(context.Context, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) Identify(context.Context, string) (string, error) {
panic("not implemented")
}
+16 -10
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package mocks
@@ -15,20 +11,30 @@ import (
"google.golang.org/grpc"
)
var _ mainflux.UsersServiceClient = (*usersServiceMock)(nil)
var _ mainflux.AuthNServiceClient = (*serviceMock)(nil)
type usersServiceMock struct {
type serviceMock struct {
users map[string]string
}
// NewUsersService creates mock of users service.
func NewUsersService(users map[string]string) mainflux.UsersServiceClient {
return &usersServiceMock{users}
func NewUsersService(users map[string]string) mainflux.AuthNServiceClient {
return &serviceMock{users}
}
func (svc usersServiceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
func (svc serviceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
if id, ok := svc.users[in.Value]; ok {
return &mainflux.UserID{Value: id}, nil
}
return nil, users.ErrUnauthorizedAccess
}
func (svc serviceMock) Issue(ctx context.Context, in *mainflux.IssueReq, opts ...grpc.CallOption) (*mainflux.Token, error) {
if id, ok := svc.users[in.GetIssuer()]; ok {
switch in.Type {
default:
return &mainflux.Token{Value: id}, nil
}
}
return nil, users.ErrUnauthorizedAccess
}
+255 -104
View File
@@ -1,17 +1,15 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package postgres
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/logger"
@@ -32,29 +30,28 @@ const (
var _ bootstrap.ConfigRepository = (*configRepository)(nil)
type configRepository struct {
db *sql.DB
db *sqlx.DB
log logger.Logger
}
// NewConfigRepository instantiates a PostgreSQL implementation of thing
// NewConfigRepository instantiates a PostgreSQL implementation of config
// repository.
func NewConfigRepository(db *sql.DB, log logger.Logger) bootstrap.ConfigRepository {
func NewConfigRepository(db *sqlx.DB, log logger.Logger) bootstrap.ConfigRepository {
return &configRepository{db: db, log: log}
}
func (cr configRepository) Save(cfg bootstrap.Config, connections []string) (string, error) {
q := `INSERT INTO configs (mainflux_thing, owner, name, mainflux_key, external_id, external_key, content, state)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`
content := nullString(cfg.Content)
name := nullString(cfg.Name)
tx, err := cr.db.Begin()
q := `INSERT INTO configs (mainflux_thing, owner, name, client_cert, client_key, ca_cert, mainflux_key, external_id, external_key, content, state)
VALUES (:mainflux_thing, :owner, :name, :client_cert, :client_key, :ca_cert, :mainflux_key, :external_id, :external_key, :content, :state)`
tx, err := cr.db.Beginx()
if err != nil {
return "", err
}
if _, err := tx.Exec(q, cfg.MFThing, cfg.Owner, name, cfg.MFKey, cfg.ExternalID, cfg.ExternalKey, content, cfg.State); err != nil {
dbcfg := toDBConfig(cfg)
if _, err := tx.NamedExec(q, dbcfg); err != nil {
e := err
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
e = bootstrap.ErrConflict
@@ -77,9 +74,9 @@ func (cr configRepository) Save(cfg bootstrap.Config, connections []string) (str
return "", err
}
q = "DELETE FROM unknown_configs WHERE external_id = $1 AND external_key = $2"
q = "DELETE FROM unknown_configs WHERE external_id = :external_id AND external_key = :external_key"
if _, err := tx.Exec(q, cfg.ExternalID, cfg.ExternalKey); err != nil {
if _, err := tx.NamedExec(q, dbcfg); err != nil {
cr.rollback("Failed to remove from unknown", tx, err)
return "", err
@@ -93,11 +90,16 @@ func (cr configRepository) Save(cfg bootstrap.Config, connections []string) (str
}
func (cr configRepository) RetrieveByID(key, id string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, external_id, external_key, name, content, state FROM configs WHERE mainflux_thing = $1 AND owner = $2`
cfg := bootstrap.Config{MFThing: id, Owner: key, MFChannels: []bootstrap.Channel{}}
var name, content sql.NullString
if err := cr.db.QueryRow(q, id, key).
Scan(&cfg.MFThing, &cfg.MFKey, &cfg.ExternalID, &cfg.ExternalKey, &name, &content, &cfg.State); err != nil {
q := `SELECT mainflux_thing, mainflux_key, external_id, external_key, name, content, state
FROM configs
WHERE mainflux_thing = $1 AND owner = $2`
dbcfg := dbConfig{
MFThing: id,
Owner: key,
}
if err := cr.db.QueryRowx(q, id, key).StructScan(&dbcfg); err != nil {
empty := bootstrap.Config{}
if err == sql.ErrNoRows {
return empty, bootstrap.ErrNotFound
@@ -107,29 +109,35 @@ func (cr configRepository) RetrieveByID(key, id string) (bootstrap.Config, error
}
q = `SELECT mainflux_channel, name, metadata FROM channels ch
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = $1 AND conn.config_owner = $2`
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = :mainflux_thing AND conn.config_owner = :owner`
rows, err := cr.db.Query(q, cfg.MFThing, cfg.Owner)
rows, err := cr.db.NamedQuery(q, dbcfg)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err))
return bootstrap.Config{}, err
}
defer rows.Close()
chans := []bootstrap.Channel{}
for rows.Next() {
c := bootstrap.Channel{}
if err := rows.Scan(&c.ID, &c.Name, &c.Metadata); err != nil {
dbch := dbChannel{}
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err))
return bootstrap.Config{}, err
}
dbch.Owner = nullString(dbcfg.Owner)
cfg.MFChannels = append(cfg.MFChannels, c)
ch, err := toChannel(dbch)
if err != nil {
return bootstrap.Config{}, err
}
chans = append(chans, ch)
}
cfg.Content = content.String
cfg.Name = name.String
cfg := toConfig(dbcfg)
cfg.MFChannels = chans
return cfg, nil
}
@@ -139,7 +147,7 @@ func (cr configRepository) RetrieveAll(key string, filter bootstrap.Filter, offs
n := len(params)
q := `SELECT mainflux_thing, mainflux_key, external_id, external_key, name, content, state
FROM configs %s ORDER BY mainflux_thing LIMIT $%d OFFSET $%d`
FROM configs %s ORDER BY mainflux_thing LIMIT $%d OFFSET $%d`
q = fmt.Sprintf(q, search, n+1, n+2)
rows, err := cr.db.Query(q, append(params, limit, offset)...)
@@ -180,13 +188,15 @@ func (cr configRepository) RetrieveAll(key string, filter bootstrap.Filter, offs
}
}
func (cr configRepository) RetrieveByExternalID(externalKey, externalID string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, owner, name, content, state FROM configs WHERE external_key = $1 AND external_id = $2`
cfg := bootstrap.Config{ExternalID: externalID, ExternalKey: externalKey, MFChannels: []bootstrap.Channel{}}
var name, content sql.NullString
func (cr configRepository) RetrieveByExternalID(externalID string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, external_key, owner, name, client_cert, client_key, ca_cert, content, state
FROM configs
WHERE external_id = $1`
dbcfg := dbConfig{
ExternalID: externalID,
}
if err := cr.db.QueryRow(q, externalKey, externalID).
Scan(&cfg.MFThing, &cfg.MFKey, &cfg.Owner, &name, &content, &cfg.State); err != nil {
if err := cr.db.QueryRowx(q, externalID).StructScan(&dbcfg); err != nil {
empty := bootstrap.Config{}
if err == sql.ErrNoRows {
return empty, bootstrap.ErrNotFound
@@ -195,29 +205,36 @@ func (cr configRepository) RetrieveByExternalID(externalKey, externalID string)
}
q = `SELECT mainflux_channel, name, metadata FROM channels ch
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = $1 AND conn.config_owner = $2`
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = :mainflux_thing AND conn.config_owner = :owner`
rows, err := cr.db.Query(q, cfg.MFThing, cfg.Owner)
rows, err := cr.db.NamedQuery(q, dbcfg)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err))
return bootstrap.Config{}, err
}
defer rows.Close()
channels := []bootstrap.Channel{}
for rows.Next() {
c := bootstrap.Channel{}
if err := rows.Scan(&c.ID, &c.Name, &c.Metadata); err != nil {
dbch := dbChannel{}
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err))
return bootstrap.Config{}, err
}
cfg.MFChannels = append(cfg.MFChannels, c)
ch, err := toChannel(dbch)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err))
return bootstrap.Config{}, err
}
channels = append(channels, ch)
}
cfg.Content = content.String
cfg.Name = name.String
cfg := toConfig(dbcfg)
cfg.MFChannels = channels
return cfg, nil
}
@@ -245,8 +262,28 @@ func (cr configRepository) Update(cfg bootstrap.Config) error {
return nil
}
func (cr configRepository) UpdateCert(owner, thingID, clientCert, clientKey, caCert string) error {
q := `UPDATE configs SET client_cert = $1, client_key = $2, ca_cert = $3 WHERE mainflux_thing = $4 AND owner = $5`
res, err := cr.db.Exec(q, clientCert, clientKey, caCert, thingID, owner)
if err != nil {
return err
}
cnt, err := res.RowsAffected()
if err != nil {
return err
}
if cnt == 0 {
return bootstrap.ErrNotFound
}
return nil
}
func (cr configRepository) UpdateConnections(key, id string, channels []bootstrap.Channel, connections []string) error {
tx, err := cr.db.Begin()
tx, err := cr.db.Beginx()
if err != nil {
return err
}
@@ -309,19 +346,28 @@ func (cr configRepository) ChangeState(key, id string, state bootstrap.State) er
}
func (cr configRepository) ListExisting(key string, ids []string) ([]bootstrap.Channel, error) {
q := "SELECT mainflux_channel, name, metadata FROM channels WHERE owner = $1 AND mainflux_channel = ANY ($2)"
var channels []bootstrap.Channel
if len(ids) == 0 {
return channels, nil
}
rows, err := cr.db.Query(q, key, pq.Array(ids))
q := "SELECT mainflux_channel, name, metadata FROM channels WHERE owner = $1 AND mainflux_channel = ANY ($2)"
rows, err := cr.db.Queryx(q, key, pq.Array(ids))
if err != nil {
return []bootstrap.Channel{}, err
}
var channels []bootstrap.Channel
for rows.Next() {
var ch bootstrap.Channel
if err := rows.Scan(&ch.ID, &ch.Name, &ch.Metadata); err != nil {
var dbch dbChannel
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read retrieved channels due to %s", err))
return []bootstrap.Channel{}, nil
return []bootstrap.Channel{}, err
}
ch, err := toChannel(dbch)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err))
return []bootstrap.Channel{}, err
}
channels = append(channels, ch)
@@ -391,8 +437,13 @@ func (cr configRepository) RemoveThing(id string) error {
}
func (cr configRepository) UpdateChannel(channel bootstrap.Channel) error {
q := `UPDATE channels SET name = $1, metadata = $2 WHERE mainflux_channel = $3`
_, err := cr.db.Exec(q, channel.Name, channel.Metadata, channel.ID)
dbch, err := toDBChannel("", channel)
if err != nil {
return err
}
q := `UPDATE channels SET name = :name, metadata = :metadata WHERE mainflux_channel = :mainflux_channel`
_, err = cr.db.NamedExec(q, dbch)
return err
}
@@ -435,7 +486,7 @@ func (cr configRepository) retrieveAll(key string, filter bootstrap.Filter) (str
return fmt.Sprintf(template, f), params
}
func (cr configRepository) rollback(content string, tx *sql.Tx, err error) {
func (cr configRepository) rollback(content string, tx *sqlx.Tx, err error) {
cr.log.Error(fmt.Sprintf("%s %s", content, err))
if err := tx.Rollback(); err != nil {
@@ -443,24 +494,23 @@ func (cr configRepository) rollback(content string, tx *sql.Tx, err error) {
}
}
func insertChannels(key string, channels []bootstrap.Channel, tx *sql.Tx) error {
func insertChannels(key string, channels []bootstrap.Channel, tx *sqlx.Tx) error {
if len(channels) == 0 {
return nil
}
q := `INSERT INTO channels (mainflux_channel, owner, name, metadata) VALUES `
v := []interface{}{key}
var vals []string
// Since the first value is owner, start with the second one.
count := 2
var chans []dbChannel
for _, ch := range channels {
vals = append(vals, fmt.Sprintf("($%d, $1, $%d, $%d)", count, count+1, count+2))
v = append(v, ch.ID, ch.Name, ch.Metadata)
count += chanFieldsNum
dbch, err := toDBChannel(key, ch)
if err != nil {
return err
}
chans = append(chans, dbch)
}
q = fmt.Sprintf("%s%s", q, strings.Join(vals, ","))
if _, err := tx.Exec(q, v...); err != nil {
q := `INSERT INTO channels (mainflux_channel, owner, name, metadata)
VALUES (:mainflux_channel, :owner, :name, :metadata)`
if _, err := tx.NamedExec(q, chans); err != nil {
e := err
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
e = bootstrap.ErrConflict
@@ -471,44 +521,38 @@ func insertChannels(key string, channels []bootstrap.Channel, tx *sql.Tx) error
return nil
}
func insertConnections(cfg bootstrap.Config, connections []string, tx *sql.Tx) error {
func insertConnections(cfg bootstrap.Config, connections []string, tx *sqlx.Tx) error {
if len(connections) == 0 {
return nil
}
q := `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner) VALUES`
v := []interface{}{cfg.MFThing, cfg.Owner}
var vals []string
// Since the first value is Config ID and the second and third
// are Config owner, start with the second one.
count := 3
for _, id := range connections {
vals = append(vals, fmt.Sprintf("($1, $%d, $2, $2)", count))
v = append(v, id)
count++
q := `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner)
VALUES (:config_id, :channel_id, :config_owner, :channel_owner)`
conns := []dbConnection{}
for _, conn := range connections {
dbconn := dbConnection{
Config: cfg.MFThing,
Channel: conn,
ConfigOwner: cfg.Owner,
ChannelOwner: cfg.Owner,
}
conns = append(conns, dbconn)
}
q = fmt.Sprintf("%s%s", q, strings.Join(vals, ","))
_, err := tx.Exec(q, v...)
_, err := tx.NamedExec(q, conns)
return err
}
func updateConnections(key, id string, connections []string, tx *sql.Tx) error {
func updateConnections(key, id string, connections []string, tx *sqlx.Tx) error {
if len(connections) == 0 {
return nil
}
q := `DELETE FROM connections
WHERE config_id = $1 AND config_owner = $2 AND channel_owner = $2
AND channel_id NOT IN ($3)`
v := []interface{}{id, key}
v = append(v, pq.Array(connections))
res, err := tx.Exec(q, v...)
WHERE config_id = $1 AND config_owner = $2 AND channel_owner = $2
AND channel_id NOT IN ($3)`
res, err := tx.Exec(q, id, key, pq.Array(connections))
if err != nil {
return err
}
@@ -518,22 +562,21 @@ func updateConnections(key, id string, connections []string, tx *sql.Tx) error {
return err
}
q = `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner) VALUES`
v = []interface{}{id, key}
var vals []string
q = `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner)
VALUES (:config_id, :channel_id, :config_owner, :channel_owner)`
// Since the first value is Config ID and the second is Config
// owner, start with the third one.
count := 3
for _, chID := range connections {
vals = append(vals, fmt.Sprintf("($1, $%d, $2, $2)", count))
v = append(v, chID)
count++
conns := []dbConnection{}
for _, conn := range connections {
dbconn := dbConnection{
Config: id,
Channel: conn,
ConfigOwner: key,
ChannelOwner: key,
}
conns = append(conns, dbconn)
}
// Add connections for current list of channels. Ignore if already exists.
q = fmt.Sprintf("%s%s%s", q, strings.Join(vals, ","), "ON CONFLICT (config_id, config_owner, channel_id, channel_owner) DO NOTHING")
if _, err := tx.Exec(q, v...); err != nil {
if _, err := tx.NamedExec(q, conns); err != nil {
return err
}
@@ -556,3 +599,111 @@ func nullString(s string) sql.NullString {
Valid: true,
}
}
type dbConfig struct {
MFThing string `db:"mainflux_thing"`
Owner string `db:"owner"`
Name sql.NullString `db:"name"`
ClientCert sql.NullString `db:"client_cert"`
ClientKey sql.NullString `db:"client_key"`
CaCert sql.NullString `db:"ca_cert"`
MFKey string `db:"mainflux_key"`
ExternalID string `db:"external_id"`
ExternalKey string `db:"external_key"`
Content sql.NullString `db:"content"`
State bootstrap.State `db:"state"`
}
func toDBConfig(cfg bootstrap.Config) dbConfig {
return dbConfig{
MFThing: cfg.MFThing,
Owner: cfg.Owner,
Name: nullString(cfg.Name),
ClientCert: nullString(cfg.ClientCert),
ClientKey: nullString(cfg.ClientKey),
CaCert: nullString(cfg.CACert),
MFKey: cfg.MFKey,
ExternalID: cfg.ExternalID,
ExternalKey: cfg.ExternalKey,
Content: nullString(cfg.Content),
State: cfg.State,
}
}
func toConfig(dbcfg dbConfig) bootstrap.Config {
cfg := bootstrap.Config{
MFThing: dbcfg.MFThing,
Owner: dbcfg.Owner,
MFKey: dbcfg.MFKey,
ExternalID: dbcfg.ExternalID,
ExternalKey: dbcfg.ExternalKey,
State: dbcfg.State,
}
if dbcfg.Name.Valid {
cfg.Name = dbcfg.Name.String
}
if dbcfg.Content.Valid {
cfg.Content = dbcfg.Content.String
}
if dbcfg.ClientCert.Valid {
cfg.ClientCert = dbcfg.ClientCert.String
}
if dbcfg.ClientKey.Valid {
cfg.ClientKey = dbcfg.ClientKey.String
}
if dbcfg.CaCert.Valid {
cfg.CACert = dbcfg.CaCert.String
}
return cfg
}
type dbChannel struct {
ID string `db:"mainflux_channel"`
Name sql.NullString `db:"name"`
Owner sql.NullString `db:"owner"`
Metadata string `db:"metadata"`
}
func toDBChannel(owner string, ch bootstrap.Channel) (dbChannel, error) {
dbch := dbChannel{
ID: ch.ID,
Name: nullString(ch.Name),
Owner: nullString(owner),
}
metadata, err := json.Marshal(ch.Metadata)
if err != nil {
return dbChannel{}, err
}
dbch.Metadata = string(metadata)
return dbch, nil
}
func toChannel(dbch dbChannel) (bootstrap.Channel, error) {
ch := bootstrap.Channel{
ID: dbch.ID,
}
if dbch.Name.Valid {
ch.Name = dbch.Name.String
}
if err := json.Unmarshal([]byte(dbch.Metadata), &ch.Metadata); err != nil {
return bootstrap.Channel{}, err
}
return ch, nil
}
type dbConnection struct {
Config string `db:"config_id"`
Channel string `db:"channel_id"`
ConfigOwner string `db:"config_owner"`
ChannelOwner string `db:"channel_owner"`
}
+174 -110
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package postgres_test
@@ -12,9 +8,9 @@ import (
"strconv"
"testing"
"github.com/gofrs/uuid"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/postgres"
uuid "github.com/satori/go.uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -29,8 +25,8 @@ var (
ExternalKey: "external-key",
Owner: "user@email.com",
MFChannels: []bootstrap.Channel{
bootstrap.Channel{ID: "1", Name: "name 1", Metadata: "{\"meta\":1}"},
bootstrap.Channel{ID: "2", Name: "name 2", Metadata: "{\"meta\":2}"},
bootstrap.Channel{ID: "1", Name: "name 1", Metadata: map[string]interface{}{"meta": 1.0}},
bootstrap.Channel{ID: "2", Name: "name 2", Metadata: map[string]interface{}{"meta": 2.0}},
},
Content: "content",
State: bootstrap.Inactive,
@@ -105,13 +101,18 @@ func TestRetrieveByID(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
id, err = repo.Save(c, channels)
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
id, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
nonexistentConfID, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
cases := []struct {
desc string
owner string
@@ -133,7 +134,7 @@ func TestRetrieveByID(t *testing.T) {
{
desc: "retrieve a non-existing config",
owner: c.Owner,
id: uuid.NewV4().String(),
id: nonexistentConfID.String(),
err: bootstrap.ErrNotFound,
},
{
@@ -157,11 +158,13 @@ func TestRetrieveAll(t *testing.T) {
for i := 0; i < numConfigs; i++ {
c := config
// Use UUID to prevent conflict errors.
id := uuid.NewV4().String()
c.ExternalID = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = uid.String()
c.Name = fmt.Sprintf("name %d", i)
c.MFThing = id
c.MFKey = id
c.MFThing = uid.String()
c.MFKey = uid.String()
if i%2 == 0 {
c.State = bootstrap.Active
@@ -171,7 +174,7 @@ func TestRetrieveAll(t *testing.T) {
c.MFChannels = nil
}
_, err := repo.Save(c, channels)
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
}
@@ -235,41 +238,33 @@ func TestRetrieveByExternalID(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
externalID string
externalKey string
err error
desc string
externalID string
err error
}{
{
desc: "retrieve with invalid external ID",
externalID: strconv.Itoa(numConfigs + 1),
externalKey: config.ExternalKey,
err: bootstrap.ErrNotFound,
desc: "retrieve with invalid external ID",
externalID: strconv.Itoa(numConfigs + 1),
err: bootstrap.ErrNotFound,
},
{
desc: "retrieve with invalid external key",
externalID: c.ExternalID,
externalKey: "invalid",
err: bootstrap.ErrNotFound,
},
{
desc: "retrieve with external key",
externalID: c.ExternalID,
externalKey: c.ExternalKey,
err: nil,
desc: "retrieve with external key",
externalID: c.ExternalID,
err: nil,
},
}
for _, tc := range cases {
_, err := repo.RetrieveByExternalID(tc.externalKey, tc.externalID)
_, err := repo.RetrieveByExternalID(tc.externalID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -281,11 +276,12 @@ func TestUpdate(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
@@ -318,6 +314,62 @@ func TestUpdate(t *testing.T) {
}
}
func TestUpdateCert(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
c.Content = "new content"
c.Name = "new name"
wrongOwner := c
wrongOwner.Owner = "3"
cases := []struct {
desc string
key string
owner string
cert string
certKey string
ca string
err error
}{
{
desc: "update with wrong owner",
key: "",
cert: "cert",
certKey: "certKey",
ca: "",
owner: "wrong",
err: bootstrap.ErrNotFound,
},
{
desc: "update a config",
key: c.MFKey,
cert: "cert",
certKey: "certKey",
ca: "ca",
owner: c.Owner,
err: nil,
},
}
for _, tc := range cases {
err := repo.UpdateCert(tc.owner, tc.key, tc.cert, tc.key, tc.ca)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateConnections(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
@@ -325,21 +377,23 @@ func TestUpdateConnections(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
// Use UUID to prevent conflicts.
id = uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err = uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
c.MFChannels = []bootstrap.Channel{}
_, err = repo.Save(c, []string{channels[0]})
c2, err := repo.Save(c, []string{channels[0]})
require.Nil(t, err, fmt.Sprintf("Saving a config expected to succeed: %s.\n", err))
cases := []struct {
@@ -366,6 +420,14 @@ func TestUpdateConnections(t *testing.T) {
connections: []string{channels[1]},
err: nil,
},
{
desc: "update connections with existing channels",
key: config.Owner,
id: c2,
channels: nil,
connections: channels,
err: nil,
},
{
desc: "update connections no channels",
key: config.Owner,
@@ -388,12 +450,13 @@ func TestRemove(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
id, err = repo.Save(c, channels)
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
id, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
// Removal works the same for both existing and non-existing
@@ -414,11 +477,12 @@ func TestChangeState(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
@@ -437,7 +501,7 @@ func TestChangeState(t *testing.T) {
},
{
desc: "change state with wrong id",
id: uuid.NewV4().String(),
id: "wrong",
owner: c.Owner,
err: bootstrap.ErrNotFound,
},
@@ -469,17 +533,17 @@ func TestListExisting(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
var chs []bootstrap.Channel
for _, ch := range config.MFChannels {
ch.Metadata = []byte(ch.Metadata.(string))
chs = append(chs, ch)
}
@@ -526,13 +590,13 @@ func TestSaveUnknown(t *testing.T) {
}{
{
desc: "save unknown",
externalID: uuid.NewV4().String(),
externalKey: uuid.NewV4().String(),
externalID: "unknown",
externalKey: "unknown",
err: nil,
},
{
desc: "save invalid unknown",
externalID: uuid.NewV4().String(),
externalID: "unknown",
externalKey: "",
err: nil,
},
@@ -547,8 +611,9 @@ func TestRetrieveUnknown(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
for i := 0; i < numConfigs; i++ {
id := uuid.NewV4().String()
repo.SaveUnknown(id, id)
id, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
repo.SaveUnknown(id.String(), id.String())
}
cases := []struct {
@@ -584,11 +649,12 @@ func TestRemoveThing(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
for i := 0; i < 2; i++ {
@@ -604,19 +670,20 @@ func TestUpdateChannel(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
id = c.MFChannels[0].ID
id := c.MFChannels[0].ID
update := bootstrap.Channel{
ID: id,
Name: "update name",
Metadata: []byte("{\"update\":\"metadata update\"}"),
Metadata: map[string]interface{}{"update": "metadata update"},
}
err = repo.UpdateChannel(update)
assert.Nil(t, err, fmt.Sprintf("updating config expected to succeed: %s.\n", err))
@@ -640,16 +707,12 @@ func TestRemoveChannel(t *testing.T) {
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
for i, ch := range c.MFChannels {
c.MFChannels[i].Metadata = []byte(ch.Metadata.(string))
}
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
@@ -668,11 +731,12 @@ func TestDisconnectThing(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// Package postgres contains repository implementations using PostgreSQL as
// the underlying database.
+9 -10
View File
@@ -1,16 +1,12 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package postgres
import (
"database/sql"
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // required for SQL access
migrate "github.com/rubenv/sql-migrate"
)
@@ -31,10 +27,10 @@ type Config struct {
// Connect creates a connection to the PostgreSQL instance and applies any
// unapplied database migrations. A non-nil error is returned to indicate
// failure.
func Connect(cfg Config) (*sql.DB, error) {
func Connect(cfg Config) (*sqlx.DB, error) {
url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
db, err := sql.Open("postgres", url)
db, err := sqlx.Open("postgres", url)
if err != nil {
return nil, err
}
@@ -46,7 +42,7 @@ func Connect(cfg Config) (*sql.DB, error) {
return db, nil
}
func migrateDB(db *sql.DB) error {
func migrateDB(db *sqlx.DB) error {
migrations := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
@@ -60,6 +56,9 @@ func migrateDB(db *sql.DB) error {
external_id TEXT UNIQUE NOT NULL,
external_key TEXT NOT NULL,
content TEXT,
client_cert TEXT,
client_key TEXT,
ca_cert TEXT,
state BIGINT NOT NULL,
PRIMARY KEY (mainflux_thing, owner)
)`,
@@ -95,7 +94,7 @@ func migrateDB(db *sql.DB) error {
},
}
_, err := migrate.Exec(db, "postgres", migrations, migrate.Up)
_, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
return err
}
+5 -9
View File
@@ -1,22 +1,18 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package postgres_test
import (
"database/sql"
"fmt"
"log"
"os"
"testing"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux/bootstrap/postgres"
"github.com/mainflux/mainflux/logger"
"gopkg.in/ory-am/dockertest.v3"
dockertest "gopkg.in/ory-am/dockertest.v3"
)
const (
@@ -26,7 +22,7 @@ const (
var (
testLog, _ = logger.New(os.Stdout, logger.Info.String())
db *sql.DB
db *sqlx.DB
)
func TestMain(m *testing.M) {
@@ -49,7 +45,7 @@ func TestMain(m *testing.M) {
if err := pool.Retry(func() error {
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
db, err = sql.Open("postgres", url)
db, err = sqlx.Open("postgres", url)
if err != nil {
return err
}
+41 -17
View File
@@ -1,17 +1,15 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
import (
"errors"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"io"
"net/http"
"github.com/mainflux/mainflux"
)
// bootstrapRes represent Mainflux Response to the Bootatrap request.
@@ -21,7 +19,10 @@ type bootstrapRes struct {
MFThing string `json:"mainflux_id"`
MFKey string `json:"mainflux_key"`
MFChannels []channelRes `json:"mainflux_channels"`
Content string `json:"content"`
Content string `json:"content,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
ClientKey string `json:"client_key,omitempty"`
CACert string `json:"ca_cert,omitempty"`
}
type channelRes struct {
@@ -42,19 +43,17 @@ func (res bootstrapRes) Empty() bool {
return false
}
type reader struct{}
type reader struct {
encKey []byte
}
// NewConfigReader return new reader which is used to generate response
// from the config.
func NewConfigReader() ConfigReader {
return reader{}
func NewConfigReader(encKey []byte) ConfigReader {
return reader{encKey: encKey}
}
func (r reader) ReadConfig(cfg Config) (mainflux.Response, error) {
if len(cfg.MFChannels) < 1 {
return bootstrapRes{}, errors.New("Invalid configuration")
}
func (r reader) ReadConfig(cfg Config, secure bool) (interface{}, error) {
var channels []channelRes
for _, ch := range cfg.MFChannels {
channels = append(channels, channelRes{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
@@ -65,7 +64,32 @@ func (r reader) ReadConfig(cfg Config) (mainflux.Response, error) {
MFThing: cfg.MFThing,
MFChannels: channels,
Content: cfg.Content,
ClientCert: cfg.ClientCert,
ClientKey: cfg.ClientKey,
CACert: cfg.CACert,
}
if secure {
b, err := json.Marshal(res)
if err != nil {
return nil, err
}
return r.encrypt(b)
}
return res, nil
}
func (r reader) encrypt(in []byte) ([]byte, error) {
block, err := aes.NewCipher(r.encKey)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(in))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], in)
return ciphertext, nil
}
+126
View File
@@ -0,0 +1,126 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package bootstrap_test
import (
"crypto/aes"
"crypto/cipher"
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type readChan struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
}
type readResp struct {
MFThing string `json:"mainflux_id"`
MFKey string `json:"mainflux_key"`
MFChannels []readChan `json:"mainflux_channels"`
Content string `json:"content,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
ClientKey string `json:"client_key,omitempty"`
CACert string `json:"ca_cert,omitempty"`
}
func dec(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
if len(in) < aes.BlockSize {
return nil, bootstrap.ErrMalformedEntity
}
iv := in[:aes.BlockSize]
in = in[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(in, in)
return in, nil
}
func TestReadConfig(t *testing.T) {
cfg := bootstrap.Config{
MFThing: "mf_id",
ClientCert: "client_cert",
ClientKey: "client_key",
CACert: "ca_cert",
MFKey: "mf_key",
MFChannels: []bootstrap.Channel{
bootstrap.Channel{
ID: "mf_id",
Name: "mf_name",
Metadata: map[string]interface{}{"key": "value}"},
},
},
Content: "content",
}
ret := readResp{
MFThing: "mf_id",
MFKey: "mf_key",
MFChannels: []readChan{
{
ID: "mf_id",
Name: "mf_name",
Metadata: map[string]interface{}{"key": "value}"},
},
},
Content: "content",
ClientCert: "client_cert",
ClientKey: "client_key",
CACert: "ca_cert",
}
bin, err := json.Marshal(ret)
require.Nil(t, err, fmt.Sprintf("Marshalling expected to succeed: %s.\n", err))
reader := bootstrap.NewConfigReader(encKey)
cases := []struct {
desc string
config bootstrap.Config
enc []byte
secret bool
err error
}{
{
desc: "read a config",
config: cfg,
enc: bin,
secret: false,
},
{
desc: "read encrypted config",
config: cfg,
enc: bin,
secret: true,
},
}
for _, tc := range cases {
res, err := reader.ReadConfig(tc.config, tc.secret)
require.Nil(t, err, fmt.Sprintf("Reading config to succeed: %s.\n", err))
if tc.secret {
d, err := dec(res.([]byte))
require.Nil(t, err, fmt.Sprintf("Decrypting expected to succeed: %s.\n", err))
assert.Equal(t, tc.enc, d, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.enc, d))
continue
}
b, err := json.Marshal(res)
require.Nil(t, err, fmt.Sprintf("Marshalling expected to succeed: %s.\n", err))
assert.Equal(t, tc.enc, b, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.enc, b))
resp, ok := res.(mainflux.Response)
require.True(t, ok, fmt.Sprintf("If not encrypted, reader should return response."))
assert.False(t, resp.Empty(), fmt.Sprintf("Response should not be empty %s.", err))
assert.Equal(t, http.StatusOK, resp.Code(), fmt.Sprintf("Default config response code should be 200."))
}
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package consumer contains events consumer for events
// published by Things service.
package consumer
@@ -1,4 +1,7 @@
package redis
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package consumer
type removeEvent struct {
id string
@@ -7,7 +10,7 @@ type removeEvent struct {
type updateChannelEvent struct {
id string
name string
metadata string
metadata map[string]interface{}
}
// Connection event is either connect or disconnect event.
@@ -1,6 +1,10 @@
package redis
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"encoding/json"
"fmt"
"github.com/go-redis/redis"
@@ -23,8 +27,8 @@ const (
exists = "BUSYGROUP Consumer Group name already exists"
)
// EventStore represents event source for things and channels provisioning.
type EventStore interface {
// Subscriber represents event source for things and channels provisioning.
type Subscriber interface {
// Subscribes to given subject and receives events.
Subscribe(string) error
}
@@ -37,7 +41,7 @@ type eventStore struct {
}
// NewEventStore returns new event store instance.
func NewEventStore(svc bootstrap.Service, client *redis.Client, consumer string, log logger.Logger) EventStore {
func NewEventStore(svc bootstrap.Service, client *redis.Client, consumer string, log logger.Logger) Subscriber {
return eventStore{
svc: svc,
client: client,
@@ -97,10 +101,16 @@ func decodeRemoveThing(event map[string]interface{}) removeEvent {
}
func decodeUpdateChannel(event map[string]interface{}) updateChannelEvent {
strmeta := read(event, "metadata", "{}")
var metadata map[string]interface{}
if err := json.Unmarshal([]byte(strmeta), metadata); err != nil {
metadata = map[string]interface{}{}
}
return updateChannelEvent{
id: read(event, "id", ""),
name: read(event, "name", ""),
metadata: read(event, "metadata", ""),
metadata: metadata,
}
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package producer contains the domain events needed to support
// event sourcing of Bootstrap service actions.
package producer
+134
View File
@@ -0,0 +1,134 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package producer
import (
"strings"
"time"
"github.com/mainflux/mainflux/bootstrap"
)
const (
configPrefix = "config."
configCreate = configPrefix + "create"
configUpdate = configPrefix + "update"
configRemove = configPrefix + "remove"
thingPrefix = "thing."
thingBootstrap = thingPrefix + "bootstrap"
thingStateChange = thingPrefix + "state_change"
thingUpdateConnections = thingPrefix + "update_connections"
)
type event interface {
encode() map[string]interface{}
}
var (
_ event = (*createConfigEvent)(nil)
_ event = (*updateConfigEvent)(nil)
_ event = (*removeConfigEvent)(nil)
_ event = (*bootstrapEvent)(nil)
_ event = (*changeStateEvent)(nil)
_ event = (*updateConnectionsEvent)(nil)
)
type createConfigEvent struct {
mfThing string
owner string
name string
mfChannels []string
externalID string
content string
timestamp time.Time
}
func (cce createConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": cce.mfThing,
"owner": cce.owner,
"name": cce.name,
"channels": strings.Join(cce.mfChannels, ", "),
"external_id": cce.externalID,
"content": cce.content,
"timestamp": cce.timestamp.Unix(),
"operation": configCreate,
}
}
type updateConfigEvent struct {
mfThing string
name string
content string
timestamp time.Time
}
func (uce updateConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": uce.mfThing,
"name": uce.name,
"content": uce.content,
"timestamp": uce.timestamp.Unix(),
"operation": configUpdate,
}
}
type removeConfigEvent struct {
mfThing string
timestamp time.Time
}
func (rce removeConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": rce.mfThing,
"timestamp": rce.timestamp.Unix(),
"operation": configRemove,
}
}
type bootstrapEvent struct {
externalID string
success bool
timestamp time.Time
}
func (be bootstrapEvent) encode() map[string]interface{} {
return map[string]interface{}{
"external_id": be.externalID,
"success": be.success,
"timestamp": be.timestamp.Unix(),
"operation": thingBootstrap,
}
}
type changeStateEvent struct {
mfThing string
state bootstrap.State
timestamp time.Time
}
func (cse changeStateEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": cse.mfThing,
"state": cse.state.String(),
"timestamp": cse.timestamp.Unix(),
"operation": thingStateChange,
}
}
type updateConnectionsEvent struct {
mfThing string
mfChannels []string
timestamp time.Time
}
func (uce updateConnectionsEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": uce.mfThing,
"channels": strings.Join(uce.mfChannels, ", "),
"timestamp": uce.timestamp.Unix(),
"operation": thingUpdateConnections,
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package producer_test
import (
"fmt"
"log"
"os"
"testing"
"github.com/go-redis/redis"
dockertest "gopkg.in/ory-am/dockertest.v3"
)
const (
wrongID = 0
wrongValue = "wrong-value"
)
var redisClient *redis.Client
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
container, err := pool.Run("redis", "5.0-alpine", nil)
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
if err := pool.Retry(func() error {
redisClient = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("localhost:%s", container.GetPort("6379/tcp")),
Password: "",
DB: 0,
})
return redisClient.Ping().Err()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
code := m.Run()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
os.Exit(code)
}
+178
View File
@@ -0,0 +1,178 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package producer
import (
"time"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux/bootstrap"
)
const (
streamID = "mainflux.bootstrap"
streamLen = 1000
)
var _ bootstrap.Service = (*eventStore)(nil)
type eventStore struct {
svc bootstrap.Service
client *redis.Client
}
// NewEventStoreMiddleware returns wrapper around bootstrap service that sends
// events to event store.
func NewEventStoreMiddleware(svc bootstrap.Service, client *redis.Client) bootstrap.Service {
return eventStore{
svc: svc,
client: client,
}
}
func (es eventStore) Add(key string, cfg bootstrap.Config) (bootstrap.Config, error) {
saved, err := es.svc.Add(key, cfg)
if err != nil {
return saved, err
}
var channels []string
for _, ch := range saved.MFChannels {
channels = append(channels, ch.ID)
}
ev := createConfigEvent{
mfThing: saved.MFThing,
owner: saved.Owner,
name: saved.Name,
mfChannels: channels,
externalID: saved.ExternalID,
content: saved.Content,
timestamp: time.Now(),
}
es.add(ev)
return saved, err
}
func (es eventStore) View(key, id string) (bootstrap.Config, error) {
return es.svc.View(key, id)
}
func (es eventStore) Update(key string, cfg bootstrap.Config) error {
if err := es.svc.Update(key, cfg); err != nil {
return err
}
ev := updateConfigEvent{
mfThing: cfg.MFThing,
name: cfg.Name,
content: cfg.Content,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) UpdateCert(key, thingKey, clientCert, clientKey, caCert string) error {
return es.svc.UpdateCert(key, thingKey, clientCert, clientKey, caCert)
}
func (es eventStore) UpdateConnections(key, id string, connections []string) error {
if err := es.svc.UpdateConnections(key, id, connections); err != nil {
return err
}
ev := updateConnectionsEvent{
mfThing: id,
mfChannels: connections,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) List(key string, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
return es.svc.List(key, filter, offset, limit)
}
func (es eventStore) Remove(key, id string) error {
if err := es.svc.Remove(key, id); err != nil {
return err
}
ev := removeConfigEvent{
mfThing: id,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) Bootstrap(externalKey, externalID string, secure bool) (bootstrap.Config, error) {
cfg, err := es.svc.Bootstrap(externalKey, externalID, secure)
ev := bootstrapEvent{
externalID: externalID,
timestamp: time.Now(),
success: true,
}
if err != nil {
ev.success = false
}
es.add(ev)
return cfg, err
}
func (es eventStore) ChangeState(key, id string, state bootstrap.State) error {
if err := es.svc.ChangeState(key, id, state); err != nil {
return err
}
ev := changeStateEvent{
mfThing: id,
state: state,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) RemoveConfigHandler(id string) error {
return es.svc.RemoveConfigHandler(id)
}
func (es eventStore) RemoveChannelHandler(id string) error {
return es.svc.RemoveChannelHandler(id)
}
func (es eventStore) UpdateChannelHandler(channel bootstrap.Channel) error {
return es.UpdateChannelHandler(channel)
}
func (es eventStore) DisconnectThingHandler(channelID, thingID string) error {
return es.svc.DisconnectThingHandler(channelID, thingID)
}
func (es eventStore) add(ev event) error {
record := &redis.XAddArgs{
Stream: streamID,
MaxLenApprox: streamLen,
Values: ev.encode(),
}
return es.client.XAdd(record).Err()
}
+548
View File
@@ -0,0 +1,548 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package producer_test
import (
"fmt"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/mocks"
"github.com/mainflux/mainflux/bootstrap/redis/producer"
mfsdk "github.com/mainflux/mainflux/sdk/go"
"github.com/mainflux/mainflux/things"
httpapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
streamID = "mainflux.bootstrap"
email = "user@example.com"
validToken = "validToken"
unknownID = "1"
unknownKey = "2"
channelsNum = 3
defaultTimout = 5
configPrefix = "config."
configCreate = configPrefix + "create"
configUpdate = configPrefix + "update"
configRemove = configPrefix + "remove"
thingPrefix = "thing."
thingStateChange = thingPrefix + "state_change"
thingBootstrap = thingPrefix + "bootstrap"
thingUpdateConnections = thingPrefix + "update_connections"
)
var (
encKey = []byte("1234567891011121")
channel = bootstrap.Channel{
ID: "1",
Name: "name",
Metadata: map[string]interface{}{"name": "value"},
}
config = bootstrap.Config{
ExternalID: "external_id",
ExternalKey: "external_key",
MFChannels: []bootstrap.Channel{channel},
Content: "config",
}
)
func newService(auth mainflux.AuthNServiceClient, url string) bootstrap.Service {
configs := mocks.NewConfigsRepository(map[string]string{unknownID: unknownKey})
config := mfsdk.Config{
BaseURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(auth, configs, sdk, encKey)
}
func newThingsService(auth mainflux.AuthNServiceClient) things.Service {
channels := make(map[string]things.Channel, channelsNum)
for i := 0; i < channelsNum; i++ {
id := strconv.Itoa(i + 1)
channels[id] = things.Channel{
ID: id,
Owner: email,
Metadata: map[string]interface{}{"meta": "data"},
}
}
return mocks.NewThingsService(map[string]things.Thing{}, channels, auth)
}
func newThingsServer(svc things.Service) *httptest.Server {
mux := httpapi.MakeHandler(mocktracer.New(), svc)
return httptest.NewServer(mux)
}
func TestAdd(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
var channels []string
for _, ch := range config.MFChannels {
channels = append(channels, ch.ID)
}
invalidConfig := config
invalidConfig.MFChannels = []bootstrap.Channel{bootstrap.Channel{ID: "empty"}}
cases := []struct {
desc string
config bootstrap.Config
key string
err error
event map[string]interface{}
}{
{
desc: "create config successfully",
config: config,
key: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": "1",
"owner": email,
"name": config.Name,
"channels": strings.Join(channels, ", "),
"external_id": config.ExternalID,
"content": config.Content,
"timestamp": time.Now().Unix(),
"operation": configCreate,
},
},
{
desc: "create invalid config",
config: invalidConfig,
key: validToken,
err: bootstrap.ErrMalformedEntity,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
_, err := svc.Add(tc.key, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestView(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
svcConfig, svcErr := svc.View(validToken, saved.MFThing)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
esConfig, esErr := svc.View(validToken, saved.MFThing)
assert.Equal(t, svcConfig, esConfig, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcConfig, esConfig))
assert.Equal(t, svcErr, esErr, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcErr, esErr))
}
func TestUpdate(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
ch := channel
ch.ID = "2"
c.MFChannels = append(c.MFChannels, ch)
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
modified := saved
modified.Content = "new-config"
modified.Name = "new name"
nonExisting := config
nonExisting.MFThing = "unknown"
cases := []struct {
desc string
config bootstrap.Config
key string
err error
event map[string]interface{}
}{
{
desc: "update config successfully",
config: modified,
key: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": modified.MFThing,
"name": modified.Name,
"content": modified.Content,
"timestamp": time.Now().Unix(),
"operation": configUpdate,
},
},
{
desc: "update non-existing config",
config: nonExisting,
key: validToken,
err: bootstrap.ErrNotFound,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.Update(tc.key, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestUpdateConnections(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
key string
connections []string
err error
event map[string]interface{}
}{
{
desc: "update connections successfully",
id: saved.MFThing,
key: validToken,
connections: []string{"2"},
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"channels": "2",
"timestamp": time.Now().Unix(),
"operation": thingUpdateConnections,
},
},
{
desc: "update connections unsuccessfully",
id: saved.MFThing,
key: validToken,
connections: []string{"256"},
err: bootstrap.ErrMalformedEntity,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.UpdateConnections(tc.key, tc.id, tc.connections)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestList(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
_, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
offset := uint64(0)
limit := uint64(10)
svcConfigs, svcErr := svc.List(validToken, bootstrap.Filter{}, offset, limit)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
esConfigs, esErr := svc.List(validToken, bootstrap.Filter{}, offset, limit)
assert.Equal(t, svcConfigs, esConfigs, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcConfigs, esConfigs))
assert.Equal(t, svcErr, esErr, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcErr, esErr))
}
func TestRemove(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
key string
err error
event map[string]interface{}
}{
{
desc: "remove config successfully",
id: saved.MFThing,
key: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"timestamp": time.Now().Unix(),
"operation": configRemove,
},
},
{
desc: "remove config with invalid credentials",
id: saved.MFThing,
key: "",
err: bootstrap.ErrUnauthorizedAccess,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.Remove(tc.key, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestBootstrap(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
externalID string
externalKey string
err error
event map[string]interface{}
}{
{
desc: "bootstrap successfully",
externalID: saved.ExternalID,
externalKey: saved.ExternalKey,
err: nil,
event: map[string]interface{}{
"external_id": saved.ExternalID,
"success": "1",
"timestamp": time.Now().Unix(),
"operation": thingBootstrap,
},
},
{
desc: "bootstrap with an error",
externalID: saved.ExternalID,
externalKey: "external",
err: bootstrap.ErrNotFound,
event: map[string]interface{}{
"external_id": saved.ExternalID,
"success": "0",
"timestamp": time.Now().Unix(),
"operation": thingBootstrap,
},
},
}
lastID := "0"
for _, tc := range cases {
_, err := svc.Bootstrap(tc.externalKey, tc.externalID, false)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestChangeState(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
key string
state bootstrap.State
err error
event map[string]interface{}
}{
{
desc: "change state to active",
id: saved.MFThing,
key: validToken,
state: bootstrap.Active,
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"state": bootstrap.Active.String(),
"timestamp": time.Now().Unix(),
"operation": thingStateChange,
},
},
{
desc: "change state invalid credentials",
id: saved.MFThing,
key: "",
state: bootstrap.Inactive,
err: bootstrap.ErrUnauthorizedAccess,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.ChangeState(tc.key, tc.id, tc.state)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func test(t *testing.T, expected, actual map[string]interface{}, description string) {
if expected != nil && actual != nil {
ts1 := expected["timestamp"].(int64)
ts2, err := strconv.ParseInt(actual["timestamp"].(string), 10, 64)
require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid timestamp, got %s", description, err))
val := ts1 == ts2 || ts2 <= ts1+defaultTimout
assert.True(t, val, fmt.Sprintf("%s: timestamp is not in valid range", description))
delete(expected, "timestamp")
delete(actual, "timestamp")
assert.Equal(t, expected, actual, fmt.Sprintf("%s: expected %v got %v\n", description, expected, actual))
}
}
+100 -92
View File
@@ -1,15 +1,13 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
import (
"context"
"encoding/json"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"errors"
"time"
@@ -17,11 +15,6 @@ import (
mfsdk "github.com/mainflux/mainflux/sdk/go"
)
const (
thingType = "device"
chanName = "channel"
)
var (
// ErrNotFound indicates a non-existent entity request.
ErrNotFound = errors.New("non-existent entity")
@@ -55,6 +48,10 @@ type Service interface {
// Update updates editable fields of the provided Config.
Update(string, Config) error
// UpdateCert updates an existing Config certificate and key.
// A non-nil error is returned to indicate operation failure.
UpdateCert(string, string, string, string, string) error
// UpdateConnections updates list of Channels related to given Config.
UpdateConnections(string, string, []string) error
@@ -66,7 +63,7 @@ type Service interface {
Remove(string, string) error
// Bootstrap returns Config to the Thing with provided external ID using external key.
Bootstrap(string, string) (Config, error)
Bootstrap(string, string, bool) (Config, error)
// ChangeState changes state of the Thing with given ID and owner.
ChangeState(string, string, State) error
@@ -92,21 +89,24 @@ type Service interface {
// is to provide convenient way to generate custom configuration response
// based on the specific Config which will be consumed by the client.
type ConfigReader interface {
ReadConfig(Config) (mainflux.Response, error)
ReadConfig(Config, bool) (interface{}, error)
}
type bootstrapService struct {
users mainflux.UsersServiceClient
auth mainflux.AuthNServiceClient
configs ConfigRepository
sdk mfsdk.SDK
encKey []byte
reader ConfigReader
}
// New returns new Bootstrap service.
func New(users mainflux.UsersServiceClient, configs ConfigRepository, sdk mfsdk.SDK) Service {
func New(auth mainflux.AuthNServiceClient, configs ConfigRepository, sdk mfsdk.SDK, encKey []byte) Service {
return &bootstrapService{
configs: configs,
sdk: sdk,
users: users,
auth: auth,
encKey: encKey,
}
}
@@ -118,7 +118,7 @@ func (bs bootstrapService) Add(key string, cfg Config) (Config, error) {
toConnect := bs.toIDList(cfg.MFChannels)
// Check if channels exist. This is the way to prevent invalid configuration to be saved.
// Check if channels exist. This is the way to prevent fetching channels that already exist.
existing, err := bs.configs.ListExisting(owner, toConnect)
if err != nil {
return Config{}, err
@@ -162,20 +162,7 @@ func (bs bootstrapService) View(key, id string) (Config, error) {
return Config{}, err
}
cfg, err := bs.configs.RetrieveByID(owner, id)
if err != nil {
return Config{}, err
}
for i, ch := range cfg.MFChannels {
if meta, ok := ch.Metadata.([]byte); ok {
if err := json.Unmarshal(meta, &cfg.MFChannels[i].Metadata); err != nil {
return Config{}, err
}
}
}
return cfg, nil
return bs.configs.RetrieveByID(owner, id)
}
func (bs bootstrapService) Update(key string, cfg Config) error {
@@ -189,6 +176,14 @@ func (bs bootstrapService) Update(key string, cfg Config) error {
return bs.configs.Update(cfg)
}
func (bs bootstrapService) UpdateCert(key, thingID, clientCert, clientKey, caCert string) error {
owner, err := bs.identify(key)
if err != nil {
return err
}
return bs.configs.UpdateCert(owner, thingID, clientCert, clientKey, caCert)
}
func (bs bootstrapService) UpdateConnections(key, id string, connections []string) error {
owner, err := bs.identify(key)
if err != nil {
@@ -201,7 +196,14 @@ func (bs bootstrapService) UpdateConnections(key, id string, connections []strin
}
add, remove := bs.updateList(cfg, connections)
channels, err := bs.updateChannels(add, key)
// Check if channels exist. This is the way to prevent fetching channels that already exist.
existing, err := bs.configs.ListExisting(owner, connections)
if err != nil {
return err
}
channels, err := bs.connectionChannels(connections, bs.toIDList(existing), key)
if err != nil {
return err
}
@@ -257,21 +259,26 @@ func (bs bootstrapService) Remove(key, id string) error {
return bs.configs.Remove(owner, id)
}
func (bs bootstrapService) Bootstrap(externalKey, externalID string) (Config, error) {
cfg, err := bs.configs.RetrieveByExternalID(externalKey, externalID)
func (bs bootstrapService) Bootstrap(externalKey, externalID string, secure bool) (Config, error) {
cfg, err := bs.configs.RetrieveByExternalID(externalID)
if err != nil {
if err == ErrNotFound {
bs.configs.SaveUnknown(externalKey, externalID)
return Config{}, ErrNotFound
}
return Config{}, ErrNotFound
return cfg, err
}
for i, ch := range cfg.MFChannels {
if meta, ok := ch.Metadata.([]byte); ok {
if err := json.Unmarshal(meta, &cfg.MFChannels[i].Metadata); err != nil {
return Config{}, err
}
if secure {
dec, err := bs.dec(externalKey)
if err != nil {
return Config{}, err
}
externalKey = dec
}
if cfg.ExternalKey != externalKey {
return Config{}, ErrNotFound
}
return cfg, nil
@@ -283,25 +290,25 @@ func (bs bootstrapService) ChangeState(key, id string, state State) error {
return err
}
thing, err := bs.configs.RetrieveByID(owner, id)
cfg, err := bs.configs.RetrieveByID(owner, id)
if err != nil {
return err
}
if thing.State == state {
if cfg.State == state {
return nil
}
switch state {
case Active:
for _, c := range thing.MFChannels {
if err := bs.sdk.ConnectThing(thing.MFThing, c.ID, key); err != nil {
for _, c := range cfg.MFChannels {
if err := bs.sdk.ConnectThing(cfg.MFThing, c.ID, key); err != nil {
return ErrThings
}
}
case Inactive:
for _, c := range thing.MFChannels {
if err := bs.sdk.DisconnectThing(thing.MFThing, c.ID, key); err != nil {
for _, c := range cfg.MFChannels {
if err := bs.sdk.DisconnectThing(cfg.MFThing, c.ID, key); err != nil {
if err == mfsdk.ErrNotFound {
continue
}
@@ -333,7 +340,7 @@ func (bs bootstrapService) identify(token string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := bs.users.Identify(ctx, &mainflux.Token{Value: token})
res, err := bs.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return "", ErrUnauthorizedAccess
}
@@ -347,7 +354,7 @@ func (bs bootstrapService) thing(key, id string) (mfsdk.Thing, error) {
var err error
if id == "" {
thingID, err = bs.sdk.CreateThing(mfsdk.Thing{Type: thingType}, key)
thingID, err = bs.sdk.CreateThing(mfsdk.Thing{}, key)
if err != nil {
return mfsdk.Thing{}, err
}
@@ -369,6 +376,35 @@ func (bs bootstrapService) thing(key, id string) (mfsdk.Thing, error) {
return thing, nil
}
func (bs bootstrapService) connectionChannels(channels, existing []string, key string) ([]Channel, error) {
add := make(map[string]bool, len(channels))
for _, ch := range channels {
add[ch] = true
}
for _, ch := range existing {
if add[ch] == true {
delete(add, ch)
}
}
var ret []Channel
for id := range add {
ch, err := bs.sdk.Channel(id, key)
if err != nil {
return nil, ErrMalformedEntity
}
ret = append(ret, Channel{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
return ret, nil
}
// Method updateList accepts config and channel IDs and returns three lists:
// 1) IDs of Channels to be added
// 2) IDs of Channels to be removed
@@ -406,49 +442,21 @@ func (bs bootstrapService) toIDList(channels []Channel) []string {
return ret
}
func (bs bootstrapService) updateChannels(add []string, key string) ([]Channel, error) {
var ret []Channel
for _, id := range add {
ch, err := bs.sdk.Channel(id, key)
if err != nil {
return []Channel{}, ErrMalformedEntity
}
ret = append(ret, Channel{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
func (bs bootstrapService) dec(in string) (string, error) {
ciphertext, err := hex.DecodeString(in)
if err != nil {
return "", ErrNotFound
}
return ret, nil
}
func (bs bootstrapService) connectionChannels(channels, existing []string, key string) ([]Channel, error) {
add := make(map[string]bool, len(channels))
for _, ch := range channels {
add[ch] = true
}
for _, ch := range existing {
if add[ch] == true {
delete(add, ch)
}
}
var ret []Channel
for id := range add {
ch, err := bs.sdk.Channel(id, key)
if err != nil {
return nil, ErrMalformedEntity
}
ret = append(ret, Channel{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
return ret, nil
block, err := aes.NewCipher(bs.encKey)
if err != nil {
return "", err
}
if len(ciphertext) < aes.BlockSize {
return "", ErrMalformedEntity
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext, ciphertext)
return string(ciphertext), nil
}
+119 -23
View File
@@ -1,25 +1,28 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap_test
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net/http/httptest"
"strconv"
"testing"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/gofrs/uuid"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/mocks"
mfsdk "github.com/mainflux/mainflux/sdk/go"
"github.com/mainflux/mainflux/things"
httpapi "github.com/mainflux/mainflux/things/api/http"
uuid "github.com/satori/go.uuid"
httpapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -35,10 +38,12 @@ const (
)
var (
encKey = []byte("1234567891011121")
channel = bootstrap.Channel{
ID: "1",
Name: "name",
Metadata: `{"name":"value"}`,
Metadata: map[string]interface{}{"name": "value"},
}
config = bootstrap.Config{
@@ -49,35 +54,50 @@ var (
}
)
func newService(users mainflux.UsersServiceClient, url string) bootstrap.Service {
func newService(auth mainflux.AuthNServiceClient, url string) bootstrap.Service {
things := mocks.NewConfigsRepository(map[string]string{unknownID: unknownKey})
config := mfsdk.Config{
BaseURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(users, things, sdk)
return bootstrap.New(auth, things, sdk, encKey)
}
func newThingsService(users mainflux.UsersServiceClient) things.Service {
func newThingsService(auth mainflux.AuthNServiceClient) things.Service {
channels := make(map[string]things.Channel, channelsNum)
for i := 0; i < channelsNum; i++ {
id := strconv.Itoa(i + 1)
channels[id] = things.Channel{
ID: id,
Owner: email,
Metadata: `{"meta":"data"}`,
Metadata: map[string]interface{}{"meta": "data"},
}
}
return mocks.NewThingsService(map[string]things.Thing{}, channels, users)
return mocks.NewThingsService(map[string]things.Thing{}, channels, auth)
}
func newThingsServer(svc things.Service) *httptest.Server {
mux := httpapi.MakeHandler(svc)
mux := httpapi.MakeHandler(mocktracer.New(), svc)
return httptest.NewServer(mux)
}
func enc(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(in))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], in)
return ciphertext, nil
}
func TestAdd(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
@@ -223,6 +243,64 @@ func TestUpdate(t *testing.T) {
}
}
func TestUpdateCert(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
c := config
ch := channel
ch.ID = "2"
c.MFChannels = append(c.MFChannels, ch)
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
key string
thingKey string
clientCert string
clientKey string
caCert string
err error
}{
{
desc: "update certs for the valid config",
thingKey: saved.MFKey,
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
key: validToken,
err: nil,
},
{
desc: "update cert for a non-existing config",
thingKey: "empty",
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
key: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "update config cert with wrong credentials",
thingKey: saved.MFKey,
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
err := svc.UpdateCert(tc.key, tc.thingKey, tc.clientCert, tc.clientKey, tc.caCert)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateConnections(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
@@ -236,7 +314,9 @@ func TestUpdateConnections(t *testing.T) {
created, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
c.ExternalID = uuid.NewV4().String()
externalID, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = externalID.String()
active, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
err = svc.ChangeState(validToken, active.MFThing, bootstrap.Active)
@@ -253,7 +333,7 @@ func TestUpdateConnections(t *testing.T) {
err error
}{
{
desc: "update connections for config with state Created",
desc: "update connections for config with state Inactive",
key: validToken,
id: created.MFThing,
connections: []string{"2"},
@@ -305,9 +385,10 @@ func TestList(t *testing.T) {
var saved []bootstrap.Config
for i := 0; i < numThings; i++ {
c := config
id := uuid.NewV4().String()
c.ExternalID = id
c.ExternalKey = id
id, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = id.String()
c.ExternalKey = id.String()
c.Name = fmt.Sprintf("%s-%d", config.Name, i)
s, err := svc.Add(validToken, c)
saved = append(saved, s)
@@ -477,12 +558,16 @@ func TestBootstrap(t *testing.T) {
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
e, err := enc([]byte(saved.ExternalKey))
require.Nil(t, err, fmt.Sprintf("Encrypting external key expected to succeed: %s.\n", err))
cases := []struct {
desc string
config bootstrap.Config
externalKey string
externalID string
err error
encrypted bool
}{
{
desc: "bootstrap using invalid external id",
@@ -490,6 +575,7 @@ func TestBootstrap(t *testing.T) {
externalID: "invalid",
externalKey: saved.ExternalKey,
err: bootstrap.ErrNotFound,
encrypted: false,
},
{
desc: "bootstrap using invalid external key",
@@ -497,6 +583,7 @@ func TestBootstrap(t *testing.T) {
externalID: saved.ExternalID,
externalKey: "invalid",
err: bootstrap.ErrNotFound,
encrypted: false,
},
{
desc: "bootstrap an existing config",
@@ -504,11 +591,20 @@ func TestBootstrap(t *testing.T) {
externalID: saved.ExternalID,
externalKey: saved.ExternalKey,
err: nil,
encrypted: false,
},
{
desc: "bootstrap encrypted",
config: saved,
externalID: saved.ExternalID,
externalKey: hex.EncodeToString(e),
err: nil,
encrypted: true,
},
}
for _, tc := range cases {
config, err := svc.Bootstrap(tc.externalKey, tc.externalID)
config, err := svc.Bootstrap(tc.externalKey, tc.externalID, tc.encrypted)
assert.Equal(t, tc.config, config, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.config, config))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
@@ -584,7 +680,7 @@ func TestUpdateChannelHandler(t *testing.T) {
ch := bootstrap.Channel{
ID: channel.ID,
Name: "new name",
Metadata: "new meta",
Metadata: map[string]interface{}{"meta": "new"},
}
cases := []struct {
@@ -657,7 +753,7 @@ func TestRemoveCoinfigHandler(t *testing.T) {
err error
}{
{
desc: "remove an existing conifg",
desc: "remove an existing config",
id: saved.MFThing,
err: nil,
},
@@ -696,7 +792,7 @@ func TestDisconnectThingsHandler(t *testing.T) {
err: nil,
},
{
desc: "disconnect dicsonnected",
desc: "disconnect disconnected",
channelID: channel.ID,
thingID: saved.MFThing,
err: nil,
+3 -7
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
@@ -18,9 +14,9 @@ const (
// State represents corresponding Mainflux Thing state. The possible Config States
// as well as description of what that State represents are given in the table:
// | State | What it means |
// | State | What it means |
// |----------+--------------------------------------------------------------------------------|
// | Inactive | Thing is created, but isn't able to communicate over Mainflux |
// | Inactive | Thing is created, but isn't able to communicate over Mainflux |
// | Active | Thing is able to communicate using Mainflux |
type State int
+131 -49
View File
@@ -1,7 +1,7 @@
swagger: "2.0"
info:
title: Mainflux Bootstrap service
description: HTTP API for managing platform devices and applications configuration.
description: HTTP API for managing platform things configuration.
version: "1.0.0"
consumes:
- "application/json"
@@ -30,7 +30,7 @@ paths:
headers:
Location:
type: string
description: Created config's relative URL (i.e. /things/configs/{configId}).
description: Created configuration's relative URL (i.e. /things/configs/{configId}).
400:
description: Failed due to malformed JSON.
403:
@@ -56,7 +56,7 @@ paths:
- $ref: "#/parameters/Name"
responses:
200:
description: |
description: |
Data retrieved. Configs from this list don't contain channels.
schema:
$ref: "#/definitions/ConfigList"
@@ -74,7 +74,7 @@ paths:
tags:
- configs
parameters:
- $ref: "#/parameters/ConfigAuthorization"
- $ref: "#/parameters/ConfigAuth"
- $ref: "#/parameters/ExternalId"
responses:
200:
@@ -88,6 +88,30 @@ paths:
added to the service later.
500:
$ref: "#/responses/ServiceError"
/things/bootstrap/secure/{externalId}:
get:
summary: Retrieves configuration
description: |
Retrieves a configuration with given external ID and encrypted external key.
tags:
- configs
parameters:
- $ref: "#/parameters/EncConfigAuth"
- $ref: "#/parameters/ExternalId"
responses:
200:
description: |
Data retrieved. In this case, Bootstrap response is encrypted using
the secret key, so an actual response is in the binary format.
schema:
$ref: "#/definitions/BootstrapRes"
404:
description: |
Failed to retrieve corresponding config. Thing which attempted
to bootstrap is saved as an unknown Thing and can be listed and
added to the service later.
500:
$ref: "#/responses/ServiceError"
/things/configs/{configId}:
get:
summary: Retrieves config info (with channels)
@@ -140,10 +164,10 @@ paths:
delete:
summary: Removes a Config
description: |
Removes a Config. In case of successfull removal the service will ensure
that the removed config is disconnected from all of the Maifnlux channels.
Removes a Config. In case of successful removal the service will ensure
that the removed config is disconnected from all of the Mainflux channels.
tags:
- confgis
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
@@ -151,16 +175,46 @@ paths:
204:
description: Config removed.
400:
description: Failed due to malformed config's ID.
description: Failed due to malformed config ID.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
/things/configs/certs/{configId}:
patch:
summary: Updates certs
description: |
Update is performed by replacing the current certificate data with values
provided in a request payload.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
- name: config
description: JSON-formatted document describing the updated thing.
in: body
schema:
$ref: "#/definitions/ConfigUpdateCertReq"
required: true
responses:
200:
description: Config updated.
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
404:
description: Config does not exist.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
/things/configs/connections/{configId}:
put:
summary: Updates channels the thing is connected to
description: |
Update connections performs update of the channel list corresponding
Update connections performs update of the channel list corresponding
Thing is connected to.
tags:
- configs
@@ -171,9 +225,7 @@ paths:
description: Array if IDs the thing is be connected to.
in: body
schema:
type: array
items:
type: string
$ref: "#/definitions/ConfigUpdateConnReq"
required: true
responses:
200:
@@ -221,7 +273,7 @@ paths:
$ref: "#/responses/ServiceError"
/things/unknown/configs:
get:
summary: Get a list of unsucessfully bootstrapped Things
summary: Get a list of unsuccessfully bootstrapped Things
description: |
Retrieves a list of unknown configs. Due to performance concerns, data
is retrieved in subsets. The API configs must ensure that the entire
@@ -252,18 +304,26 @@ parameters:
in: header
type: string
required: true
ConfigAuthorization:
ConfigAuth:
name: configAuthorization
description: Configuration external key.
in: header
type: string
required: true
EncConfigAuth:
name: configAuthorization
description: |
Hex-encoded configuration external key encrypted using
the AES algorithm and SHA256 sum of the external key
itself as an encryption key.
in: header
type: string
required: true
ConfigId:
name: configId
description: Unique Config identifier.
description: Unique Config identifier. It's the ID of the corresponding Thing.
in: path
type: integer
minimum: 1
type: string
required: true
ExternalId:
name: externalId
@@ -306,7 +366,7 @@ parameters:
responses:
ServiceError:
description: Unexpected server-side error occured.
description: Unexpected server-side error occurred.
definitions:
ConfigList:
@@ -333,7 +393,7 @@ definitions:
items:
$ref: "#/definitions/ConfigRes"
required:
- confgis
- configs
State:
type: integer
enum:
@@ -342,7 +402,7 @@ definitions:
ConfigRes:
type: object
properties:
mainflux_thing:
mainflux_id:
type: string
description: Corresponding Mainflux Thing ID.
mainflux_key:
@@ -365,46 +425,56 @@ definitions:
description: Custom metadata related to the Channel.
external_id:
type: string
description: External ID (MAC address or some uinque identifier).
description: External ID (MAC address or some unique identifier).
external_key:
type: string
description: External key.
content:
type: string
description: Free-form custom configuration
description: Free-form custom configuration.
state:
$ref: '#/definitions/State'
required:
- external_id
- external_key
BootstrapRes:
type: object
properties:
mainflux_thing:
type: object
properties:
mainflux_id:
type: string
description: Corresponding Mainflux Thing ID.
mainflux_key:
type: string
description: Corresponding Mainflux Thing key.
mainflux_channels:
type: array
minItems: 0
items:
type: string
description: Corresponding Mainflux Thing ID.
mainflux_key:
type: string
description: Corresponding Mainflux Thing key.
mainflux_channels:
type: array
minItems: 0
items:
type: string
content:
type: string
description: Free-form custom configuration
required:
- mainflux_thing
- mainflux_key
- mainflux_channels
- content
content:
type: string
description: Free-form custom configuration.
client_cert:
type: string
description: Client certrificate.
client_key:
type: string
description: Key for the client_cert.
ca_cert:
type: string
description: Issuing CA certificate.
required:
- mainflux_id
- mainflux_key
- mainflux_channels
- content
ConfigReq:
type: object
properties:
external_id:
type: string
description: External ID (MAC address or some uinque identifier).
description: External ID (MAC address or some unique identifier).
external_key:
type: string
description: External key.
@@ -424,15 +494,27 @@ definitions:
ConfigUpdateReq:
type: object
properties:
channels:
type: array
minItems: 0
items:
type: string
content:
type: string
name:
type: string
required:
- content
- name
- name
ConfigUpdateConnReq:
type: object
properties:
channels:
type: array
minItems: 0
items:
type: string
ConfigUpdateCertReq:
type: object
properties:
client_cert:
type: string
client_key:
type: string
ca_cert:
type: string
+3 -3
View File
@@ -26,12 +26,12 @@ mainflux-cli users token john.doe@email.com password
### System Provisioning
#### Create Thing (type Device)
```
mainflux-cli things create '{"type":"device", "name":"myDevice"}' <user_auth_token>
mainflux-cli things create '{"name":"myDevice"}' <user_auth_token>
```
#### Create Thing (type Application)
```
mainflux-cli things create '{"type":"app", "name":"myDevice"}' <user_auth_token>
mainflux-cli things create '{"name":"myDevice"}' <user_auth_token>
```
#### Update Thing
@@ -104,5 +104,5 @@ mainflux-cli channels connections <channel_id> <user_auth_token>
### Messaging
#### Send a message over HTTP
```
mainflux-cli msg send <channel_id> '[{"bn":"Dev1","n":"temp","v":20}, {"n":"hum","v":40}, {"bn":"Dev2", "n":"temp","v":20}, {"n":"hum","v":40}]' <thing_auth_token>
mainflux-cli messages send <channel_id> '[{"bn":"Dev1","n":"temp","v":20}, {"n":"hum","v":40}, {"bn":"Dev2", "n":"temp","v":20}, {"n":"hum","v":40}]' <thing_auth_token>
```
+2 -6
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package cli
@@ -51,7 +47,7 @@ var cmdChannels = []cobra.Command{
}
if args[0] == "all" {
l, err := sdk.Channels(args[1], uint64(Offset), uint64(Limit))
l, err := sdk.Channels(args[1], uint64(Offset), uint64(Limit), Name)
if err != nil {
logError(err)
return
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package cli
+205 -112
View File
@@ -1,19 +1,16 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package cli
import (
"bufio"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"github.com/docker/docker/pkg/namesgenerator"
mfxsdk "github.com/mainflux/mainflux/sdk/go"
@@ -22,83 +19,35 @@ import (
var errMalformedCSV = errors.New("malformed CSV")
func createThing(name, kind, token string) (mfxsdk.Thing, error) {
id, err := sdk.CreateThing(mfxsdk.Thing{Name: name, Type: kind}, token)
if err != nil {
return mfxsdk.Thing{}, err
}
t, err := sdk.Thing(id, token)
if err != nil {
return mfxsdk.Thing{}, err
}
m := mfxsdk.Thing{
ID: id,
Name: name,
Type: kind,
Key: t.Key,
}
return m, nil
}
func createChannel(name, token string) (mfxsdk.Channel, error) {
id, err := sdk.CreateChannel(mfxsdk.Channel{Name: name}, token)
if err != nil {
return mfxsdk.Channel{}, nil
}
c := mfxsdk.Channel{
ID: id,
Name: name,
}
return c, nil
}
const jsonExt = ".json"
const csvExt = ".csv"
var cmdProvision = []cobra.Command{
cobra.Command{
Use: "things",
Short: "things <things_csv> <user_token>",
Long: `Provisions things`,
Short: "things <things_file> <user_token>",
Long: `Bulk create things`,
Run: func(cmd *cobra.Command, args []string) {
things := []mfxsdk.Thing{}
if len(args) != 2 {
logUsage(cmd.Short)
return
}
c, err := os.Open(args[0])
if _, err := os.Stat(args[0]); os.IsNotExist(err) {
logError(err)
return
}
things, err := thingsFromFile(args[0])
if err != nil {
logError(err)
return
}
reader := csv.NewReader(bufio.NewReader(c))
for {
l, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
logError(err)
return
}
if len(l) < 2 {
logError(errMalformedCSV)
return
}
m, err := createThing(l[0], l[1], args[1])
if err != nil {
logError(err)
return
}
things = append(things, m)
things, err = sdk.CreateThings(things, args[1])
if err != nil {
logError(err)
return
}
logJSON(things)
@@ -106,50 +55,52 @@ var cmdProvision = []cobra.Command{
},
cobra.Command{
Use: "channels",
Short: "channels <channels_csv> <user_token>",
Long: `Provisions channels`,
Short: "channels <channels_file> <user_token>",
Long: `Bulk create channels`,
Run: func(cmd *cobra.Command, args []string) {
channels := []mfxsdk.Channel{}
if len(args) != 2 {
logUsage(cmd.Short)
return
}
c, err := os.Open(args[0])
channels, err := channelsFromFile(args[0])
if err != nil {
logError(err)
return
}
reader := csv.NewReader(bufio.NewReader(c))
for {
l, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
logError(err)
return
}
if len(l) < 1 {
logError(errMalformedCSV)
return
}
c, err := createChannel(l[0], args[1])
if err != nil {
logError(err)
return
}
channels = append(channels, c)
channels, err = sdk.CreateChannels(channels, args[1])
if err != nil {
logError(err)
return
}
logJSON(channels)
},
},
cobra.Command{
Use: "connect",
Short: "connect <connections_file> <user_token>",
Long: `Bulk connect things to channels`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsage(cmd.Short)
return
}
connIDs, err := connectionsFromFile(args[0])
if err != nil {
logError(err)
return
}
err = sdk.Connect(connIDs, args[1])
if err != nil {
logError(err)
return
}
},
},
cobra.Command{
Use: "test",
Short: "test",
@@ -187,30 +138,34 @@ var cmdProvision = []cobra.Command{
// Create things
for i := 0; i < numThings; i++ {
n := fmt.Sprintf("d%d", i)
k := "device"
if i%2 != 0 {
k = "app"
t := mfxsdk.Thing{
Name: n,
}
m, err := createThing(n, k, ut)
if err != nil {
logError(err)
return
}
things = append(things, m)
things = append(things, t)
}
things, err = sdk.CreateThings(things, ut)
if err != nil {
logError(err)
return
}
// Create channels
for i := 0; i < numChan; i++ {
n := fmt.Sprintf("c%d", i)
c, err := createChannel(n, ut)
if err != nil {
logError(err)
return
c := mfxsdk.Channel{
Name: n,
}
channels = append(channels, c)
}
channels, err = sdk.CreateChannels(channels, ut)
if err != nil {
logError(err)
return
}
// Connect things to channels - first thing to both channels, second only to first
for i := 0; i < numThings; i++ {
@@ -239,8 +194,8 @@ var cmdProvision = []cobra.Command{
func NewProvisionCmd() *cobra.Command {
cmd := cobra.Command{
Use: "provision",
Short: "Provision things and channels from config file",
Long: `Provision things and channels: use csv config file to provision things and channels`,
Short: "Provision things and channels from a config file",
Long: `Provision things and channels: use json or csv file to bulk provision things and channels`,
}
for i := range cmdProvision {
@@ -249,3 +204,141 @@ func NewProvisionCmd() *cobra.Command {
return &cmd
}
func thingsFromFile(path string) ([]mfxsdk.Thing, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return []mfxsdk.Thing{}, err
}
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
if err != nil {
return []mfxsdk.Thing{}, err
}
defer file.Close()
things := []mfxsdk.Thing{}
switch filepath.Ext(path) {
case csvExt:
reader := csv.NewReader(file)
for {
l, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return []mfxsdk.Thing{}, err
}
if len(l) < 1 {
return []mfxsdk.Thing{}, errors.New("empty line found in file")
}
thing := mfxsdk.Thing{
Name: l[0],
}
things = append(things, thing)
}
case jsonExt:
err := json.NewDecoder(file).Decode(&things)
if err != nil {
return []mfxsdk.Thing{}, err
}
default:
return []mfxsdk.Thing{}, err
}
return things, nil
}
func channelsFromFile(path string) ([]mfxsdk.Channel, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return []mfxsdk.Channel{}, err
}
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
if err != nil {
return []mfxsdk.Channel{}, err
}
defer file.Close()
channels := []mfxsdk.Channel{}
switch filepath.Ext(path) {
case csvExt:
reader := csv.NewReader(file)
for {
l, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return []mfxsdk.Channel{}, err
}
if len(l) < 1 {
return []mfxsdk.Channel{}, errors.New("empty line found in file")
}
channel := mfxsdk.Channel{
Name: l[0],
}
channels = append(channels, channel)
}
case jsonExt:
err := json.NewDecoder(file).Decode(&channels)
if err != nil {
return []mfxsdk.Channel{}, err
}
default:
return []mfxsdk.Channel{}, err
}
return channels, nil
}
func connectionsFromFile(path string) (mfxsdk.ConnectionIDs, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return mfxsdk.ConnectionIDs{}, err
}
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
if err != nil {
return mfxsdk.ConnectionIDs{}, err
}
defer file.Close()
connections := mfxsdk.ConnectionIDs{}
switch filepath.Ext(path) {
case csvExt:
reader := csv.NewReader(file)
for {
l, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return mfxsdk.ConnectionIDs{}, err
}
if len(l) < 1 {
return mfxsdk.ConnectionIDs{}, errors.New("empty line found in file")
}
connections.ThingIDs = append(connections.ThingIDs, l[0])
connections.ChannelIDs = append(connections.ChannelIDs, l[1])
}
case jsonExt:
err := json.NewDecoder(file).Decode(&connections)
if err != nil {
return mfxsdk.ConnectionIDs{}, err
}
default:
return mfxsdk.ConnectionIDs{}, err
}
return connections, nil
}
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package cli
+8 -7
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package cli
@@ -53,7 +49,7 @@ var cmdThings = []cobra.Command{
}
if args[0] == "all" {
l, err := sdk.Things(args[1], uint64(Offset), uint64(Limit))
l, err := sdk.Things(args[1], uint64(Offset), uint64(Limit), Name)
if err != nil {
logError(err)
return
@@ -123,7 +119,12 @@ var cmdThings = []cobra.Command{
return
}
if err := sdk.ConnectThing(args[0], args[1], args[2]); err != nil {
connIDs := mfxsdk.ConnectionIDs{
[]string{args[0]},
[]string{args[1]},
}
if err := sdk.Connect(connIDs, args[2]); err != nil {
logError(err)
return
}
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package cli
+4 -6
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package cli
@@ -19,7 +15,9 @@ var (
// Limit query parameter
Limit uint = 10
// Offset query parameter
Offset uint
Offset uint = 0
// Name query parameter
Name string = ""
)
func logJSON(iList ...interface{}) {
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package cli
+241
View File
@@ -0,0 +1,241 @@
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"os/signal"
"syscall"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/authn"
api "github.com/mainflux/mainflux/authn/api"
grpcapi "github.com/mainflux/mainflux/authn/api/grpc"
httpapi "github.com/mainflux/mainflux/authn/api/http"
"github.com/mainflux/mainflux/authn/jwt"
"github.com/mainflux/mainflux/authn/postgres"
"github.com/mainflux/mainflux/authn/tracing"
mfidp "github.com/mainflux/mainflux/authn/uuid"
"github.com/mainflux/mainflux/logger"
"github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
defLogLevel = "error"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "authn"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defHTTPPort = "8180"
defGRPCPort = "8181"
defSecret = "authn"
defServerCert = ""
defServerKey = ""
defJaegerURL = ""
envLogLevel = "MF_AUTHN_LOG_LEVEL"
envDBHost = "MF_AUTHN_DB_HOST"
envDBPort = "MF_AUTHN_DB_PORT"
envDBUser = "MF_AUTHN_DB_USER"
envDBPass = "MF_AUTHN_DB_PASS"
envDBName = "MF_AUTHN_DB"
envDBSSLMode = "MF_AUTHN_DB_SSL_MODE"
envDBSSLCert = "MF_AUTHN_DB_SSL_CERT"
envDBSSLKey = "MF_AUTHN_DB_SSL_KEY"
envDBSSLRootCert = "MF_AUTHN_DB_SSL_ROOT_CERT"
envHTTPPort = "MF_AUTHN_HTTP_PORT"
envGRPCPort = "MF_AUTHN_GRPC_PORT"
envSecret = "MF_AUTHN_SECRET"
envServerCert = "MF_AUTHN_SERVER_CERT"
envServerKey = "MF_AUTHN_SERVER_KEY"
envJaegerURL = "MF_JAEGER_URL"
)
type config struct {
logLevel string
dbConfig postgres.Config
httpPort string
grpcPort string
secret string
serverCert string
serverKey string
jaegerURL string
resetURL string
}
type tokenConfig struct {
hmacSampleSecret []byte // secret for signing token
tokenDuration string // token in duration in min
}
func main() {
cfg := loadConfig()
logger, err := logger.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
db := connectToDB(cfg.dbConfig, logger)
defer db.Close()
tracer, closer := initJaeger("authn", cfg.jaegerURL, logger)
defer closer.Close()
dbTracer, dbCloser := initJaeger("authn_db", cfg.jaegerURL, logger)
defer dbCloser.Close()
svc := newService(db, dbTracer, cfg.secret, logger)
errs := make(chan error, 2)
go startHTTPServer(tracer, svc, cfg.httpPort, cfg.serverCert, cfg.serverKey, logger, errs)
go startGRPCServer(tracer, svc, cfg.grpcPort, cfg.serverCert, cfg.serverKey, logger, errs)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
err = <-errs
logger.Error(fmt.Sprintf("Authentication service terminated: %s", err))
}
func loadConfig() config {
dbConfig := postgres.Config{
Host: mainflux.Env(envDBHost, defDBHost),
Port: mainflux.Env(envDBPort, defDBPort),
User: mainflux.Env(envDBUser, defDBUser),
Pass: mainflux.Env(envDBPass, defDBPass),
Name: mainflux.Env(envDBName, defDBName),
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
return config{
logLevel: mainflux.Env(envLogLevel, defLogLevel),
dbConfig: dbConfig,
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
grpcPort: mainflux.Env(envGRPCPort, defGRPCPort),
secret: mainflux.Env(envSecret, defSecret),
serverCert: mainflux.Env(envServerCert, defServerCert),
serverKey: mainflux.Env(envServerKey, defServerKey),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
}
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
if url == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
os.Exit(1)
}
return tracer, closer
}
func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
db, err := postgres.Connect(dbConfig)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
os.Exit(1)
}
return db
}
func newService(db *sqlx.DB, tracer opentracing.Tracer, secret string, logger logger.Logger) authn.Service {
database := postgres.NewDatabase(db)
repo := tracing.New(postgres.New(database), tracer)
idp := mfidp.New()
t := jwt.New(secret)
svc := authn.New(repo, idp, t)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "authn",
Subsystem: "api",
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"}),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "authn",
Subsystem: "api",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"}),
)
return svc
}
func startHTTPServer(tracer opentracing.Tracer, svc authn.Service, port string, certFile string, keyFile string, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
if certFile != "" || keyFile != "" {
logger.Info(fmt.Sprintf("Authentication service started using https, cert %s key %s, exposed port %s", certFile, keyFile, port))
errs <- http.ListenAndServeTLS(p, certFile, keyFile, httpapi.MakeHandler(svc, tracer))
return
}
logger.Info(fmt.Sprintf("Authentication service started using http, exposed port %s", port))
errs <- http.ListenAndServe(p, httpapi.MakeHandler(svc, tracer))
}
func startGRPCServer(tracer opentracing.Tracer, svc authn.Service, port string, certFile string, keyFile string, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
listener, err := net.Listen("tcp", p)
if err != nil {
logger.Error(fmt.Sprintf("Failed to listen on port %s: %s", port, err))
}
var server *grpc.Server
if certFile != "" || keyFile != "" {
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
if err != nil {
logger.Error(fmt.Sprintf("Failed to load authn certificates: %s", err))
os.Exit(1)
}
logger.Info(fmt.Sprintf("Authentication gRPC service started using https on port %s with cert %s key %s", port, certFile, keyFile))
server = grpc.NewServer(grpc.Creds(creds))
} else {
logger.Info(fmt.Sprintf("Authentication gRPC service started using http on port %s", port))
server = grpc.NewServer()
}
mainflux.RegisterAuthNServiceServer(server, grpcapi.NewServer(tracer, svc))
logger.Info(fmt.Sprintf("Authentication gRPC service started, exposed port %s", port))
errs <- server.Serve(listener)
}
+177 -102
View File
@@ -1,106 +1,129 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"database/sql"
"crypto/aes"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
authapi "github.com/mainflux/mainflux/authn/api/grpc"
rediscons "github.com/mainflux/mainflux/bootstrap/redis/consumer"
redisprod "github.com/mainflux/mainflux/bootstrap/redis/producer"
"github.com/mainflux/mainflux/logger"
opentracing "github.com/opentracing/opentracing-go"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
r "github.com/go-redis/redis"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
api "github.com/mainflux/mainflux/bootstrap/api"
"github.com/mainflux/mainflux/bootstrap/postgres"
"github.com/mainflux/mainflux/bootstrap/redis"
"github.com/mainflux/mainflux/logger"
mflog "github.com/mainflux/mainflux/logger"
mfsdk "github.com/mainflux/mainflux/sdk/go"
usersapi "github.com/mainflux/mainflux/users/api/grpc"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
defLogLevel = "error"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "bootstrap"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defClientTLS = "false"
defCACerts = ""
defPort = "8180"
defServerCert = ""
defServerKey = ""
defBaseURL = "http://localhost"
defThingsPrefix = ""
defUsersURL = "localhost:8181"
defESURL = "localhost:6379"
defESPass = ""
defESDB = "0"
defInstanceName = "bootstrap"
defLogLevel = "error"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "bootstrap"
defDBSSLMode = "disable"
defDBSSLCert = ""
defDBSSLKey = ""
defDBSSLRootCert = ""
defEncryptKey = "12345678910111213141516171819202"
defClientTLS = "false"
defCACerts = ""
defPort = "8180"
defServerCert = ""
defServerKey = ""
defBaseURL = "http://localhost"
defThingsPrefix = ""
defThingsESURL = "localhost:6379"
defThingsESPass = ""
defThingsESDB = "0"
defESURL = "localhost:6379"
defESPass = ""
defESDB = "0"
defESConsumerName = "bootstrap"
defJaegerURL = ""
defAuthURL = "localhost:8181"
defAuthTimeout = "1" // in seconds
envLogLevel = "MF_BOOTSTRAP_LOG_LEVEL"
envDBHost = "MF_BOOTSTRAP_DB_HOST"
envDBPort = "MF_BOOTSTRAP_DB_PORT"
envDBUser = "MF_BOOTSTRAP_DB_USER"
envDBPass = "MF_BOOTSTRAP_DB_PASS"
envDBName = "MF_BOOTSTRAP_DB"
envDBSSLMode = "MF_BOOTSTRAP_DB_SSL_MODE"
envDBSSLCert = "MF_BOOTSTRAP_DB_SSL_CERT"
envDBSSLKey = "MF_BOOTSTRAP_DB_SSL_KEY"
envDBSSLRootCert = "MF_BOOTSTRAP_DB_SSL_ROOT_CERT"
envClientTLS = "MF_BOOTSTRAP_CLIENT_TLS"
envCACerts = "MF_BOOTSTRAP_CA_CERTS"
envPort = "MF_BOOTSTRAP_PORT"
envServerCert = "MF_BOOTSTRAP_SERVER_CERT"
envServerKey = "MF_BOOTSTRAP_SERVER_KEY"
envBaseURL = "MF_SDK_BASE_URL"
envThingsPrefix = "MF_SDK_THINGS_PREFIX"
envUsersURL = "MF_USERS_URL"
envESURL = "MF_THINGS_ES_URL"
envESPass = "MF_THINGS_ES_PASS"
envESDB = "MF_THINGS_ES_DB"
envInstanceName = "MF_BOOTSTRAP_INSTANCE_NAME"
envLogLevel = "MF_BOOTSTRAP_LOG_LEVEL"
envDBHost = "MF_BOOTSTRAP_DB_HOST"
envDBPort = "MF_BOOTSTRAP_DB_PORT"
envDBUser = "MF_BOOTSTRAP_DB_USER"
envDBPass = "MF_BOOTSTRAP_DB_PASS"
envDBName = "MF_BOOTSTRAP_DB"
envDBSSLMode = "MF_BOOTSTRAP_DB_SSL_MODE"
envDBSSLCert = "MF_BOOTSTRAP_DB_SSL_CERT"
envDBSSLKey = "MF_BOOTSTRAP_DB_SSL_KEY"
envDBSSLRootCert = "MF_BOOTSTRAP_DB_SSL_ROOT_CERT"
envEncryptKey = "MF_BOOTSTRAP_ENCRYPT_KEY"
envClientTLS = "MF_BOOTSTRAP_CLIENT_TLS"
envCACerts = "MF_BOOTSTRAP_CA_CERTS"
envPort = "MF_BOOTSTRAP_PORT"
envServerCert = "MF_BOOTSTRAP_SERVER_CERT"
envServerKey = "MF_BOOTSTRAP_SERVER_KEY"
envBaseURL = "MF_SDK_BASE_URL"
envThingsPrefix = "MF_SDK_THINGS_PREFIX"
envThingsESURL = "MF_THINGS_ES_URL"
envThingsESPass = "MF_THINGS_ES_PASS"
envThingsESDB = "MF_THINGS_ES_DB"
envESURL = "MF_BOOTSTRAP_ES_URL"
envESPass = "MF_BOOTSTRAP_ES_PASS"
envESDB = "MF_BOOTSTRAP_ES_DB"
envESConsumerName = "MF_BOOTSTRAP_EVENT_CONSUMER"
envJaegerURL = "MF_JAEGER_URL"
envAuthURL = "MF_AUTH_URL"
envAuthTimeout = "MF_AUTH_TIMEOUT"
)
type config struct {
logLevel string
dbConfig postgres.Config
clientTLS bool
caCerts string
httpPort string
serverCert string
serverKey string
baseURL string
thingsPrefix string
usersURL string
esURL string
esPass string
esDB string
instanceName string
logLevel string
dbConfig postgres.Config
clientTLS bool
encKey []byte
caCerts string
httpPort string
serverCert string
serverKey string
baseURL string
thingsPrefix string
esThingsURL string
esThingsPass string
esThingsDB string
esURL string
esPass string
esDB string
esConsumerName string
jaegerURL string
authURL string
authTimeout time.Duration
}
func main() {
cfg := loadConfig()
logger, err := logger.New(os.Stdout, cfg.logLevel)
logger, err := mflog.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
@@ -108,17 +131,25 @@ func main() {
db := connectToDB(cfg.dbConfig, logger)
defer db.Close()
conn := connectToUsers(cfg, logger)
defer conn.Close()
thingsESConn := connectToRedis(cfg.esThingsURL, cfg.esThingsPass, cfg.esThingsDB, logger)
defer thingsESConn.Close()
esConn := connectToRedis(cfg.esURL, cfg.esPass, cfg.esDB, logger)
defer esConn.Close()
esClient := connectToRedis(cfg.esURL, cfg.esPass, cfg.esDB, logger)
defer esClient.Close()
svc := newService(conn, db, logger, cfg)
authTracer, authCloser := initJaeger("auth", cfg.jaegerURL, logger)
defer authCloser.Close()
authConn := connectToAuth(cfg, logger)
defer authConn.Close()
auth := authapi.NewClient(authTracer, authConn, cfg.authTimeout)
svc := newService(auth, db, logger, esClient, cfg)
errs := make(chan error, 2)
go startHTTPServer(svc, cfg, logger, errs)
go subscribeToThingsES(svc, esConn, cfg.instanceName, logger)
go subscribeToThingsES(svc, thingsESConn, cfg.esConsumerName, logger)
go func() {
c := make(chan os.Signal)
@@ -147,26 +178,46 @@ func loadConfig() config {
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
}
return config{
logLevel: mainflux.Env(envLogLevel, defLogLevel),
dbConfig: dbConfig,
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
httpPort: mainflux.Env(envPort, defPort),
serverCert: mainflux.Env(envServerCert, defServerCert),
serverKey: mainflux.Env(envServerKey, defServerKey),
baseURL: mainflux.Env(envBaseURL, defBaseURL),
thingsPrefix: mainflux.Env(envThingsPrefix, defThingsPrefix),
usersURL: mainflux.Env(envUsersURL, defUsersURL),
timeout, err := strconv.ParseInt(mainflux.Env(envAuthTimeout, defAuthTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envAuthTimeout, err.Error())
}
encKey, err := hex.DecodeString(mainflux.Env(envEncryptKey, defEncryptKey))
if err != nil {
log.Fatalf("Invalid %s value: %s", envEncryptKey, err.Error())
}
if err := os.Unsetenv(envEncryptKey); err != nil {
log.Fatalf("Unable to unset %s value: %s", envEncryptKey, err.Error())
}
if _, err := aes.NewCipher(encKey); err != nil {
log.Fatalf("Invalid %s value: %s", envEncryptKey, err.Error())
}
esURL: mainflux.Env(envESURL, defESURL),
esPass: mainflux.Env(envESPass, defESPass),
esDB: mainflux.Env(envESDB, defESDB),
instanceName: mainflux.Env(envInstanceName, defInstanceName),
return config{
logLevel: mainflux.Env(envLogLevel, defLogLevel),
dbConfig: dbConfig,
clientTLS: tls,
encKey: encKey,
caCerts: mainflux.Env(envCACerts, defCACerts),
httpPort: mainflux.Env(envPort, defPort),
serverCert: mainflux.Env(envServerCert, defServerCert),
serverKey: mainflux.Env(envServerKey, defServerKey),
baseURL: mainflux.Env(envBaseURL, defBaseURL),
thingsPrefix: mainflux.Env(envThingsPrefix, defThingsPrefix),
esThingsURL: mainflux.Env(envThingsESURL, defThingsESURL),
esThingsPass: mainflux.Env(envThingsESPass, defThingsESPass),
esThingsDB: mainflux.Env(envThingsESDB, defThingsESDB),
esURL: mainflux.Env(envESURL, defESURL),
esPass: mainflux.Env(envESPass, defESPass),
esDB: mainflux.Env(envESDB, defESDB),
esConsumerName: mainflux.Env(envESConsumerName, defESConsumerName),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
authURL: mainflux.Env(envAuthURL, defAuthURL),
authTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToDB(cfg postgres.Config, logger logger.Logger) *sql.DB {
func connectToDB(cfg postgres.Config, logger mflog.Logger) *sqlx.DB {
db, err := postgres.Connect(cfg)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
@@ -175,7 +226,7 @@ func connectToDB(cfg postgres.Config, logger logger.Logger) *sql.DB {
return db
}
func connectToRedis(redisURL, redisPass, redisDB string, logger logger.Logger) *r.Client {
func connectToRedis(redisURL, redisPass, redisDB string, logger mflog.Logger) *r.Client {
db, err := strconv.Atoi(redisDB)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to redis: %s", err))
@@ -189,7 +240,31 @@ func connectToRedis(redisURL, redisPass, redisDB string, logger logger.Logger) *
})
}
func newService(conn *grpc.ClientConn, db *sql.DB, logger logger.Logger, cfg config) bootstrap.Service {
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
if url == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func newService(auth mainflux.AuthNServiceClient, db *sqlx.DB, logger mflog.Logger, esClient *r.Client, cfg config) bootstrap.Service {
thingsRepo := postgres.NewConfigRepository(db, logger)
config := mfsdk.Config{
@@ -198,9 +273,9 @@ func newService(conn *grpc.ClientConn, db *sql.DB, logger logger.Logger, cfg con
}
sdk := mfsdk.NewSDK(config)
users := usersapi.NewClient(conn)
svc := bootstrap.New(users, thingsRepo, sdk)
svc := bootstrap.New(auth, thingsRepo, sdk, cfg.encKey)
svc = redisprod.NewEventStoreMiddleware(svc, esClient)
svc = api.NewLoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
@@ -220,7 +295,7 @@ func newService(conn *grpc.ClientConn, db *sql.DB, logger logger.Logger, cfg con
return svc
}
func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn {
func connectToAuth(cfg config, logger logger.Logger) *grpc.ClientConn {
var opts []grpc.DialOption
if cfg.clientTLS {
if cfg.caCerts != "" {
@@ -236,7 +311,7 @@ func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn {
logger.Info("gRPC communication is not encrypted")
}
conn, err := grpc.Dial(cfg.usersURL, opts...)
conn, err := grpc.Dial(cfg.authURL, opts...)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
os.Exit(1)
@@ -245,20 +320,20 @@ func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn {
return conn
}
func startHTTPServer(svc bootstrap.Service, cfg config, logger logger.Logger, errs chan error) {
func startHTTPServer(svc bootstrap.Service, cfg config, logger mflog.Logger, errs chan error) {
p := fmt.Sprintf(":%s", cfg.httpPort)
if cfg.serverCert != "" || cfg.serverKey != "" {
logger.Info(fmt.Sprintf("Bootstrap service started using https on port %s with cert %s key %s",
cfg.httpPort, cfg.serverCert, cfg.serverKey))
errs <- http.ListenAndServeTLS(p, cfg.serverCert, cfg.serverKey, api.MakeHandler(svc, bootstrap.NewConfigReader()))
errs <- http.ListenAndServeTLS(p, cfg.serverCert, cfg.serverKey, api.MakeHandler(svc, bootstrap.NewConfigReader(cfg.encKey)))
return
}
logger.Info(fmt.Sprintf("Bootstrap service started using http on port %s", cfg.httpPort))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, bootstrap.NewConfigReader()))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, bootstrap.NewConfigReader(cfg.encKey)))
}
func subscribeToThingsES(svc bootstrap.Service, client *r.Client, consumer string, logger logger.Logger) {
eventStore := redis.NewEventStore(svc, client, consumer, logger)
func subscribeToThingsES(svc bootstrap.Service, client *r.Client, consumer string, logger mflog.Logger) {
eventStore := rediscons.NewEventStore(svc, client, consumer, logger)
logger.Info("Subscribed to Redis Event Store")
if err := eventStore.Subscribe("mainflux.things"); err != nil {
logger.Warn(fmt.Sprintf("Botstrap service failed to subscribe to event sourcing: %s", err))
+96 -38
View File
@@ -1,14 +1,12 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
@@ -16,6 +14,7 @@ import (
"strconv"
"strings"
"syscall"
"time"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/gocql/gocql"
@@ -24,8 +23,10 @@ import (
"github.com/mainflux/mainflux/readers"
"github.com/mainflux/mainflux/readers/api"
"github.com/mainflux/mainflux/readers/cassandra"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
@@ -33,31 +34,42 @@ import (
const (
sep = ","
defLogLevel = "error"
defPort = "8180"
defCluster = "127.0.0.1"
defKeyspace = "mainflux"
defThingsURL = "localhost:8181"
defClientTLS = "false"
defCACerts = ""
defLogLevel = "error"
defPort = "8180"
defCluster = "127.0.0.1"
defKeyspace = "mainflux"
defDBUsername = ""
defDBPassword = ""
defDBPort = "9042"
defThingsURL = "localhost:8181"
defClientTLS = "false"
defCACerts = ""
defJaegerURL = ""
defThingsTimeout = "1" // in seconds
envLogLevel = "MF_CASSANDRA_READER_LOG_LEVEL"
envPort = "MF_CASSANDRA_READER_PORT"
envCluster = "MF_CASSANDRA_READER_DB_CLUSTER"
envKeyspace = "MF_CASSANDRA_READER_DB_KEYSPACE"
envThingsURL = "MF_THINGS_URL"
envClientTLS = "MF_CASSANDRA_READER_CLIENT_TLS"
envCACerts = "MF_CASSANDRA_READER_CA_CERTS"
envLogLevel = "MF_CASSANDRA_READER_LOG_LEVEL"
envPort = "MF_CASSANDRA_READER_PORT"
envCluster = "MF_CASSANDRA_READER_DB_CLUSTER"
envKeyspace = "MF_CASSANDRA_READER_DB_KEYSPACE"
envDBUsername = "MF_CASSANDRA_READER_DB_USERNAME"
envDBPassword = "MF_CASSANDRA_READER_DB_PASSWORD"
envDBPort = "MF_CASSANDRA_READER_DB_PORT"
envThingsURL = "MF_THINGS_URL"
envClientTLS = "MF_CASSANDRA_READER_CLIENT_TLS"
envCACerts = "MF_CASSANDRA_READER_CA_CERTS"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_CASSANDRA_READER_THINGS_TIMEOUT"
)
type config struct {
logLevel string
port string
cluster string
keyspace string
thingsURL string
clientTLS bool
caCerts string
logLevel string
port string
dbCfg cassandra.DBConfig
thingsURL string
clientTLS bool
caCerts string
jaegerURL string
thingsTimeout time.Duration
}
func main() {
@@ -68,13 +80,16 @@ func main() {
log.Fatalf(err.Error())
}
session := connectToCassandra(cfg.cluster, cfg.keyspace, logger)
session := connectToCassandra(cfg.dbCfg, logger)
defer session.Close()
conn := connectToThings(cfg, logger)
defer conn.Close()
tc := thingsapi.NewClient(conn)
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
tc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
repo := newService(session, logger)
errs := make(chan error, 2)
@@ -92,24 +107,43 @@ func main() {
}
func loadConfig() config {
dbPort, err := strconv.Atoi(mainflux.Env(envDBPort, defDBPort))
if err != nil {
log.Fatal(err)
}
dbCfg := cassandra.DBConfig{
Hosts: strings.Split(mainflux.Env(envCluster, defCluster), sep),
Keyspace: mainflux.Env(envKeyspace, defKeyspace),
Username: mainflux.Env(envDBUsername, defDBUsername),
Password: mainflux.Env(envDBPassword, defDBPassword),
Port: dbPort,
}
tls, err := strconv.ParseBool(mainflux.Env(envClientTLS, defClientTLS))
if err != nil {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
cluster: mainflux.Env(envCluster, defCluster),
keyspace: mainflux.Env(envKeyspace, defKeyspace),
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
dbCfg: dbCfg,
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
func connectToCassandra(cluster, keyspace string, logger logger.Logger) *gocql.Session {
session, err := cassandra.Connect(strings.Split(cluster, sep), keyspace)
func connectToCassandra(dbCfg cassandra.DBConfig, logger logger.Logger) *gocql.Session {
session, err := cassandra.Connect(dbCfg)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to Cassandra cluster: %s", err))
os.Exit(1)
@@ -142,6 +176,30 @@ func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
return conn
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
if url == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func newService(session *gocql.Session, logger logger.Logger) readers.MessageRepository {
repo := cassandra.New(session)
repo = api.LoggingMiddleware(repo, logger)
+80 -29
View File
@@ -1,54 +1,63 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"github.com/BurntSushi/toml"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/gocql/gocql"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/transformers/senml"
"github.com/mainflux/mainflux/writers"
"github.com/mainflux/mainflux/writers/api"
"github.com/mainflux/mainflux/writers/cassandra"
"github.com/nats-io/go-nats"
nats "github.com/nats-io/go-nats"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
const (
queue = "cassandra-writer"
sep = ","
svcName = "cassandra-writer"
sep = ","
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defPort = "8180"
defCluster = "127.0.0.1"
defKeyspace = "mainflux"
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defPort = "8180"
defCluster = "127.0.0.1"
defKeyspace = "mainflux"
defDBUsername = ""
defDBPassword = ""
defDBPort = "9042"
defChanCfgPath = "/config/channels.toml"
envNatsURL = "MF_NATS_URL"
envLogLevel = "MF_CASSANDRA_WRITER_LOG_LEVEL"
envPort = "MF_CASSANDRA_WRITER_PORT"
envCluster = "MF_CASSANDRA_WRITER_DB_CLUSTER"
envKeyspace = "MF_CASSANDRA_WRITER_DB_KEYSPACE"
envNatsURL = "MF_NATS_URL"
envLogLevel = "MF_CASSANDRA_WRITER_LOG_LEVEL"
envPort = "MF_CASSANDRA_WRITER_PORT"
envCluster = "MF_CASSANDRA_WRITER_DB_CLUSTER"
envKeyspace = "MF_CASSANDRA_WRITER_DB_KEYSPACE"
envDBUsername = "MF_CASSANDRA_WRITER_DB_USERNAME"
envDBPassword = "MF_CASSANDRA_WRITER_DB_PASSWORD"
envDBPort = "MF_CASSANDRA_WRITER_DB_PORT"
envChanCfgPath = "MF_CASSANDRA_WRITER_CHANNELS_CONFIG"
)
type config struct {
natsURL string
logLevel string
port string
cluster string
keyspace string
dbCfg cassandra.DBConfig
channels map[string]bool
}
func main() {
@@ -62,11 +71,12 @@ func main() {
nc := connectToNATS(cfg.natsURL, logger)
defer nc.Close()
session := connectToCassandra(cfg.cluster, cfg.keyspace, logger)
session := connectToCassandra(cfg.dbCfg, logger)
defer session.Close()
repo := newService(session, logger)
if err := writers.Start(nc, repo, queue, logger); err != nil {
st := senml.New()
if err := writers.Start(nc, repo, st, svcName, cfg.channels, logger); err != nil {
logger.Error(fmt.Sprintf("Failed to create Cassandra writer: %s", err))
}
@@ -85,15 +95,56 @@ func main() {
}
func loadConfig() config {
dbPort, err := strconv.Atoi(mainflux.Env(envDBPort, defDBPort))
if err != nil {
log.Fatal(err)
}
dbCfg := cassandra.DBConfig{
Hosts: strings.Split(mainflux.Env(envCluster, defCluster), sep),
Keyspace: mainflux.Env(envKeyspace, defKeyspace),
Username: mainflux.Env(envDBUsername, defDBUsername),
Password: mainflux.Env(envDBPassword, defDBPassword),
Port: dbPort,
}
chanCfgPath := mainflux.Env(envChanCfgPath, defChanCfgPath)
return config{
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
cluster: mainflux.Env(envCluster, defCluster),
keyspace: mainflux.Env(envKeyspace, defKeyspace),
dbCfg: dbCfg,
channels: loadChansConfig(chanCfgPath),
}
}
type channels struct {
List []string `toml:"filter"`
}
type chanConfig struct {
Channels channels `toml:"channels"`
}
func loadChansConfig(chanConfigPath string) map[string]bool {
data, err := ioutil.ReadFile(chanConfigPath)
if err != nil {
log.Fatal(err)
}
var chanCfg chanConfig
if err := toml.Unmarshal(data, &chanCfg); err != nil {
log.Fatal(err)
}
chans := map[string]bool{}
for _, ch := range chanCfg.Channels.List {
chans[ch] = true
}
return chans
}
func connectToNATS(url string, logger logger.Logger) *nats.Conn {
nc, err := nats.Connect(url)
if err != nil {
@@ -104,8 +155,8 @@ func connectToNATS(url string, logger logger.Logger) *nats.Conn {
return nc
}
func connectToCassandra(cluster, keyspace string, logger logger.Logger) *gocql.Session {
session, err := cassandra.Connect(strings.Split(cluster, sep), keyspace)
func connectToCassandra(dbCfg cassandra.DBConfig, logger logger.Logger) *gocql.Session {
session, err := cassandra.Connect(dbCfg)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to Cassandra cluster: %s", err))
os.Exit(1)
@@ -116,8 +167,8 @@ func connectToCassandra(cluster, keyspace string, logger logger.Logger) *gocql.S
func newService(session *gocql.Session, logger logger.Logger) writers.MessageRepository {
repo := cassandra.New(session)
repo = writers.LoggingMiddleware(repo, logger)
repo = writers.MetricsMiddleware(
repo = api.LoggingMiddleware(repo, logger)
repo = api.MetricsMiddleware(
repo,
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "cassandra",
@@ -139,5 +190,5 @@ func newService(session *gocql.Session, logger logger.Logger) writers.MessageRep
func startHTTPServer(port string, errs chan error, logger logger.Logger) {
p := fmt.Sprintf(":%s", port)
logger.Info(fmt.Sprintf("Cassandra writer service started, exposed port %s", port))
errs <- http.ListenAndServe(p, cassandra.MakeHandler())
errs <- http.ListenAndServe(p, api.MakeHandler(svcName))
}
+9 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package main
@@ -120,6 +116,14 @@ func main() {
"offset query parameter",
)
rootCmd.PersistentFlags().StringVarP(
&cli.Name,
"name",
"n",
"",
"name query parameter",
)
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
+76 -36
View File
@@ -1,14 +1,12 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
@@ -24,8 +22,10 @@ import (
"github.com/mainflux/mainflux/coap/api"
"github.com/mainflux/mainflux/coap/nats"
logger "github.com/mainflux/mainflux/logger"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@@ -33,31 +33,37 @@ import (
)
const (
defPort = "5683"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
defLogLevel = "error"
defClientTLS = "false"
defCACerts = ""
defPingPeriod = "12"
defPort = "5683"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
defLogLevel = "error"
defClientTLS = "false"
defCACerts = ""
defPingPeriod = "12"
defJaegerURL = ""
defThingsTimeout = "1" // in seconds
envPort = "MF_COAP_ADAPTER_PORT"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
envLogLevel = "MF_COAP_ADAPTER_LOG_LEVEL"
envClientTLS = "MF_COAP_ADAPTER_CLIENT_TLS"
envCACerts = "MF_COAP_ADAPTER_CA_CERTS"
envPingPeriod = "MF_COAP_ADAPTER_PING_PERIOD"
envPort = "MF_COAP_ADAPTER_PORT"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
envLogLevel = "MF_COAP_ADAPTER_LOG_LEVEL"
envClientTLS = "MF_COAP_ADAPTER_CLIENT_TLS"
envCACerts = "MF_COAP_ADAPTER_CA_CERTS"
envPingPeriod = "MF_COAP_ADAPTER_PING_PERIOD"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_COAP_ADAPTER_THINGS_TIMEOUT"
)
type config struct {
port string
natsURL string
thingsURL string
logLevel string
clientTLS bool
caCerts string
pingPeriod time.Duration
port string
natsURL string
thingsURL string
logLevel string
clientTLS bool
caCerts string
pingPeriod time.Duration
jaegerURL string
thingsTimeout time.Duration
}
func main() {
@@ -78,10 +84,13 @@ func main() {
conn := connectToThings(cfg, logger)
defer conn.Close()
cc := thingsapi.NewClient(conn)
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
cc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
respChan := make(chan string, 10000)
pubsub := nats.New(nc)
svc := coap.New(pubsub, respChan)
svc := coap.New(pubsub, cc, respChan)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
@@ -130,14 +139,21 @@ func loadConfig() config {
log.Fatalf("Value of %s must be between 1 and 24", envPingPeriod)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
port: mainflux.Env(envPort, defPort),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
pingPeriod: time.Duration(pp),
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
port: mainflux.Env(envPort, defPort),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
pingPeriod: time.Duration(pp),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
@@ -165,6 +181,30 @@ func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
return conn
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
if url == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func startHTTPServer(port string, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
logger.Info(fmt.Sprintf("CoAP service started, exposed port %s", port))
+79 -34
View File
@@ -1,20 +1,19 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"google.golang.org/grpc/credentials"
@@ -24,34 +23,43 @@ import (
"github.com/mainflux/mainflux/http/api"
"github.com/mainflux/mainflux/http/nats"
"github.com/mainflux/mainflux/logger"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
broker "github.com/nats-io/go-nats"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
)
const (
defClientTLS = "false"
defCACerts = ""
defPort = "8180"
defLogLevel = "error"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
envClientTLS = "MF_HTTP_ADAPTER_CLIENT_TLS"
envCACerts = "MF_HTTP_ADAPTER_CA_CERTS"
envPort = "MF_HTTP_ADAPTER_PORT"
envLogLevel = "MF_HTTP_ADAPTER_LOG_LEVEL"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
defClientTLS = "false"
defCACerts = ""
defPort = "8180"
defLogLevel = "error"
defNatsURL = broker.DefaultURL
defThingsURL = "localhost:8181"
defJaegerURL = ""
defThingsTimeout = "1" // in seconds
envClientTLS = "MF_HTTP_ADAPTER_CLIENT_TLS"
envCACerts = "MF_HTTP_ADAPTER_CA_CERTS"
envPort = "MF_HTTP_ADAPTER_PORT"
envLogLevel = "MF_HTTP_ADAPTER_LOG_LEVEL"
envNatsURL = "MF_NATS_URL"
envThingsURL = "MF_THINGS_URL"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_HTTP_ADAPTER_THINGS_TIMEOUT"
)
type config struct {
thingsURL string
natsURL string
logLevel string
port string
clientTLS bool
caCerts string
thingsURL string
natsURL string
logLevel string
port string
clientTLS bool
caCerts string
jaegerURL string
thingsTimeout time.Duration
}
func main() {
@@ -73,10 +81,16 @@ func main() {
conn := connectToThings(cfg, logger)
defer conn.Close()
cc := thingsapi.NewClient(conn)
tracer, closer := initJaeger("http_adapter", cfg.jaegerURL, logger)
defer closer.Close()
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
cc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
pub := nats.NewMessagePublisher(nc)
svc := adapter.New(pub)
svc := adapter.New(pub, cc)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
@@ -99,7 +113,7 @@ func main() {
go func() {
p := fmt.Sprintf(":%s", cfg.port)
logger.Info(fmt.Sprintf("HTTP adapter service started on port %s", cfg.port))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, cc))
errs <- http.ListenAndServe(p, api.MakeHandler(svc, tracer))
}()
go func() {
@@ -118,14 +132,45 @@ func loadConfig() config {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
return config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
return config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
}
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
if url == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
+85 -45
View File
@@ -2,12 +2,15 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
influxdata "github.com/influxdata/influxdb/client/v2"
@@ -16,47 +19,55 @@ import (
"github.com/mainflux/mainflux/readers"
"github.com/mainflux/mainflux/readers/api"
"github.com/mainflux/mainflux/readers/influxdb"
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
thingsapi "github.com/mainflux/mainflux/things/api/auth/grpc"
opentracing "github.com/opentracing/opentracing-go"
stdprometheus "github.com/prometheus/client_golang/prometheus"
jconfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
defThingsURL = "localhost:8181"
defLogLevel = "error"
defPort = "8180"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "8086"
defDBUser = "mainflux"
defDBPass = "mainflux"
defClientTLS = "false"
defCACerts = ""
defThingsURL = "localhost:8181"
defLogLevel = "error"
defPort = "8180"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "8086"
defDBUser = "mainflux"
defDBPass = "mainflux"
defClientTLS = "false"
defCACerts = ""
defJaegerURL = ""
defThingsTimeout = "1" // in seconds
envThingsURL = "MF_THINGS_URL"
envLogLevel = "MF_INFLUX_READER_LOG_LEVEL"
envPort = "MF_INFLUX_READER_PORT"
envDBName = "MF_INFLUX_READER_DB_NAME"
envDBHost = "MF_INFLUX_READER_DB_HOST"
envDBPort = "MF_INFLUX_READER_DB_PORT"
envDBUser = "MF_INFLUX_READER_DB_USER"
envDBPass = "MF_INFLUX_READER_DB_PASS"
envClientTLS = "MF_INFLUX_READER_CLIENT_TLS"
envCACerts = "MF_INFLUX_READER_CA_CERTS"
envThingsURL = "MF_THINGS_URL"
envLogLevel = "MF_INFLUX_READER_LOG_LEVEL"
envPort = "MF_INFLUX_READER_PORT"
envDBName = "MF_INFLUX_READER_DB_NAME"
envDBHost = "MF_INFLUX_READER_DB_HOST"
envDBPort = "MF_INFLUX_READER_DB_PORT"
envDBUser = "MF_INFLUX_READER_DB_USER"
envDBPass = "MF_INFLUX_READER_DB_PASS"
envClientTLS = "MF_INFLUX_READER_CLIENT_TLS"
envCACerts = "MF_INFLUX_READER_CA_CERTS"
envJaegerURL = "MF_JAEGER_URL"
envThingsTimeout = "MF_INFLUX_READER_THINGS_TIMEOUT"
)
type config struct {
thingsURL string
logLevel string
port string
dbName string
dbHost string
dbPort string
dbUser string
dbPass string
clientTLS bool
caCerts string
thingsURL string
logLevel string
port string
dbName string
dbHost string
dbPort string
dbUser string
dbPass string
clientTLS bool
caCerts string
jaegerURL string
thingsTimeout time.Duration
}
func main() {
@@ -68,7 +79,10 @@ func main() {
conn := connectToThings(cfg, logger)
defer conn.Close()
tc := thingsapi.NewClient(conn)
thingsTracer, thingsCloser := initJaeger("things", cfg.jaegerURL, logger)
defer thingsCloser.Close()
tc := thingsapi.NewClient(conn, thingsTracer, cfg.thingsTimeout)
client, err := influxdata.NewHTTPClient(clientCfg)
if err != nil {
@@ -98,17 +112,24 @@ func loadConfigs() (config, influxdata.HTTPConfig) {
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
}
timeout, err := strconv.ParseInt(mainflux.Env(envThingsTimeout, defThingsTimeout), 10, 64)
if err != nil {
log.Fatalf("Invalid %s value: %s", envThingsTimeout, err.Error())
}
cfg := config{
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
dbName: mainflux.Env(envDBName, defDBName),
dbHost: mainflux.Env(envDBHost, defDBHost),
dbPort: mainflux.Env(envDBPort, defDBPort),
dbUser: mainflux.Env(envDBUser, defDBUser),
dbPass: mainflux.Env(envDBPass, defDBPass),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
dbName: mainflux.Env(envDBName, defDBName),
dbHost: mainflux.Env(envDBHost, defDBHost),
dbPort: mainflux.Env(envDBPort, defDBPort),
dbUser: mainflux.Env(envDBUser, defDBUser),
dbPass: mainflux.Env(envDBPass, defDBPass),
clientTLS: tls,
caCerts: mainflux.Env(envCACerts, defCACerts),
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
thingsTimeout: time.Duration(timeout) * time.Second,
}
clientCfg := influxdata.HTTPConfig{
@@ -144,13 +165,32 @@ func connectToThings(cfg config, logger logger.Logger) *grpc.ClientConn {
return conn
}
func newService(client influxdata.Client, dbName string, logger logger.Logger) readers.MessageRepository {
repo, err := influxdb.New(client, dbName)
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
if url == "" {
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
}
tracer, closer, err := jconfig.Configuration{
ServiceName: svcName,
Sampler: &jconfig.SamplerConfig{
Type: "const",
Param: 1,
},
Reporter: &jconfig.ReporterConfig{
LocalAgentHostPort: url,
LogSpans: true,
},
}.NewTracer()
if err != nil {
logger.Error(fmt.Sprintf("Failed to create InfluxDB writer: %s", err))
logger.Error(fmt.Sprintf("Failed to init Jaeger client: %s", err))
os.Exit(1)
}
return tracer, closer
}
func newService(client influxdata.Client, dbName string, logger logger.Logger) readers.MessageRepository {
repo := influxdb.New(client, dbName)
repo = api.LoggingMiddleware(repo, logger)
repo = api.MetricsMiddleware(
repo,
+86 -78
View File
@@ -1,78 +1,75 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/BurntSushi/toml"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
influxdata "github.com/influxdata/influxdb/client/v2"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/transformers/senml"
"github.com/mainflux/mainflux/writers"
"github.com/mainflux/mainflux/writers/api"
"github.com/mainflux/mainflux/writers/influxdb"
"github.com/nats-io/go-nats"
nats "github.com/nats-io/go-nats"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
const (
queue = "influxdb-writer"
svcName = "influxdb-writer"
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defPort = "8180"
defBatchSize = "5000"
defBatchTimeout = "5"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "8086"
defDBUser = "mainflux"
defDBPass = "mainflux"
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defPort = "8180"
defDBName = "mainflux"
defDBHost = "localhost"
defDBPort = "8086"
defDBUser = "mainflux"
defDBPass = "mainflux"
defChanCfgPath = "/config/channels.toml"
envNatsURL = "MF_NATS_URL"
envLogLevel = "MF_INFLUX_WRITER_LOG_LEVEL"
envPort = "MF_INFLUX_WRITER_PORT"
envBatchSize = "MF_INFLUX_WRITER_BATCH_SIZE"
envBatchTimeout = "MF_INFLUX_WRITER_BATCH_TIMEOUT"
envDBName = "MF_INFLUX_WRITER_DB_NAME"
envDBHost = "MF_INFLUX_WRITER_DB_HOST"
envDBPort = "MF_INFLUX_WRITER_DB_PORT"
envDBUser = "MF_INFLUX_WRITER_DB_USER"
envDBPass = "MF_INFLUX_WRITER_DB_PASS"
envNatsURL = "MF_NATS_URL"
envLogLevel = "MF_INFLUX_WRITER_LOG_LEVEL"
envPort = "MF_INFLUX_WRITER_PORT"
envDBName = "MF_INFLUX_WRITER_DB_NAME"
envDBHost = "MF_INFLUX_WRITER_DB_HOST"
envDBPort = "MF_INFLUX_WRITER_DB_PORT"
envDBUser = "MF_INFLUX_WRITER_DB_USER"
envDBPass = "MF_INFLUX_WRITER_DB_PASS"
envChanCfgPath = "MF_INFLUX_WRITER_CHANNELS_CONFIG"
)
type config struct {
NatsURL string
LogLevel string
Port string
BatchSize string
BatchTimeout string
DBName string
DBHost string
DBPort string
DBUser string
DBPass string
natsURL string
logLevel string
port string
dbName string
dbHost string
dbPort string
dbUser string
dbPass string
channels map[string]bool
}
func main() {
cfg, clientCfg := loadConfigs()
logger, err := logger.New(os.Stdout, cfg.LogLevel)
logger, err := logger.New(os.Stdout, cfg.logLevel)
if err != nil {
log.Fatalf(err.Error())
}
nc, err := nats.Connect(cfg.NatsURL)
nc, err := nats.Connect(cfg.natsURL)
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
os.Exit(1)
@@ -86,30 +83,14 @@ func main() {
}
defer client.Close()
batchTimeout, err := strconv.Atoi(cfg.BatchTimeout)
if err != nil {
logger.Error(fmt.Sprintf("Invalid value for batch timeout: %s", err))
os.Exit(1)
}
batchSize, err := strconv.Atoi(cfg.BatchSize)
if err != nil {
logger.Error(fmt.Sprintf("Invalid value of batch size: %s", err))
os.Exit(1)
}
timeout := time.Duration(batchTimeout) * time.Second
repo, err := influxdb.New(client, cfg.DBName, batchSize, timeout)
if err != nil {
logger.Error(fmt.Sprintf("Failed to create InfluxDB writer: %s", err))
os.Exit(1)
}
repo := influxdb.New(client, cfg.dbName)
counter, latency := makeMetrics()
repo = writers.LoggingMiddleware(repo, logger)
repo = writers.MetricsMiddleware(repo, counter, latency)
if err := writers.Start(nc, repo, queue, logger); err != nil {
logger.Error(fmt.Sprintf("Failed to start message writer: %s", err))
repo = api.LoggingMiddleware(repo, logger)
repo = api.MetricsMiddleware(repo, counter, latency)
st := senml.New()
if err := writers.Start(nc, repo, st, svcName, cfg.channels, logger); err != nil {
logger.Error(fmt.Sprintf("Failed to start InfluxDB writer: %s", err))
os.Exit(1)
}
@@ -120,35 +101,62 @@ func main() {
errs <- fmt.Errorf("%s", <-c)
}()
go startHTTPService(cfg.Port, logger, errs)
go startHTTPService(cfg.port, logger, errs)
err = <-errs
logger.Error(fmt.Sprintf("InfluxDB writer service terminated: %s", err))
}
func loadConfigs() (config, influxdata.HTTPConfig) {
chanCfgPath := mainflux.Env(envChanCfgPath, defChanCfgPath)
cfg := config{
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
Port: mainflux.Env(envPort, defPort),
BatchSize: mainflux.Env(envBatchSize, defBatchSize),
BatchTimeout: mainflux.Env(envBatchTimeout, defBatchTimeout),
DBName: mainflux.Env(envDBName, defDBName),
DBHost: mainflux.Env(envDBHost, defDBHost),
DBPort: mainflux.Env(envDBPort, defDBPort),
DBUser: mainflux.Env(envDBUser, defDBUser),
DBPass: mainflux.Env(envDBPass, defDBPass),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
port: mainflux.Env(envPort, defPort),
dbName: mainflux.Env(envDBName, defDBName),
dbHost: mainflux.Env(envDBHost, defDBHost),
dbPort: mainflux.Env(envDBPort, defDBPort),
dbUser: mainflux.Env(envDBUser, defDBUser),
dbPass: mainflux.Env(envDBPass, defDBPass),
channels: loadChansConfig(chanCfgPath),
}
clientCfg := influxdata.HTTPConfig{
Addr: fmt.Sprintf("http://%s:%s", cfg.DBHost, cfg.DBPort),
Username: cfg.DBUser,
Password: cfg.DBPass,
Addr: fmt.Sprintf("http://%s:%s", cfg.dbHost, cfg.dbPort),
Username: cfg.dbUser,
Password: cfg.dbPass,
}
return cfg, clientCfg
}
type channels struct {
List []string `toml:"filter"`
}
type chanConfig struct {
Channels channels `toml:"channels"`
}
func loadChansConfig(chanConfigPath string) map[string]bool {
data, err := ioutil.ReadFile(chanConfigPath)
if err != nil {
log.Fatal(err)
}
var chanCfg chanConfig
if err := toml.Unmarshal(data, &chanCfg); err != nil {
log.Fatal(err)
}
chans := map[string]bool{}
for _, ch := range chanCfg.Channels.List {
chans[ch] = true
}
return chans
}
func makeMetrics() (*kitprometheus.Counter, *kitprometheus.Summary) {
counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "influxdb",
@@ -170,5 +178,5 @@ func makeMetrics() (*kitprometheus.Counter, *kitprometheus.Summary) {
func startHTTPService(port string, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", port)
logger.Info(fmt.Sprintf("InfluxDB writer service started, exposed port %s", p))
errs <- http.ListenAndServe(p, influxdb.MakeHandler())
errs <- http.ListenAndServe(p, api.MakeHandler(svcName))
}
+62 -59
View File
@@ -1,3 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package main
import (
@@ -9,45 +12,45 @@ import (
"strconv"
"syscall"
mqtt "github.com/eclipse/paho.mqtt.golang"
mqttPaho "github.com/eclipse/paho.mqtt.golang"
r "github.com/go-redis/redis"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/lora"
"github.com/mainflux/mainflux/lora/api"
"github.com/mainflux/mainflux/lora/mqtt"
pub "github.com/mainflux/mainflux/lora/nats"
mqttBroker "github.com/mainflux/mainflux/lora/paho"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/mainflux/mainflux/lora/redis"
"github.com/nats-io/go-nats"
nats "github.com/nats-io/go-nats"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
const (
defHTTPPort = "8180"
defLoraMsgURL = "tcp://localhost:1883"
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defESURL = "localhost:6379"
defESPass = ""
defESDB = "0"
defInstanceName = "lora"
defRouteMapURL = "localhost:6379"
defRouteMapPass = ""
defRouteMapDB = "0"
defHTTPPort = "8180"
defLoraMsgURL = "tcp://localhost:1883"
defNatsURL = nats.DefaultURL
defLogLevel = "error"
defESURL = "localhost:6379"
defESPass = ""
defESDB = "0"
defESConsumerName = "lora"
defRouteMapURL = "localhost:6379"
defRouteMapPass = ""
defRouteMapDB = "0"
envHTTPPort = "MF_LORA_ADAPTER_HTTP_PORT"
envLoraMsgURL = "MF_LORA_ADAPTER_MESSAGES_URL"
envNatsURL = "MF_NATS_URL"
envLogLevel = "MF_LORA_ADAPTER_LOG_LEVEL"
envESURL = "MF_THINGS_ES_URL"
envESPass = "MF_THINGS_ES_PASS"
envESDB = "MF_THINGS_ES_DB"
envInstanceName = "MF_LORA_ADAPTER_INSTANCE_NAME"
envRouteMapURL = "MF_LORA_ADAPTER_ROUTEMAP_URL"
envRouteMapPass = "MF_LORA_ADAPTER_ROUTEMAP_PASS"
envRouteMapDB = "MF_LORA_ADAPTER_ROUTEMAP_DB"
envHTTPPort = "MF_LORA_ADAPTER_HTTP_PORT"
envLoraMsgURL = "MF_LORA_ADAPTER_MESSAGES_URL"
envNatsURL = "MF_NATS_URL"
envLogLevel = "MF_LORA_ADAPTER_LOG_LEVEL"
envESURL = "MF_THINGS_ES_URL"
envESPass = "MF_THINGS_ES_PASS"
envESDB = "MF_THINGS_ES_DB"
envESConsumerName = "MF_LORA_ADAPTER_EVENT_CONSUMER"
envRouteMapURL = "MF_LORA_ADAPTER_ROUTE_MAP_URL"
envRouteMapPass = "MF_LORA_ADAPTER_ROUTE_MAP_PASS"
envRouteMapDB = "MF_LORA_ADAPTER_ROUTE_MAP_DB"
loraServerTopic = "application/+/device/+/rx"
@@ -56,17 +59,17 @@ const (
)
type config struct {
httpPort string
loraMsgURL string
natsURL string
logLevel string
esURL string
esPass string
esDB string
instanceName string
routeMapURL string
routeMapPass string
routeMapDB string
httpPort string
loraMsgURL string
natsURL string
logLevel string
esURL string
esPass string
esDB string
esConsumerName string
routeMapURL string
routeMapPass string
routeMapDB string
}
func main() {
@@ -112,7 +115,7 @@ func main() {
)
go subscribeToLoRaBroker(svc, mqttConn, logger)
go subscribeToThingsES(svc, esConn, cfg.instanceName, logger)
go subscribeToThingsES(svc, esConn, cfg.esConsumerName, logger)
errs := make(chan error, 2)
@@ -130,17 +133,17 @@ func main() {
func loadConfig() config {
return config{
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
loraMsgURL: mainflux.Env(envLoraMsgURL, defLoraMsgURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
esURL: mainflux.Env(envESURL, defESURL),
esPass: mainflux.Env(envESPass, defESPass),
esDB: mainflux.Env(envESDB, defESDB),
instanceName: mainflux.Env(envInstanceName, defInstanceName),
routeMapURL: mainflux.Env(envRouteMapURL, defRouteMapURL),
routeMapPass: mainflux.Env(envRouteMapPass, defRouteMapPass),
routeMapDB: mainflux.Env(envRouteMapDB, defRouteMapDB),
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
loraMsgURL: mainflux.Env(envLoraMsgURL, defLoraMsgURL),
natsURL: mainflux.Env(envNatsURL, defNatsURL),
logLevel: mainflux.Env(envLogLevel, defLogLevel),
esURL: mainflux.Env(envESURL, defESURL),
esPass: mainflux.Env(envESPass, defESPass),
esDB: mainflux.Env(envESDB, defESDB),
esConsumerName: mainflux.Env(envESConsumerName, defESConsumerName),
routeMapURL: mainflux.Env(envRouteMapURL, defRouteMapURL),
routeMapPass: mainflux.Env(envRouteMapPass, defRouteMapPass),
routeMapDB: mainflux.Env(envRouteMapDB, defRouteMapDB),
}
}
@@ -155,20 +158,20 @@ func connectToNATS(url string, logger logger.Logger) *nats.Conn {
return conn
}
func connectToMQTTBroker(loraURL string, logger logger.Logger) mqtt.Client {
opts := mqtt.NewClientOptions()
func connectToMQTTBroker(loraURL string, logger logger.Logger) mqttPaho.Client {
opts := mqttPaho.NewClientOptions()
opts.AddBroker(loraURL)
opts.SetUsername("")
opts.SetPassword("")
opts.SetOnConnectHandler(func(c mqtt.Client) {
opts.SetOnConnectHandler(func(c mqttPaho.Client) {
logger.Info("Connected to Lora MQTT broker")
})
opts.SetConnectionLostHandler(func(c mqtt.Client, err error) {
opts.SetConnectionLostHandler(func(c mqttPaho.Client, err error) {
logger.Error(fmt.Sprintf("MQTT connection lost: %s", err.Error()))
os.Exit(1)
})
client := mqtt.NewClient(opts)
client := mqttPaho.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
logger.Error(fmt.Sprintf("Failed to connect to Lora MQTT broker: %s", token.Error()))
os.Exit(1)
@@ -191,10 +194,10 @@ func connectToRedis(redisURL, redisPass, redisDB string, logger logger.Logger) *
})
}
func subscribeToLoRaBroker(svc lora.Service, mc mqtt.Client, logger logger.Logger) {
mqttBroker := mqttBroker.NewBroker(svc, mc, logger)
func subscribeToLoRaBroker(svc lora.Service, mc mqttPaho.Client, logger logger.Logger) {
mqtt := mqtt.NewBroker(svc, mc, logger)
logger.Info("Subscribed to Lora MQTT broker")
if err := mqttBroker.Subscribe(loraServerTopic); err != nil {
if err := mqtt.Subscribe(loraServerTopic); err != nil {
logger.Error(fmt.Sprintf("Failed to subscribe to Lora MQTT broker: %s", err))
os.Exit(1)
}
@@ -204,17 +207,17 @@ func subscribeToThingsES(svc lora.Service, client *r.Client, consumer string, lo
eventStore := redis.NewEventStore(svc, client, consumer, logger)
logger.Info("Subscribed to Redis Event Store")
if err := eventStore.Subscribe("mainflux.things"); err != nil {
logger.Warn(fmt.Sprintf("Lora-adapter service failed to subscribe to event sourcing: %s", err))
logger.Warn(fmt.Sprintf("Lora-adapter service failed to subscribe to Redis event source: %s", err))
}
}
func newRouteMapRepositoy(client *r.Client, prefix string, logger logger.Logger) lora.RouteMapRepository {
logger.Info("Connected to Redis Route map")
logger.Info(fmt.Sprintf("Connected to %s Redis Route-map", prefix))
return redis.NewRouteMapRepository(client, prefix)
}
func startHTTPServer(cfg config, logger logger.Logger, errs chan error) {
p := fmt.Sprintf(":%s", cfg.httpPort)
logger.Info(fmt.Sprintf("Lora-adapter service started, exposed port %s", cfg.httpPort))
logger.Info(fmt.Sprintf("lora-adapter service started, exposed port %s", cfg.httpPort))
errs <- http.ListenAndServe(p, api.MakeHandler())
}

Some files were not shown because too many files have changed in this diff Show More