Rokwire Building Block SDK for Go
rokwire-building-block-sdk-go is the official Rokwire Building Block SDK for Golang. This SDK enables easy communication with the Rokwire Building Blocks and provides commonly used utilities.
Prerequisites
Installation
To install this package or upgrade to the latest version, use go get
:
go get -u github.com/rokwire/rokwire-building-block-sdk-go
Upgrading
Staying up to date
To update rokwire-building-block-sdk-go to the latest version, use go get -u github.com/rokwire/rokwire-building-block-sdk-go
.
Migration steps
Follow the steps below to upgrade to the associated version of this library. Note that the steps for each version are cumulative, so if you are attempting to upgrade by several versions, be sure to make the changes described for each version between your current version and the latest.
Unreleased
Breaking changes
web
Adapter[T common.Storage].RegisterHandlerFunc
has been renamed to Adapter[T common.Storage].RegisterGeneratedHandlerFunc
.
Breaking changes
keys
PrivKey.Sign
now takes message string
as an argument instead of message []byte
.
PubKey.Verify
now takes message string
as an argument instead of message []byte
.
sigauth
SignatureAuth.Sign
now takes message string
as an argument instead of message []byte
.
SignatureAuth.CheckServiceSignature
now takes message string
as an argument instead of message []byte
.
SignatureAuth.CheckSignature
now takes message string
as an argument instead of message []byte
.
SignatureAuth.LegacyCheckSignature
now takes message string
as an argument instead of message []byte
.
tokenauth
Claims
now uses jwt.RegisteredClaims
(github.com/golang-jwt/jwt/v5) as an embedded struct instead of jwt.StandardClaims
(github.com/golang-jwt/jwt v3).
Packages
This library contains several packages:
auth
The auth
package provides the Service
type which contains the configurations to locate and communicate with the ROKWIRE Auth Service. The other packages in this library depend on the Service
object, or other objects which depend on it, to handle any necessary communication with this central Auth Service.
This package also provides the ServiceRegLoader
, ServiceRegManager
, ServiceAccountLoader
, and ServiceAccountManager
types.
The ServiceRegManager
type uses the configuration defined in an Service
instance and a ServiceRegLoader
instance to load, store, and manage service registration data (ServiceReg
type).
The ServiceAccountManager
type uses the configuration defined in an Service
and a ServiceAccountLoader
instance to load, storage, and manage service account data (e.g., access tokens, with the AccessToken
type).
Import the rokwire-building-block-sdk-go/services/core/auth
package into your code using this template:
package yours
import (
...
"github.com/rokwire/rokwire-building-block-sdk-go/services/core/auth"
)
func main() {
// Instantiate an auth.Service to maintain basic auth data
authService := auth.Service{
ServiceID: "sample",
ServiceHost: "https://rokwire.illinois.edu/sample",
FirstParty: true,
AuthBaseURL: "https://rokwire.illinois.edu/auth",
}
// Instantiate a remote ServiceRegLoader to load auth service registration record from auth service
serviceRegLoader, err := auth.NewRemoteServiceRegLoader(&authService, []string{"auth"})
if err != nil {
log.Fatalf("Error initializing remote service registration loader: %v", err)
}
// Instantiate a ServiceRegManager to manage the service registration data loaded by serviceRegLoader
serviceRegManager, err := auth.NewServiceRegManager(&authService, serviceRegLoader)
if err != nil {
log.Fatalf("Error initializing service registration manager: %v", err)
}
// Instantiate a remote ServiceAccountLoader to load auth service account data from auth service
staticTokenAuth := auth.StaticTokenServiceAuth{ServiceToken: "sampleToken"}
serviceAccountLoader, err := auth.NewRemoteServiceAccountLoader(&authService, "sampleAccountID", staticTokenAuth)
if err != nil {
log.Fatalf("Error initializing remote service account loader: %v", err)
}
// Instantiate a remote ServiceAccountManager to manage service account-related data
serviceAccountManager, err := auth.NewServiceAccountManager(&authService, serviceAccountLoader)
if err != nil {
log.Fatalf("Error initializing service account manager: %v", err)
}
...
}
core
The core
package provides the Service
type which contains the configurations and helper functions to utilize certain functions implemented by the ROKWIRE Core Building Block. One example of these functions is getting the IDs of accounts deleted within a set amount of time ago.
tokenauth
The tokenauth
package provides the TokenAuth
type which exposes the interface to validate and authorize auth tokens generated by the ROKWIRE Auth Service.
webauth
The webauth
package provides the utility functions that are useful when handling web applications. This includes setting cookies and verifying both cookies and headers to secure these web applications.
sigauth
The sigauth
package provides the SignatureAuth
type which exposes the interface to sign and verify HTTP requests to communicate securely between services within the ROKWIRE ecosystem.
authorization
The authorization
package provides a generic Authorization
interface and a specific CasbinAuthorization
and CasbinScopeAuthorization
implementation of this interface that can be used with the TokenAuth
object. There are two standard Casbin models that can be found in authorization/authorization_model_string.conf
and authorization/authorization_model_scope.conf
that can be used with each of these types respectively. You can also define your own model if neither of these fits the use case.
envloader
The envloader
package provides the EnvLoader
interface which facilitates the loading of environment variables from various environments. Two standard implementations have been provided: LocalEnvLoader
and AWSSecretsManagerEnvLoader
. The LocalEnvLoader
loads all variables from the environment variables set on the local machine, while the AWSSecretsManagerEnvLoader
will load them from an AWS SecretsManager Secret.
AWSSecretsManagerEnvLoader
When using the AWSSecretsManagerEnvLoader
, two environment variables must be set on the local machine to configure the specific secret to be accessed. The underlying infrastructure must also have the appropriate AWS permissions/roles to access the specied secret.
Environment Variables:
Name |
Description |
APP_SECRET_ARN |
The AWS ARN of the AWS SecretsManager Secret to be accessed |
AWS_REGION |
The AWS region of the AWS SecretsManager Secret to be accessed |
The NewEnvLoader()
function can be used to automatically select and create the correct EnvLoader
implementation object. If the two environment variables mentioned above are set, an AWSSecretsManagerEnvLoader
will be returned, otherwise a LocalEnvLoader
will be returned.
rokwireutils
The rokwireutils
package contains constants and standard utilities shared by the other packages.
keys
The keys
package contains constants and generalized public key and private key wrapper types that are used by other packaages.
logs
The logs
package provides the Logger
and Log
types. The Logger
object provides the base configurations for the entire application, while the Log
object carries state related to a specific request.
Import the rokwire-building-block-sdk-go/utils/logging/logs
package into your code using this template:
package yours
import (
...
"github.com/rokwire/rokwire-building-block-sdk-go/utils/logging/logs"
)
func main() {
var logger = logs.NewLogger("example", nil)
logger.SetLevel(logs.Debug)
...
}
errors
The errors
package provides the Error
type which expands upon the functionality provided by the typical error
primitive provided by Golang. For example, additional context such as a trace of wrapped errors is automatically maintained when using the Wrap
functions. Various components of this chain can then be accessed through the convenience functions provided by this package.
logutils
The logutils
package contains constants and standard utilities shared by the logs
and errors
packages.
Error Wrappers
There are several convenience functions to help standardize the error generation process.
//NewError returns an error containing the provided message
func NewError(message string) error
//NewErrorf returns an error containing the formatted message
func NewErrorf(message string, args ...interface{}) error
//WrapErrorf returns an error containing the provided message and error
func WrapError(message string, err error) error
//WrapErrorf returns an error containing the formatted message and provided error
func WrapErrorf(format string, err error, args ...interface{}) error
These functions should be used in place of fmt.Errorf
and errors.New
. They provide several key benefits.
- Consistent formatting: When using these functions, the provided messages will be formatted in one standard format. This will make it easier to read and follow logs throughout and across services.
- Context: These functions will all automatically include information about the function that is generating the error to help trace the path of the call when the errors are logged at a higher level.
- Convenience: This provides on central package that can be imported to create errors. It also provides convenience functions to wrap existing errors with more context which should be a common practice with our logging approach.
Logging Helpers
There are several convenience functions that will help perform logging in common situations.
The LogError
function can be used to log a message along with an existing error
object.
//LogError prints the log at error level with given message and error
// Returns combined error message as string
func (l *Log) LogError(message string, err error) string
The following functions manage logging, generating, and sending HTTP responses conveniently.
// SendHTTPResponse finalizes response data and sends the content of an HTTPResponse to the provided http.ResponseWriter
func (l *Log) SendHTTPResponse(w http.ResponseWriter, response HTTPResponse)
// HTTPResponseSuccess generates an HTTPResponse with the message "Success" with status code 200, sets standard headers, and stores the status to the log context
func (l *Log) HTTPResponseSuccess() HTTPResponse
// HTTPResponseSuccess generates an HTTPResponse with the provided success message with status code 200, sets standard headers, and stores the message and status to the log context
func (l *Log) HTTPResponseSuccessMessage(message string) HTTPResponse
// HTTPResponseSuccess generates an HTTPResponse with the provided success message and status code, sets standard headers, and stores the message and status to the log context
func (l *Log) HTTPResponseSuccessStatusMessage(message string, code int) HTTPResponse
// HTTPResponseSuccessJSON generates an HTTPResponse with the provided JSON as the HTTP response body with status code 200, sets standard headers,
// and stores the status to the log context
func (l *Log) HTTPResponseSuccessJSON(json []byte) HTTPResponse
// HTTPResponseSuccessStatusJSON generates an HTTPResponse with the provided JSON as the HTTP response body and status code, sets standard headers,
// and stores the status to the log context
func (l *Log) HTTPResponseSuccessStatusJSON(json []byte, code int) HTTPResponse
// HTTPResponseSuccessBytes generates an HTTPResponse with the provided bytes as the HTTP response body with status code 200,
// sets standard headers, and stores the status to the log context
func (l *Log) HTTPResponseSuccessBytes(bytes []byte, contentType string) HTTPResponse
// HTTPResponseSuccessBytes generates an HTTPResponse with the provided bytes as the HTTP response body and status code,
// sets standard headers, and stores the status to the log context
func (l *Log) HTTPResponseSuccessStatusBytes(bytes []byte, contentType string, code int) HTTPResponse
// HTTPResponseError logs the provided message and error and generates an HTTPResponse
func (l *Log) HTTPResponseError(message string, err error, code int, showDetails bool) HTTPResponse
Message Templates
This library includes two standardized templates/grammars for messages, as well as a dictionary of commonly used terms. The intention of providing this is to help keep the logs very consistent and easy to interpret when adding new functionality with new logs.
Data Template
The "data" message template can be used to describe common statuses of a specified data element.
Pattern: {data status} {type}: {args}
Example: Invalid query param: id=test_id
Action Template
The "action" message template can be used to describe common actions performed on a specified data type.
Pattern: {action status} {action} {type} for {args}
Example: Error marshalling organization for id=test_id
Message Template Parameters
Below are definitions and examples for the template parameters references above.
Data Status
Data statuses describe the data element and are represented by the logDataStatus
type.
StatusValid
("Valid"), StatusFound
("Found"), StatusInvalid
("Invalid"), MissingStatus
("Missing")
Action Status
Action statuses describe the the action and are represented by the logActionStatus
type.
StatusSuccess
("Success"), StatusError
("Error")
Action
Actions represent the action taken on the data element and are represented by the LogAction
type.
- Eg.
ActionFind
("finding"), ActionMarshal
("marshalling"), ActionInitialize
("initializing"), ActionSend
("sending")... etc.
Many common actions are defined in the logging library and these should be used when possible to maintain standardization. If you cannot construct an accurate message with the provided defined actions, you may provide your own action verb (ending in -ing) to describe the situation.
Type
Types are representations of the data type that the status applies to represented by the LogData
type.
- Eg.
TypeQueryParam
("query param"), TypeRequest
("request"), "organization", "user"... etc
There are several common types that will be reused across applications defined in the logging library, however each application should define types to represent various models specific to its context.
Args
Args are arbitrary parameters which can be included to provide additional information about the data or action represented by the logArgs
interface. There are three types of logArgs
: FieldArgs
(map[string]string
), ListArgs
([]string
), and StringArgs
(string
). Most commonly, these will be variable name and value pairs (FieldArgs
).
- Eg.
FieldArgs{"id": "test_id", "name": "test_name"}
, ListArgs{"id", "name"}
, StringArgs("id")
... etc
Message Template Helper Functions
There are several convenience functions to help log or create an error from these templates.
Note: nil
"args" params are ok
Messages:
// MessageData generates a message string for a data element
func MessageData(status MessageDataStatus, dataType MessageDataType, args MessageArgs) string
// MessageAction generates a message string for an action
func MessageAction(status MessageActionStatus, action MessageActionType, dataType MessageDataType, args MessageArgs) string
// MessageActionError generates a message string for an action that resulted in an error
func MessageActionError(action MessageActionType, dataType MessageDataType, args MessageArgs) string
// MessageActionSuccess generates a message string for an action that succeeded
func MessageActionSuccess(action MessageActionType, dataType MessageDataType, args MessageArgs) string
Errors:
//DataError generates an error for a data element
func DataError(status logDataStatus, dataType LogData, args logArgs) error
//WrapDataError wraps an error for a data element
func WrapDataError(status logDataStatus, dataType LogData, args logArgs, err error) error
//ActionError generates an error for an action
func ActionError(action LogAction, dataType LogData, args logArgs) error
//WrapActionError wraps an error for an action
func WrapActionError(action LogAction, dataType LogData, args logArgs, err error) error
Responses:
// HTTPResponseSuccessAction generates an HTTPResponse with the provided success action message, sets standard headers, and stores the message to the log context with status code 200
func (l *Log) HTTPResponseSuccessAction(action logutils.MessageActionType, dataType logutils.MessageDataType, args logutils.MessageArgs) HTTPResponse
// HTTPResponseSuccessStatusAction generates an HTTPResponse with the provided success action message and status code, sets standard headers, and stores the message to the log context
func (l *Log) HTTPResponseSuccessStatusAction(action logutils.MessageActionType, dataType logutils.MessageDataType, args logutils.MessageArgs, code int) HTTPResponse
// HTTPResponseErrorAction logs an action message and error and generates an HTTPResponse
func (l *Log) HTTPResponseErrorAction(action logutils.MessageActionType, dataType logutils.MessageDataType, args logutils.MessageArgs, err error, code int, showDetails bool) HTTPResponse
// HTTPResponseErrorData logs a data message and error and generates an HTTPResponse
func (l *Log) HTTPResponseErrorData(status logutils.MessageDataStatus, dataType logutils.MessageDataType, args logutils.MessageArgs, err error, code int, showDetails bool) HTTPResponse
Other Conventions
There are several recommended conventions for the use of this library:
Internal functions do not write logs unless necessary
Internal functions (core, storage, auth...etc) should not log to the console in general. They should instead return an error to be logged at the API handler level. Using the error wrapping functions in this libarary will make sure that the relevant context is not lost along the way.
Exceptions to this rule include warnings, where it is important that a log is generated indicating that an error occurred, but it is not a critical error which prevented successful execution. When this happens the error should not be returned, so a Warn
function should be called on the Log
object with the relevant information. Debug logging statements are also an exception here, for example printing the contents of an object at a specific point to keep a record in the dev environment. Finally, on some occasions, Info
logs can be printed in core functions to indicate that a specific action occurred... etc.
Use the Log object whenever possible
When it is necessary to write to the logs, the Log
object should be used over the Logger
object (or any other logging library/package) whenever possible. Log
objects contain additional information and ensure that any printed logs are properly associated with the request being handled. They also allow you to store context to be logged upon the success or failure of the request.
For example, if a non-critical issue occurs in a storage function and we want to log a warning without returning an error, the storage function should include a *log.Log
in the function params. No Logger
object should be stored and made available to internal functions outside of the initialization context.
Exceptions to this rule include initialization when there is no request being processed and therefore no Log
object. Timer based functions are also in this category. In these cases the Logger
should be used instead.
Errors should almost always be wrapped at every level
When an error is received from a function call and returned, one of the error wrapping helpers should be used to provide additional context in a message. This also will ensure that the chain of function calls is preserved within the Error
object.
To get started, take a look at example/app.go
Usage
To get started, take a look at the example/
directory.
Contributing
If you would like to contribute to this project, please be sure to read the Contributing Guidelines, Code of Conduct, and Conventions before beginning.
Secret Detection
This repository is configured with a pre-commit hook that runs Yelp's Detect Secrets. If you intend to contribute directly to this repository, you must install pre-commit on your local machine to ensure that no secrets are pushed accidentally.
# Install software
$ git pull # Pull in pre-commit configuration & baseline
$ pip install pre-commit
$ pre-commit install