mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
NOISSUE - Update bootstrap and provision service (#3476)
Property Based Tests / api-test (push) Has been cancelled
Continuous Delivery / lint-and-build (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled
Property Based Tests / api-test (push) Has been cancelled
Continuous Delivery / lint-and-build (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> Signed-off-by: JeffMboya <jangina.mboya@gmail.com> Co-authored-by: JeffMboya <jangina.mboya@gmail.com>
This commit is contained in:
@@ -75,6 +75,13 @@ jobs:
|
|||||||
- "pkg/ulid/**"
|
- "pkg/ulid/**"
|
||||||
- "pkg/uuid/**"
|
- "pkg/uuid/**"
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
- "bootstrap/**"
|
||||||
|
- "cmd/bootstrap/**"
|
||||||
|
- "pkg/bootstrap/**"
|
||||||
|
- "provision/**"
|
||||||
|
- "pkg/sdk/**"
|
||||||
|
|
||||||
channels:
|
channels:
|
||||||
- "channels/**"
|
- "channels/**"
|
||||||
- "cmd/channels/**"
|
- "cmd/channels/**"
|
||||||
@@ -233,10 +240,11 @@ jobs:
|
|||||||
|
|
||||||
if [[ "${{ steps.changes.outputs.workflow }}" == "true" || "${{ steps.changes.outputs.pkg-errors }}" == "true" ]]; then
|
if [[ "${{ steps.changes.outputs.workflow }}" == "true" || "${{ steps.changes.outputs.pkg-errors }}" == "true" ]]; then
|
||||||
# If workflow or pkg/errors changed, test everything
|
# If workflow or pkg/errors changed, test everything
|
||||||
modules=("auth" "channels" "cli" "clients" "domains" "groups" "internal" "journal" "logger" "pkg-errors" "pkg-events" "pkg-grpcclient" "pkg-messaging" "pkg-sdk" "pkg-transformers" "pkg-ulid" "pkg-uuid" "users" "notifications" "api" "consumers" "readers" "re" "alarms" "reports")
|
modules=("auth" "bootstrap" "channels" "cli" "clients" "domains" "groups" "internal" "journal" "logger" "pkg-errors" "pkg-events" "pkg-grpcclient" "pkg-messaging" "pkg-sdk" "pkg-transformers" "pkg-ulid" "pkg-uuid" "users" "notifications" "api" "consumers" "readers" "re" "alarms" "reports")
|
||||||
else
|
else
|
||||||
# Add only changed modules
|
# Add only changed modules
|
||||||
[[ "${{ steps.changes.outputs.auth }}" == "true" ]] && modules+=("auth")
|
[[ "${{ steps.changes.outputs.auth }}" == "true" ]] && modules+=("auth")
|
||||||
|
[[ "${{ steps.changes.outputs.bootstrap }}" == "true" ]] && modules+=("bootstrap")
|
||||||
[[ "${{ steps.changes.outputs.channels }}" == "true" ]] && modules+=("channels")
|
[[ "${{ steps.changes.outputs.channels }}" == "true" ]] && modules+=("channels")
|
||||||
[[ "${{ steps.changes.outputs.cli }}" == "true" ]] && modules+=("cli")
|
[[ "${{ steps.changes.outputs.cli }}" == "true" ]] && modules+=("cli")
|
||||||
[[ "${{ steps.changes.outputs.clients }}" == "true" ]] && modules+=("clients")
|
[[ "${{ steps.changes.outputs.clients }}" == "true" ]] && modules+=("clients")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
override MG_DOCKER_IMAGE_NAME_PREFIX := ghcr.io/absmach/magistrala
|
override MG_DOCKER_IMAGE_NAME_PREFIX := ghcr.io/absmach/magistrala
|
||||||
MG_DOCKER_VOLUME_NAME_PREFIX ?= magistrala
|
MG_DOCKER_VOLUME_NAME_PREFIX ?= magistrala
|
||||||
BUILD_DIR ?= build
|
BUILD_DIR ?= build
|
||||||
SERVICES = auth users clients groups channels domains notifications certs re postgres-writer postgres-reader timescale-writer timescale-reader cli alarms reports bootstrap journal fluxmq
|
SERVICES = auth users clients groups channels domains notifications certs re postgres-writer postgres-reader timescale-writer timescale-reader cli alarms reports bootstrap provision journal fluxmq
|
||||||
TEST_API_SERVICES = journal auth certs clients users channels groups domains
|
TEST_API_SERVICES = journal auth certs clients users channels groups domains
|
||||||
TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES))
|
TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES))
|
||||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||||
|
|||||||
+745
-293
File diff suppressed because it is too large
Load Diff
@@ -94,6 +94,9 @@ func (client authGrpcClient) Authorize(ctx context.Context, req *grpcAuthV1.Auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
if patReq != nil {
|
if patReq != nil {
|
||||||
|
if patReq.GetDomain() != "" {
|
||||||
|
authReqData.Domain = patReq.GetDomain()
|
||||||
|
}
|
||||||
authReqData.UserID = patReq.GetUserId()
|
authReqData.UserID = patReq.GetUserId()
|
||||||
authReqData.PatID = patReq.GetPatId()
|
authReqData.PatID = patReq.GetPatId()
|
||||||
authReqData.EntityType = patReq.GetEntityType()
|
authReqData.EntityType = patReq.GetEntityType()
|
||||||
|
|||||||
@@ -131,6 +131,8 @@ func TestAuthorize(t *testing.T) {
|
|||||||
token string
|
token string
|
||||||
authRequest *grpcAuthV1.AuthZReq
|
authRequest *grpcAuthV1.AuthZReq
|
||||||
authResponse *grpcAuthV1.AuthZRes
|
authResponse *grpcAuthV1.AuthZRes
|
||||||
|
expectedReq *policies.Policy
|
||||||
|
expectedPAT *auth.PATAuthz
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -270,6 +272,47 @@ func TestAuthorize(t *testing.T) {
|
|||||||
authResponse: &grpcAuthV1.AuthZRes{Authorized: true},
|
authResponse: &grpcAuthV1.AuthZRes{Authorized: true},
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "authorize bootstrap PAT keeps PAT domain when policy domain is empty",
|
||||||
|
token: validPATToken,
|
||||||
|
authRequest: &grpcAuthV1.AuthZReq{
|
||||||
|
PolicyReq: &grpcAuthV1.PolicyReq{
|
||||||
|
Subject: id,
|
||||||
|
SubjectType: policies.UserType,
|
||||||
|
SubjectKind: policies.UsersKind,
|
||||||
|
Permission: policies.MembershipPermission,
|
||||||
|
ObjectType: policies.DomainType,
|
||||||
|
Object: domainID,
|
||||||
|
},
|
||||||
|
PatReq: &grpcAuthV1.PATReq{
|
||||||
|
PatId: id,
|
||||||
|
Domain: domainID,
|
||||||
|
Operation: "create",
|
||||||
|
UserId: id,
|
||||||
|
EntityId: auth.AnyIDs,
|
||||||
|
EntityType: auth.BootstrapStr,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authResponse: &grpcAuthV1.AuthZRes{Authorized: true},
|
||||||
|
expectedReq: &policies.Policy{
|
||||||
|
Domain: domainID,
|
||||||
|
SubjectType: policies.UserType,
|
||||||
|
SubjectKind: policies.UsersKind,
|
||||||
|
Subject: id,
|
||||||
|
Permission: policies.MembershipPermission,
|
||||||
|
ObjectType: policies.DomainType,
|
||||||
|
Object: domainID,
|
||||||
|
},
|
||||||
|
expectedPAT: &auth.PATAuthz{
|
||||||
|
PatID: id,
|
||||||
|
UserID: id,
|
||||||
|
EntityType: auth.BootstrapType,
|
||||||
|
EntityID: auth.AnyIDs,
|
||||||
|
Operation: "create",
|
||||||
|
Domain: domainID,
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "authorize user with unauthorized PAT token",
|
desc: "authorize user with unauthorized PAT token",
|
||||||
token: inValidToken,
|
token: inValidToken,
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ func decodeAuthorizeRequest(_ context.Context, grpcReq any) (any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if patReq != nil {
|
if patReq != nil {
|
||||||
|
if patReq.GetDomain() != "" {
|
||||||
|
authRequest.Domain = patReq.GetDomain()
|
||||||
|
}
|
||||||
authRequest.UserID = patReq.GetUserId()
|
authRequest.UserID = patReq.GetUserId()
|
||||||
authRequest.PatID = patReq.GetPatId()
|
authRequest.PatID = patReq.GetPatId()
|
||||||
authRequest.EntityType = patReq.GetEntityType()
|
authRequest.EntityType = patReq.GetEntityType()
|
||||||
|
|||||||
+7
-1
@@ -76,6 +76,7 @@ const (
|
|||||||
GroupsType EntityType = iota
|
GroupsType EntityType = iota
|
||||||
ChannelsType
|
ChannelsType
|
||||||
ClientsType
|
ClientsType
|
||||||
|
BootstrapType
|
||||||
DashboardType
|
DashboardType
|
||||||
MessagesType
|
MessagesType
|
||||||
DomainsType
|
DomainsType
|
||||||
@@ -88,6 +89,7 @@ const (
|
|||||||
GroupsScopeStr = "groups"
|
GroupsScopeStr = "groups"
|
||||||
ChannelsScopeStr = "channels"
|
ChannelsScopeStr = "channels"
|
||||||
ClientsScopeStr = "clients"
|
ClientsScopeStr = "clients"
|
||||||
|
BootstrapStr = "bootstrap"
|
||||||
DashboardsStr = "dashboards"
|
DashboardsStr = "dashboards"
|
||||||
MessagesStr = "messages"
|
MessagesStr = "messages"
|
||||||
DomainsStr = "domains"
|
DomainsStr = "domains"
|
||||||
@@ -104,6 +106,8 @@ func (et EntityType) String() string {
|
|||||||
return ChannelsScopeStr
|
return ChannelsScopeStr
|
||||||
case ClientsType:
|
case ClientsType:
|
||||||
return ClientsScopeStr
|
return ClientsScopeStr
|
||||||
|
case BootstrapType:
|
||||||
|
return BootstrapStr
|
||||||
case DashboardType:
|
case DashboardType:
|
||||||
return DashboardsStr
|
return DashboardsStr
|
||||||
case MessagesType:
|
case MessagesType:
|
||||||
@@ -129,6 +133,8 @@ func ParseEntityType(et string) (EntityType, error) {
|
|||||||
return ChannelsType, nil
|
return ChannelsType, nil
|
||||||
case ClientsScopeStr:
|
case ClientsScopeStr:
|
||||||
return ClientsType, nil
|
return ClientsType, nil
|
||||||
|
case BootstrapStr:
|
||||||
|
return BootstrapType, nil
|
||||||
case DashboardsStr:
|
case DashboardsStr:
|
||||||
return DashboardType, nil
|
return DashboardType, nil
|
||||||
case MessagesStr:
|
case MessagesStr:
|
||||||
@@ -169,7 +175,7 @@ func (et *EntityType) UnmarshalText(data []byte) (err error) {
|
|||||||
|
|
||||||
func IsValidOperationForEntity(entityType EntityType, operation string) bool {
|
func IsValidOperationForEntity(entityType EntityType, operation string) bool {
|
||||||
switch entityType {
|
switch entityType {
|
||||||
case ClientsType, ChannelsType, GroupsType, DomainsType, RulesType, ReportsType:
|
case ClientsType, ChannelsType, GroupsType, BootstrapType, DomainsType, RulesType, ReportsType:
|
||||||
return true
|
return true
|
||||||
case DashboardType:
|
case DashboardType:
|
||||||
return operation == OpDashboardShare || operation == OpDashboardUnshare
|
return operation == OpDashboardShare || operation == OpDashboardUnshare
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ func TestEntityTypeString(t *testing.T) {
|
|||||||
et: auth.ClientsType,
|
et: auth.ClientsType,
|
||||||
expected: "clients",
|
expected: "clients",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Bootstrap entity type",
|
||||||
|
et: auth.BootstrapType,
|
||||||
|
expected: "bootstrap",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Dashboard entity type",
|
desc: "Dashboard entity type",
|
||||||
et: auth.DashboardType,
|
et: auth.DashboardType,
|
||||||
@@ -91,6 +96,12 @@ func TestParseEntityType(t *testing.T) {
|
|||||||
expected: auth.ClientsType,
|
expected: auth.ClientsType,
|
||||||
err: false,
|
err: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Parse bootstrap",
|
||||||
|
et: "bootstrap",
|
||||||
|
expected: auth.BootstrapType,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Parse dashboards",
|
desc: "Parse dashboards",
|
||||||
et: "dashboards",
|
et: "dashboards",
|
||||||
@@ -155,6 +166,12 @@ func TestEntityTypeMarshalJSON(t *testing.T) {
|
|||||||
expected: []byte(`"clients"`),
|
expected: []byte(`"clients"`),
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Marshal bootstrap",
|
||||||
|
et: auth.BootstrapType,
|
||||||
|
expected: []byte(`"bootstrap"`),
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Marshal rules",
|
desc: "Marshal rules",
|
||||||
et: auth.RulesType,
|
et: auth.RulesType,
|
||||||
@@ -197,6 +214,12 @@ func TestEntityTypeUnmarshalJSON(t *testing.T) {
|
|||||||
expected: auth.ChannelsType,
|
expected: auth.ChannelsType,
|
||||||
err: false,
|
err: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Unmarshal bootstrap",
|
||||||
|
data: []byte(`"bootstrap"`),
|
||||||
|
expected: auth.BootstrapType,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Unmarshal rules",
|
desc: "Unmarshal rules",
|
||||||
data: []byte(`"rules"`),
|
data: []byte(`"rules"`),
|
||||||
@@ -250,6 +273,12 @@ func TestEntityTypeMarshalText(t *testing.T) {
|
|||||||
expected: []byte("channels"),
|
expected: []byte("channels"),
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Marshal bootstrap as text",
|
||||||
|
et: auth.BootstrapType,
|
||||||
|
expected: []byte("bootstrap"),
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@@ -280,6 +309,12 @@ func TestEntityTypeUnmarshalText(t *testing.T) {
|
|||||||
expected: auth.ChannelsType,
|
expected: auth.ChannelsType,
|
||||||
err: false,
|
err: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Unmarshal bootstrap from text",
|
||||||
|
data: []byte("bootstrap"),
|
||||||
|
expected: auth.BootstrapType,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Unmarshal unknown from text",
|
desc: "Unmarshal unknown from text",
|
||||||
data: []byte("unknown"),
|
data: []byte("unknown"),
|
||||||
|
|||||||
+8
-8
@@ -4,9 +4,9 @@ New devices need to be configured properly and connected to the Magistrala. Boot
|
|||||||
|
|
||||||
1. Creating new Magistrala Clients
|
1. Creating new Magistrala Clients
|
||||||
2. Providing basic configuration for the newly created Clients
|
2. Providing basic configuration for the newly created Clients
|
||||||
3. Enabling/disabling Clients
|
3. Enabling/disabling bootstrap enrollments
|
||||||
|
|
||||||
Pre-provisioning a new Client is as simple as sending Configuration data to the Bootstrap service. Once the Client is online, it sends a request for initial config to Bootstrap service. Bootstrap service provides an API for enabling and disabling Clients. Only enabled Clients can exchange messages over Magistrala. Bootstrapping does not implicitly enable Clients, it has to be done manually.
|
Pre-provisioning a new Client is as simple as sending Configuration data to the Bootstrap service. Once the Client is online, it sends a request for initial config to Bootstrap service. Bootstrap service provides an API for enabling and disabling bootstrap enrollments. Bootstrapping does not implicitly enable an enrollment; it has to be done manually.
|
||||||
|
|
||||||
In order to bootstrap successfully, the Client 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 Client is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Client will be saved so that it can be provisioned later.
|
In order to bootstrap successfully, the Client 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 Client is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Client will be saved so that it can be provisioned later.
|
||||||
|
|
||||||
@@ -20,14 +20,14 @@ Client Configuration consists of two logical parts: the custom configuration tha
|
|||||||
|
|
||||||
> Note: list of channels contains IDs of the Magistrala channels. These channels are _pre-provisioned_ on the Magistrala side and, unlike corresponding Magistrala Client, Bootstrap service is not able to create Magistrala Channels.
|
> Note: list of channels contains IDs of the Magistrala channels. These channels are _pre-provisioned_ on the Magistrala side and, unlike corresponding Magistrala Client, Bootstrap service is not able to create Magistrala Channels.
|
||||||
|
|
||||||
Enabling and disabling Client (adding Client to/from whitelist) is as simple as connecting corresponding Magistrala Client to the given list of Channels. Configuration keeps _state_ of the Client:
|
Enabling and disabling a bootstrap enrollment is an enrollment toggle. Configuration keeps a _status_:
|
||||||
|
|
||||||
| State | What it means |
|
| Status | What it means |
|
||||||
| -------- | ---------------------------------------------- |
|
| -------- | ----------------------------------------------------------- |
|
||||||
| Inactive | Client is created, but isn't enabled |
|
| disabled | Enrollment exists, but bootstrap is not allowed |
|
||||||
| Active | Client is able to communicate using Magistrala |
|
| enabled | Enrollment can be used to fetch bootstrap configuration |
|
||||||
|
|
||||||
Switching between states `Active` and `Inactive` enables and disables Client, respectively.
|
Switching between statuses `enabled` and `disabled` enables and disables the enrollment, respectively.
|
||||||
|
|
||||||
Client configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Client. 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.
|
Client configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Client. 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.
|
||||||
|
|
||||||
|
|||||||
+291
-90
@@ -26,21 +26,16 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
|||||||
return nil, svcerr.ErrAuthorization
|
return nil, svcerr.ErrAuthorization
|
||||||
}
|
}
|
||||||
|
|
||||||
channels := []bootstrap.Channel{}
|
|
||||||
for _, c := range req.Channels {
|
|
||||||
channels = append(channels, bootstrap.Channel{ID: c})
|
|
||||||
}
|
|
||||||
|
|
||||||
config := bootstrap.Config{
|
config := bootstrap.Config{
|
||||||
ClientID: req.ClientID,
|
ExternalID: req.ExternalID,
|
||||||
ExternalID: req.ExternalID,
|
ExternalKey: req.ExternalKey,
|
||||||
ExternalKey: req.ExternalKey,
|
Name: req.Name,
|
||||||
Channels: channels,
|
ClientCert: req.ClientCert,
|
||||||
Name: req.Name,
|
ClientKey: req.ClientKey,
|
||||||
ClientCert: req.ClientCert,
|
CACert: req.CACert,
|
||||||
ClientKey: req.ClientKey,
|
Content: req.Content,
|
||||||
CACert: req.CACert,
|
ProfileID: req.ProfileID,
|
||||||
Content: req.Content,
|
RenderContext: req.RenderContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
saved, err := svc.Add(ctx, session, req.token, config)
|
saved, err := svc.Add(ctx, session, req.token, config)
|
||||||
@@ -49,8 +44,17 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := configRes{
|
res := configRes{
|
||||||
id: saved.ClientID,
|
ID: saved.ID,
|
||||||
created: true,
|
ExternalID: saved.ExternalID,
|
||||||
|
Name: saved.Name,
|
||||||
|
Content: saved.Content,
|
||||||
|
Status: saved.Status,
|
||||||
|
ProfileID: saved.ProfileID,
|
||||||
|
RenderContext: saved.RenderContext,
|
||||||
|
ClientCert: saved.ClientCert,
|
||||||
|
CACert: saved.CACert,
|
||||||
|
ClientKey: saved.ClientKey,
|
||||||
|
created: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
@@ -69,13 +73,13 @@ func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
|||||||
return nil, svcerr.ErrAuthorization
|
return nil, svcerr.ErrAuthorization
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := svc.UpdateCert(ctx, session, req.clientID, req.ClientCert, req.ClientKey, req.CACert)
|
cfg, err := svc.UpdateCert(ctx, session, req.configID, req.ClientCert, req.ClientKey, req.CACert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res := updateConfigRes{
|
res := updateConfigRes{
|
||||||
ClientID: cfg.ClientID,
|
ID: cfg.ID,
|
||||||
ClientCert: cfg.ClientCert,
|
ClientCert: cfg.ClientCert,
|
||||||
CACert: cfg.CACert,
|
CACert: cfg.CACert,
|
||||||
ClientKey: cfg.ClientKey,
|
ClientKey: cfg.ClientKey,
|
||||||
@@ -102,24 +106,14 @@ func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var channels []channelRes
|
|
||||||
for _, ch := range config.Channels {
|
|
||||||
channels = append(channels, channelRes{
|
|
||||||
ID: ch.ID,
|
|
||||||
Name: ch.Name,
|
|
||||||
Metadata: ch.Metadata,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
res := viewRes{
|
res := viewRes{
|
||||||
ClientID: config.ClientID,
|
ID: config.ID,
|
||||||
CLientSecret: config.ClientSecret,
|
ExternalID: config.ExternalID,
|
||||||
Channels: channels,
|
Name: config.Name,
|
||||||
ExternalID: config.ExternalID,
|
Content: config.Content,
|
||||||
ExternalKey: config.ExternalKey,
|
Status: config.Status,
|
||||||
Name: config.Name,
|
ProfileID: config.ProfileID,
|
||||||
Content: config.Content,
|
RenderContext: config.RenderContext,
|
||||||
State: config.State,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
@@ -139,46 +133,16 @@ func updateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := bootstrap.Config{
|
config := bootstrap.Config{
|
||||||
ClientID: req.id,
|
ID: req.id,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Content: req.Content,
|
Content: req.Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := svc.Update(ctx, session, config); err != nil {
|
if err := svc.Update(ctx, session, config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res := configRes{
|
return updateRes{}, nil
|
||||||
id: config.ClientID,
|
|
||||||
created: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateConnEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
|
||||||
return func(ctx context.Context, request any) (any, error) {
|
|
||||||
req := request.(updateConnReq)
|
|
||||||
if err := req.validate(); err != nil {
|
|
||||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
|
||||||
if !ok {
|
|
||||||
return nil, svcerr.ErrAuthorization
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := svc.UpdateConnections(ctx, session, req.token, req.id, req.Channels); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res := configRes{
|
|
||||||
id: req.id,
|
|
||||||
created: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,24 +170,14 @@ func listEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cfg := range page.Configs {
|
for _, cfg := range page.Configs {
|
||||||
var channels []channelRes
|
|
||||||
for _, ch := range cfg.Channels {
|
|
||||||
channels = append(channels, channelRes{
|
|
||||||
ID: ch.ID,
|
|
||||||
Name: ch.Name,
|
|
||||||
Metadata: ch.Metadata,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
view := viewRes{
|
view := viewRes{
|
||||||
ClientID: cfg.ClientID,
|
ID: cfg.ID,
|
||||||
CLientSecret: cfg.ClientSecret,
|
ExternalID: cfg.ExternalID,
|
||||||
Channels: channels,
|
Name: cfg.Name,
|
||||||
ExternalID: cfg.ExternalID,
|
Content: cfg.Content,
|
||||||
ExternalKey: cfg.ExternalKey,
|
Status: cfg.Status,
|
||||||
Name: cfg.Name,
|
ProfileID: cfg.ProfileID,
|
||||||
Content: cfg.Content,
|
RenderContext: cfg.RenderContext,
|
||||||
State: cfg.State,
|
|
||||||
}
|
}
|
||||||
res.Configs = append(res.Configs, view)
|
res.Configs = append(res.Configs, view)
|
||||||
}
|
}
|
||||||
@@ -268,9 +222,9 @@ func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader, sec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
func enableConfigEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
return func(ctx context.Context, request any) (any, error) {
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
req := request.(changeStateReq)
|
req := request.(changeConfigStatusReq)
|
||||||
if err := req.validate(); err != nil {
|
if err := req.validate(); err != nil {
|
||||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
}
|
}
|
||||||
@@ -280,10 +234,257 @@ func stateEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
|||||||
return nil, svcerr.ErrAuthorization
|
return nil, svcerr.ErrAuthorization
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := svc.ChangeState(ctx, session, req.token, req.id, req.State); err != nil {
|
cfg, err := svc.EnableConfig(ctx, session, req.id)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return stateRes{}, nil
|
return changeConfigStatusRes{Config: cfg}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableConfigEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(changeConfigStatusReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := svc.DisableConfig(ctx, session, req.id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return changeConfigStatusRes{Config: cfg}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProfileEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(createProfileReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
saved, err := svc.CreateProfile(ctx, session, req.Profile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profileRes{Profile: saved, created: true}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadProfileEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(uploadProfileReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
saved, err := svc.CreateProfile(ctx, session, req.Profile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profileRes{Profile: saved, created: true}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewProfileEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(viewProfileReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
p, err := svc.ViewProfile(ctx, session, req.profileID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profileRes{Profile: p}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileSlotsEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(viewProfileReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
p, err := svc.ViewProfile(ctx, session, req.profileID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profileSlotsRes{BindingSlots: p.BindingSlots}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPreviewEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(renderPreviewReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
p, err := svc.ViewProfile(ctx, session, req.profileID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := req.Config
|
||||||
|
cfg.DomainID = session.DomainID
|
||||||
|
cfg.ProfileID = p.ID
|
||||||
|
if cfg.RenderContext == nil {
|
||||||
|
cfg.RenderContext = req.RenderContext
|
||||||
|
}
|
||||||
|
|
||||||
|
rendered, err := bootstrap.NewRenderer().Render(p, cfg, req.Bindings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderPreviewRes{Content: string(rendered)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateProfileEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(updateProfileReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
req.Profile.ID = req.profileID
|
||||||
|
if err := svc.UpdateProfile(ctx, session, req.Profile); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profileRes{Profile: req.Profile}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteProfileEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(deleteProfileReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
if err := svc.DeleteProfile(ctx, session, req.profileID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return removeRes{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listProfilesEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(listProfilesReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
page, err := svc.ListProfiles(ctx, session, req.offset, req.limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profilesPageRes{ProfilesPage: page}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignProfileEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(assignProfileReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
if err := svc.AssignProfile(ctx, session, req.configID, req.ProfileID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return removeRes{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindResourcesEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(bindResourcesReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
if err := svc.BindResources(ctx, session, req.token, req.configID, req.Bindings); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return removeRes{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listBindingsEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(listBindingsReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
snapshots, err := svc.ListBindings(ctx, session, req.configID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bindingsRes{Bindings: snapshots}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshBindingsEndpoint(svc bootstrap.Service) endpoint.Endpoint {
|
||||||
|
return func(ctx context.Context, request any) (any, error) {
|
||||||
|
req := request.(refreshBindingsReq)
|
||||||
|
if err := req.validate(); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
session, ok := ctx.Value(authn.SessionKey).(authn.Session)
|
||||||
|
if !ok {
|
||||||
|
return nil, svcerr.ErrAuthorization
|
||||||
|
}
|
||||||
|
if err := svc.RefreshBindings(ctx, session, req.token, req.configID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return removeRes{}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+303
-372
@@ -38,7 +38,6 @@ const (
|
|||||||
invalidToken = "invalid"
|
invalidToken = "invalid"
|
||||||
email = "test@example.com"
|
email = "test@example.com"
|
||||||
unknown = "unknown"
|
unknown = "unknown"
|
||||||
channelsNum = 3
|
|
||||||
contentType = "application/json"
|
contentType = "application/json"
|
||||||
wrongID = "wrong_id"
|
wrongID = "wrong_id"
|
||||||
|
|
||||||
@@ -49,44 +48,32 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
encKey = []byte("1234567891011121")
|
encKey = []byte("1234567891011121")
|
||||||
metadata = map[string]any{"meta": "data"}
|
addExternalID = testsutil.GenerateUUID(&testing.T{})
|
||||||
addExternalID = testsutil.GenerateUUID(&testing.T{})
|
addExternalKey = testsutil.GenerateUUID(&testing.T{})
|
||||||
addExternalKey = testsutil.GenerateUUID(&testing.T{})
|
addID = testsutil.GenerateUUID(&testing.T{})
|
||||||
addClientID = testsutil.GenerateUUID(&testing.T{})
|
addReq = struct {
|
||||||
addClientSecret = testsutil.GenerateUUID(&testing.T{})
|
ExternalID string `json:"external_id"`
|
||||||
addReq = struct {
|
ExternalKey string `json:"external_key"`
|
||||||
ClientID string `json:"client_id"`
|
Name string `json:"name"`
|
||||||
ClientSecret string `json:"client_secret"`
|
Content string `json:"content"`
|
||||||
ExternalID string `json:"external_id"`
|
|
||||||
ExternalKey string `json:"external_key"`
|
|
||||||
Channels []string `json:"channels"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
}{
|
}{
|
||||||
ClientID: addClientID,
|
ExternalID: addExternalID,
|
||||||
ClientSecret: addClientSecret,
|
ExternalKey: addExternalKey,
|
||||||
ExternalID: addExternalID,
|
Name: "name",
|
||||||
ExternalKey: addExternalKey,
|
Content: "config",
|
||||||
Channels: []string{"1"},
|
|
||||||
Name: "name",
|
|
||||||
Content: "config",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateReq = struct {
|
updateReq = struct {
|
||||||
Channels []string `json:"channels,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
Content string `json:"content,omitempty"`
|
Status bootstrap.Status `json:"status,omitempty"`
|
||||||
State bootstrap.State `json:"state,omitempty"`
|
ClientCert string `json:"client_cert,omitempty"`
|
||||||
ClientCert string `json:"client_cert,omitempty"`
|
CACert string `json:"ca_cert,omitempty"`
|
||||||
ClientSecret string `json:"client_secret,omitempty"`
|
|
||||||
CACert string `json:"ca_cert,omitempty"`
|
|
||||||
}{
|
}{
|
||||||
Channels: []string{"1"},
|
Content: "config update",
|
||||||
Content: "config update",
|
Status: bootstrap.EnabledStatus,
|
||||||
State: 1,
|
ClientCert: "newcert",
|
||||||
ClientCert: "newcert",
|
CACert: "newca",
|
||||||
ClientSecret: "newkey",
|
|
||||||
CACert: "newca",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
missingIDRes = toJSON(apiutil.ErrMissingID)
|
missingIDRes = toJSON(apiutil.ErrMissingID)
|
||||||
@@ -108,21 +95,14 @@ type testRequest struct {
|
|||||||
|
|
||||||
func newConfig() bootstrap.Config {
|
func newConfig() bootstrap.Config {
|
||||||
return bootstrap.Config{
|
return bootstrap.Config{
|
||||||
ClientID: addClientID,
|
ID: addID,
|
||||||
ClientSecret: addClientSecret,
|
ExternalID: addExternalID,
|
||||||
ExternalID: addExternalID,
|
ExternalKey: addExternalKey,
|
||||||
ExternalKey: addExternalKey,
|
Name: addName,
|
||||||
Channels: []bootstrap.Channel{
|
Content: addContent,
|
||||||
{
|
ClientCert: "newcert",
|
||||||
ID: "1",
|
ClientKey: "newkey",
|
||||||
Metadata: metadata,
|
CACert: "newca",
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: addName,
|
|
||||||
Content: addContent,
|
|
||||||
ClientCert: "newcert",
|
|
||||||
ClientKey: "newkey",
|
|
||||||
CACert: "newca",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,14 +180,6 @@ func TestAdd(t *testing.T) {
|
|||||||
|
|
||||||
data := toJSON(addReq)
|
data := toJSON(addReq)
|
||||||
|
|
||||||
neID := addReq
|
|
||||||
neID.ClientID = testsutil.GenerateUUID(t)
|
|
||||||
neData := toJSON(neID)
|
|
||||||
|
|
||||||
invalidChannels := addReq
|
|
||||||
invalidChannels.Channels = []string{wrongID}
|
|
||||||
wrongData := toJSON(invalidChannels)
|
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
req string
|
req string
|
||||||
@@ -238,7 +210,7 @@ func TestAdd(t *testing.T) {
|
|||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusCreated,
|
status: http.StatusCreated,
|
||||||
location: "/clients/configs/" + c.ClientID,
|
location: "/clients/configs/" + c.ID,
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -261,26 +233,6 @@ func TestAdd(t *testing.T) {
|
|||||||
location: "",
|
location: "",
|
||||||
err: svcerr.ErrConflict,
|
err: svcerr.ErrConflict,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "add a config with non-existent ID",
|
|
||||||
req: neData,
|
|
||||||
domainID: domainID,
|
|
||||||
token: validToken,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusBadRequest,
|
|
||||||
location: "",
|
|
||||||
err: svcerr.ErrConflict,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "add a config with invalid channels",
|
|
||||||
req: wrongData,
|
|
||||||
domainID: domainID,
|
|
||||||
token: validToken,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusBadRequest,
|
|
||||||
location: "",
|
|
||||||
err: svcerr.ErrConflict,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
desc: "add a config with wrong JSON",
|
desc: "add a config with wrong JSON",
|
||||||
req: "{\"external_id\": 5}",
|
req: "{\"external_id\": 5}",
|
||||||
@@ -354,20 +306,12 @@ func TestView(t *testing.T) {
|
|||||||
defer bs.Close()
|
defer bs.Close()
|
||||||
c := newConfig()
|
c := newConfig()
|
||||||
|
|
||||||
var channels []channel
|
|
||||||
for _, ch := range c.Channels {
|
|
||||||
channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
|
|
||||||
}
|
|
||||||
|
|
||||||
data := config{
|
data := config{
|
||||||
ClientID: c.ClientID,
|
ID: c.ID,
|
||||||
ClientSecret: c.ClientSecret,
|
Status: c.Status,
|
||||||
State: c.State,
|
ExternalID: c.ExternalID,
|
||||||
Channels: channels,
|
Name: c.Name,
|
||||||
ExternalID: c.ExternalID,
|
Content: c.Content,
|
||||||
ExternalKey: c.ExternalKey,
|
|
||||||
Name: c.Name,
|
|
||||||
Content: c.Content,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -383,7 +327,7 @@ func TestView(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view a config with invalid token",
|
desc: "view a config with invalid token",
|
||||||
token: invalidToken,
|
token: invalidToken,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
res: config{},
|
res: config{},
|
||||||
authenticateErr: svcerr.ErrAuthentication,
|
authenticateErr: svcerr.ErrAuthentication,
|
||||||
@@ -392,7 +336,7 @@ func TestView(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view a config",
|
desc: "view a config",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
res: data,
|
res: data,
|
||||||
err: nil,
|
err: nil,
|
||||||
@@ -408,7 +352,7 @@ func TestView(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view a config with an empty token",
|
desc: "view a config with an empty token",
|
||||||
token: "",
|
token: "",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
res: config{},
|
res: config{},
|
||||||
err: apiutil.ErrBearerToken,
|
err: apiutil.ErrBearerToken,
|
||||||
@@ -416,7 +360,7 @@ func TestView(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view config without authorization",
|
desc: "view config without authorization",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
status: http.StatusForbidden,
|
status: http.StatusForbidden,
|
||||||
res: config{},
|
res: config{},
|
||||||
err: svcerr.ErrAuthorization,
|
err: svcerr.ErrAuthorization,
|
||||||
@@ -445,10 +389,6 @@ func TestView(t *testing.T) {
|
|||||||
assert.Nil(t, err, fmt.Sprintf("Decoding expected 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))
|
|
||||||
// Empty channels to prevent order mismatch.
|
|
||||||
tc.res.Channels = []channel{}
|
|
||||||
view.Channels = []channel{}
|
|
||||||
assert.Equal(t, tc.res, view, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, view))
|
assert.Equal(t, tc.res, view, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, view))
|
||||||
svcCall.Unset()
|
svcCall.Unset()
|
||||||
authCall.Unset()
|
authCall.Unset()
|
||||||
@@ -477,7 +417,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update with invalid token",
|
desc: "update with invalid token",
|
||||||
req: data,
|
req: data,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: invalidToken,
|
token: invalidToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
@@ -487,7 +427,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update with an empty token",
|
desc: "update with an empty token",
|
||||||
req: data,
|
req: data,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: "",
|
token: "",
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
@@ -496,7 +436,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update a valid config",
|
desc: "update a valid config",
|
||||||
req: data,
|
req: data,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
@@ -505,7 +445,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update a config with wrong content type",
|
desc: "update a config with wrong content type",
|
||||||
req: data,
|
req: data,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: "",
|
contentType: "",
|
||||||
status: http.StatusUnsupportedMediaType,
|
status: http.StatusUnsupportedMediaType,
|
||||||
@@ -523,7 +463,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update a config with invalid request format",
|
desc: "update a config with invalid request format",
|
||||||
req: "}",
|
req: "}",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusBadRequest,
|
status: http.StatusBadRequest,
|
||||||
@@ -531,7 +471,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "update a config with an empty request",
|
desc: "update a config with an empty request",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
req: "",
|
req: "",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
@@ -549,7 +489,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
svcCall := svc.On("Update", mock.Anything, tc.session, mock.Anything).Return(tc.err)
|
svcCall := svc.On("Update", mock.Anything, tc.session, mock.Anything).Return(tc.err)
|
||||||
req := testRequest{
|
req := testRequest{
|
||||||
client: bs.Client(),
|
client: bs.Client(),
|
||||||
method: http.MethodPut,
|
method: http.MethodPatch,
|
||||||
url: fmt.Sprintf("%s/%s/clients/configs/%s", bs.URL, domainID, tc.id),
|
url: fmt.Sprintf("%s/%s/clients/configs/%s", bs.URL, domainID, tc.id),
|
||||||
contentType: tc.contentType,
|
contentType: tc.contentType,
|
||||||
token: tc.token,
|
token: tc.token,
|
||||||
@@ -585,7 +525,7 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update with invalid token",
|
desc: "update with invalid token",
|
||||||
req: data,
|
req: data,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: invalidToken,
|
token: invalidToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
@@ -595,7 +535,7 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update with an empty token",
|
desc: "update with an empty token",
|
||||||
req: data,
|
req: data,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: "",
|
token: "",
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
@@ -604,7 +544,7 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update a valid config",
|
desc: "update a valid config",
|
||||||
req: data,
|
req: data,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
@@ -613,7 +553,7 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update a config with wrong content type",
|
desc: "update a config with wrong content type",
|
||||||
req: data,
|
req: data,
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: "",
|
contentType: "",
|
||||||
status: http.StatusUnsupportedMediaType,
|
status: http.StatusUnsupportedMediaType,
|
||||||
@@ -631,7 +571,7 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "update a config with invalid request format",
|
desc: "update a config with invalid request format",
|
||||||
req: "}",
|
req: "}",
|
||||||
id: c.ClientSecret,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
status: http.StatusBadRequest,
|
status: http.StatusBadRequest,
|
||||||
@@ -639,7 +579,7 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "update a config with an empty request",
|
desc: "update a config with an empty request",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
req: "",
|
req: "",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
@@ -672,130 +612,9 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateConnections(t *testing.T) {
|
|
||||||
bs, svc, auth := newBootstrapServer()
|
|
||||||
defer bs.Close()
|
|
||||||
c := newConfig()
|
|
||||||
data := toJSON(updateReq)
|
|
||||||
|
|
||||||
invalidChannels := updateReq
|
|
||||||
invalidChannels.Channels = []string{wrongID}
|
|
||||||
|
|
||||||
wrongData := toJSON(invalidChannels)
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
req string
|
|
||||||
id string
|
|
||||||
token string
|
|
||||||
session smqauthn.Session
|
|
||||||
contentType string
|
|
||||||
status int
|
|
||||||
authenticateErr error
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "update connections with invalid token",
|
|
||||||
req: data,
|
|
||||||
id: c.ClientID,
|
|
||||||
token: invalidToken,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusUnauthorized,
|
|
||||||
authenticateErr: svcerr.ErrAuthentication,
|
|
||||||
err: svcerr.ErrAuthentication,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update connections with an empty token",
|
|
||||||
req: data,
|
|
||||||
id: c.ClientID,
|
|
||||||
token: "",
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusUnauthorized,
|
|
||||||
err: apiutil.ErrBearerToken,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update connections valid config",
|
|
||||||
req: data,
|
|
||||||
id: c.ClientID,
|
|
||||||
token: validToken,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusOK,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update connections with wrong content type",
|
|
||||||
req: data,
|
|
||||||
id: c.ClientID,
|
|
||||||
token: validToken,
|
|
||||||
contentType: "",
|
|
||||||
status: http.StatusUnsupportedMediaType,
|
|
||||||
err: apiutil.ErrUnsupportedContentType,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update connections for a non-existing config",
|
|
||||||
req: data,
|
|
||||||
id: wrongID,
|
|
||||||
token: validToken,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusNotFound,
|
|
||||||
err: svcerr.ErrNotFound,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update connections with invalid channels",
|
|
||||||
req: wrongData,
|
|
||||||
id: c.ClientID,
|
|
||||||
token: validToken,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusNotFound,
|
|
||||||
err: svcerr.ErrNotFound,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update a config with invalid request format",
|
|
||||||
req: "}",
|
|
||||||
id: c.ClientID,
|
|
||||||
token: validToken,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusBadRequest,
|
|
||||||
err: svcerr.ErrMalformedEntity,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update a config with an empty request",
|
|
||||||
id: c.ClientID,
|
|
||||||
req: "",
|
|
||||||
token: validToken,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusBadRequest,
|
|
||||||
err: svcerr.ErrMalformedEntity,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
|
||||||
if tc.token == validToken {
|
|
||||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
|
||||||
}
|
|
||||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
|
||||||
repoCall := svc.On("UpdateConnections", mock.Anything, tc.session, tc.token, mock.Anything, mock.Anything).Return(tc.err)
|
|
||||||
req := testRequest{
|
|
||||||
client: bs.Client(),
|
|
||||||
method: http.MethodPut,
|
|
||||||
url: fmt.Sprintf("%s/%s/clients/configs/connections/%s", bs.URL, domainID, tc.id),
|
|
||||||
contentType: tc.contentType,
|
|
||||||
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))
|
|
||||||
repoCall.Unset()
|
|
||||||
authCall.Unset()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
configNum := 101
|
configNum := 101
|
||||||
changedStateNum := 20
|
changedStatusNum := 20
|
||||||
var active, inactive []config
|
var active, inactive []config
|
||||||
list := make([]config, configNum)
|
list := make([]config, configNum)
|
||||||
|
|
||||||
@@ -807,43 +626,32 @@ func TestList(t *testing.T) {
|
|||||||
|
|
||||||
for i := 0; i < configNum; i++ {
|
for i := 0; i < configNum; i++ {
|
||||||
c.ExternalID = strconv.Itoa(i)
|
c.ExternalID = strconv.Itoa(i)
|
||||||
c.ClientSecret = c.ExternalID
|
|
||||||
c.Name = fmt.Sprintf("%s-%d", addName, i)
|
c.Name = fmt.Sprintf("%s-%d", addName, i)
|
||||||
c.ExternalKey = fmt.Sprintf("%s%s", addExternalKey, strconv.Itoa(i))
|
c.ExternalKey = fmt.Sprintf("%s%s", addExternalKey, strconv.Itoa(i))
|
||||||
|
|
||||||
var channels []channel
|
|
||||||
for _, ch := range c.Channels {
|
|
||||||
channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
|
|
||||||
}
|
|
||||||
s := config{
|
s := config{
|
||||||
ClientID: c.ClientID,
|
ID: c.ID,
|
||||||
ClientSecret: c.ClientSecret,
|
ExternalID: c.ExternalID,
|
||||||
Channels: channels,
|
Name: c.Name,
|
||||||
ExternalID: c.ExternalID,
|
Content: c.Content,
|
||||||
ExternalKey: c.ExternalKey,
|
Status: c.Status,
|
||||||
Name: c.Name,
|
|
||||||
Content: c.Content,
|
|
||||||
State: c.State,
|
|
||||||
}
|
}
|
||||||
list[i] = s
|
list[i] = s
|
||||||
}
|
}
|
||||||
// Change state of first 20 elements for filtering tests.
|
// Change status of first 20 elements for filtering tests.
|
||||||
for i := 0; i < changedStateNum; i++ {
|
for i := 0; i < changedStatusNum; i++ {
|
||||||
state := bootstrap.Active
|
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
state = bootstrap.Inactive
|
// Even elements remain inactive (default status).
|
||||||
}
|
|
||||||
svcCall := svc.On("ChangeState", context.Background(), mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
||||||
err := svc.ChangeState(context.Background(), smqauthn.Session{}, validToken, list[i].ClientID, state)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err))
|
|
||||||
|
|
||||||
svcCall.Unset()
|
|
||||||
|
|
||||||
list[i].State = state
|
|
||||||
if state == bootstrap.Inactive {
|
|
||||||
inactive = append(inactive, list[i])
|
inactive = append(inactive, list[i])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Odd elements are enabled (active).
|
||||||
|
enabledCfg := bootstrap.Config{ID: list[i].ID, Status: bootstrap.Active}
|
||||||
|
svcCall := svc.On("EnableConfig", context.Background(), mock.Anything, mock.Anything).Return(enabledCfg, nil)
|
||||||
|
_, err := svc.EnableConfig(context.Background(), smqauthn.Session{}, list[i].ID)
|
||||||
|
assert.Nil(t, err, fmt.Sprintf("Enabling config expected to succeed: %s.\n", err))
|
||||||
|
svcCall.Unset()
|
||||||
|
list[i].Status = bootstrap.Active
|
||||||
active = append(active, list[i])
|
active = append(active, list[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -979,7 +787,7 @@ func TestList(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view list with invalid query parameters",
|
desc: "view list with invalid query parameters",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d&key=%%", path, 10, 10, bootstrap.Inactive),
|
url: fmt.Sprintf("%s?offset=%d&limit=%d&status=%s&key=%%", path, 10, 10, bootstrap.Disabled),
|
||||||
status: http.StatusBadRequest,
|
status: http.StatusBadRequest,
|
||||||
res: configPage{},
|
res: configPage{},
|
||||||
err: apiutil.ErrInvalidQueryParams,
|
err: apiutil.ErrInvalidQueryParams,
|
||||||
@@ -987,7 +795,7 @@ func TestList(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view first 10 active",
|
desc: "view first 10 active",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d", path, 0, 20, bootstrap.Active),
|
url: fmt.Sprintf("%s?offset=%d&limit=%d&status=%s", path, 0, 20, bootstrap.Enabled),
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
res: configPage{
|
res: configPage{
|
||||||
Total: uint64(len(active)),
|
Total: uint64(len(active)),
|
||||||
@@ -1000,7 +808,7 @@ func TestList(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view first 10 inactive",
|
desc: "view first 10 inactive",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d", path, 0, 20, bootstrap.Inactive),
|
url: fmt.Sprintf("%s?offset=%d&limit=%d&status=%s", path, 0, 20, bootstrap.Disabled),
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
res: configPage{
|
res: configPage{
|
||||||
Total: uint64(len(list) - len(inactive)),
|
Total: uint64(len(list) - len(inactive)),
|
||||||
@@ -1013,7 +821,7 @@ func TestList(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view first 5 active",
|
desc: "view first 5 active",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d", path, 0, 10, bootstrap.Active),
|
url: fmt.Sprintf("%s?offset=%d&limit=%d&status=%s", path, 0, 10, bootstrap.Enabled),
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
res: configPage{
|
res: configPage{
|
||||||
Total: uint64(len(active)),
|
Total: uint64(len(active)),
|
||||||
@@ -1026,7 +834,7 @@ func TestList(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "view last 5 inactive",
|
desc: "view last 5 inactive",
|
||||||
token: validToken,
|
token: validToken,
|
||||||
url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d", path, 10, 10, bootstrap.Inactive),
|
url: fmt.Sprintf("%s?offset=%d&limit=%d&status=%s", path, 10, 10, bootstrap.Disabled),
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
res: configPage{
|
res: configPage{
|
||||||
Total: uint64(len(list) - len(active)),
|
Total: uint64(len(list) - len(active)),
|
||||||
@@ -1085,7 +893,7 @@ func TestRemove(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "remove with invalid token",
|
desc: "remove with invalid token",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: invalidToken,
|
token: invalidToken,
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
authenticateErr: svcerr.ErrAuthentication,
|
authenticateErr: svcerr.ErrAuthentication,
|
||||||
@@ -1093,7 +901,7 @@ func TestRemove(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "remove with an empty token",
|
desc: "remove with an empty token",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: "",
|
token: "",
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
err: apiutil.ErrBearerToken,
|
err: apiutil.ErrBearerToken,
|
||||||
@@ -1107,7 +915,7 @@ func TestRemove(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "remove config",
|
desc: "remove config",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
status: http.StatusNoContent,
|
status: http.StatusNoContent,
|
||||||
err: nil,
|
err: nil,
|
||||||
@@ -1151,27 +959,18 @@ func TestBootstrap(t *testing.T) {
|
|||||||
encExternKey, err := enc([]byte(c.ExternalKey))
|
encExternKey, err := enc([]byte(c.ExternalKey))
|
||||||
assert.Nil(t, err, fmt.Sprintf("Encrypting config expected to succeed: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Encrypting config expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
var channels []channel
|
|
||||||
for _, ch := range c.Channels {
|
|
||||||
channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
|
|
||||||
}
|
|
||||||
|
|
||||||
s := struct {
|
s := struct {
|
||||||
ClientID string `json:"client_id"`
|
ID string `json:"id"`
|
||||||
ClientSecret string `json:"client_secret"`
|
Content string `json:"content"`
|
||||||
Channels []channel `json:"channels"`
|
ClientCert string `json:"client_cert"`
|
||||||
Content string `json:"content"`
|
ClientKey string `json:"client_key"`
|
||||||
ClientCert string `json:"client_cert"`
|
CACert string `json:"ca_cert"`
|
||||||
ClientKey string `json:"client_key"`
|
|
||||||
CACert string `json:"ca_cert"`
|
|
||||||
}{
|
}{
|
||||||
ClientID: c.ClientID,
|
ID: c.ID,
|
||||||
ClientSecret: c.ClientSecret,
|
Content: c.Content,
|
||||||
Channels: channels,
|
ClientCert: c.ClientCert,
|
||||||
Content: c.Content,
|
ClientKey: c.ClientKey,
|
||||||
ClientCert: c.ClientCert,
|
CACert: c.CACert,
|
||||||
ClientKey: c.ClientKey,
|
|
||||||
CACert: c.CACert,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data := toJSON(s)
|
data := toJSON(s)
|
||||||
@@ -1276,97 +1075,63 @@ func TestBootstrap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChangeState(t *testing.T) {
|
func TestChangeStatus(t *testing.T) {
|
||||||
bs, svc, auth := newBootstrapServer()
|
bs, svc, auth := newBootstrapServer()
|
||||||
defer bs.Close()
|
defer bs.Close()
|
||||||
c := newConfig()
|
c := newConfig()
|
||||||
|
|
||||||
inactive := fmt.Sprintf("{\"state\": %d}", bootstrap.Inactive)
|
activeCfg := bootstrap.Config{ID: c.ID, Status: bootstrap.Active}
|
||||||
active := fmt.Sprintf("{\"state\": %d}", bootstrap.Active)
|
inactiveCfg := bootstrap.Config{ID: c.ID, Status: bootstrap.Inactive}
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
id string
|
id string
|
||||||
token string
|
token string
|
||||||
session smqauthn.Session
|
session smqauthn.Session
|
||||||
state string
|
action string
|
||||||
contentType string
|
|
||||||
status int
|
status int
|
||||||
authenticateErr error
|
authenticateErr error
|
||||||
err error
|
svcCfg bootstrap.Config
|
||||||
|
svcErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "change state with invalid token",
|
desc: "enable with invalid token",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: invalidToken,
|
token: invalidToken,
|
||||||
state: active,
|
action: "enable",
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusUnauthorized,
|
status: http.StatusUnauthorized,
|
||||||
authenticateErr: svcerr.ErrAuthentication,
|
authenticateErr: svcerr.ErrAuthentication,
|
||||||
err: svcerr.ErrAuthentication,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "change state with an empty token",
|
desc: "enable with empty token",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: "",
|
token: "",
|
||||||
state: active,
|
action: "enable",
|
||||||
contentType: contentType,
|
status: http.StatusUnauthorized,
|
||||||
status: http.StatusUnauthorized,
|
|
||||||
err: apiutil.ErrBearerToken,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "change state with invalid content type",
|
desc: "enable config",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
state: active,
|
action: "enable",
|
||||||
contentType: "",
|
status: http.StatusOK,
|
||||||
status: http.StatusUnsupportedMediaType,
|
svcCfg: activeCfg,
|
||||||
err: apiutil.ErrUnsupportedContentType,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "change state to active",
|
desc: "disable config",
|
||||||
id: c.ClientID,
|
id: c.ID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
state: active,
|
action: "disable",
|
||||||
contentType: contentType,
|
status: http.StatusOK,
|
||||||
status: http.StatusOK,
|
svcCfg: inactiveCfg,
|
||||||
err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "change state to inactive",
|
desc: "enable non-existing config",
|
||||||
id: c.ClientID,
|
id: wrongID,
|
||||||
token: validToken,
|
token: validToken,
|
||||||
state: inactive,
|
action: "enable",
|
||||||
contentType: contentType,
|
status: http.StatusNotFound,
|
||||||
status: http.StatusOK,
|
svcErr: svcerr.ErrNotFound,
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "change state of non-existing config",
|
|
||||||
id: wrongID,
|
|
||||||
token: validToken,
|
|
||||||
state: active,
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusNotFound,
|
|
||||||
err: svcerr.ErrNotFound,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "change state to invalid value",
|
|
||||||
id: c.ClientID,
|
|
||||||
token: validToken,
|
|
||||||
state: fmt.Sprintf("{\"state\": %d}", -3),
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusBadRequest,
|
|
||||||
err: svcerr.ErrMalformedEntity,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "change state with invalid data",
|
|
||||||
id: c.ClientID,
|
|
||||||
token: validToken,
|
|
||||||
state: "",
|
|
||||||
contentType: contentType,
|
|
||||||
status: http.StatusBadRequest,
|
|
||||||
err: svcerr.ErrMalformedEntity,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1376,14 +1141,16 @@ func TestChangeState(t *testing.T) {
|
|||||||
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||||
}
|
}
|
||||||
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
|
||||||
svcCall := svc.On("ChangeState", mock.Anything, tc.session, tc.token, mock.Anything, mock.Anything).Return(tc.err)
|
methodName := "EnableConfig"
|
||||||
|
if tc.action == "disable" {
|
||||||
|
methodName = "DisableConfig"
|
||||||
|
}
|
||||||
|
svcCall := svc.On(methodName, mock.Anything, tc.session, mock.Anything).Return(tc.svcCfg, tc.svcErr)
|
||||||
req := testRequest{
|
req := testRequest{
|
||||||
client: bs.Client(),
|
client: bs.Client(),
|
||||||
method: http.MethodPut,
|
method: http.MethodPost,
|
||||||
url: fmt.Sprintf("%s/%s/clients/state/%s", bs.URL, domainID, tc.id),
|
url: fmt.Sprintf("%s/%s/clients/configs/%s/%s", bs.URL, domainID, tc.id, tc.action),
|
||||||
token: tc.token,
|
token: tc.token,
|
||||||
contentType: tc.contentType,
|
|
||||||
body: strings.NewReader(tc.state),
|
|
||||||
}
|
}
|
||||||
res, err := req.make()
|
res, err := req.make()
|
||||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||||
@@ -1394,21 +1161,185 @@ func TestChangeState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type channel struct {
|
func TestUploadProfile(t *testing.T) {
|
||||||
ID string `json:"id"`
|
bs, svc, auth := newBootstrapServer()
|
||||||
Name string `json:"name,omitempty"`
|
defer bs.Close()
|
||||||
Metadata any `json:"metadata,omitempty"`
|
|
||||||
|
session := smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||||
|
saved := bootstrap.Profile{
|
||||||
|
ID: testsutil.GenerateUUID(t),
|
||||||
|
Name: "gateway",
|
||||||
|
TemplateFormat: bootstrap.TemplateFormatGoTemplate,
|
||||||
|
ContentTemplate: "{{ .Device.ID }}",
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
contentType string
|
||||||
|
body string
|
||||||
|
profile bootstrap.Profile
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "upload JSON profile",
|
||||||
|
contentType: "application/json",
|
||||||
|
body: `{"name":"gateway","template_format":"go-template","content_template":"{{ .Device.ID }}"}`,
|
||||||
|
profile: bootstrap.Profile{
|
||||||
|
Name: "gateway",
|
||||||
|
TemplateFormat: bootstrap.TemplateFormatGoTemplate,
|
||||||
|
ContentTemplate: "{{ .Device.ID }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "upload YAML profile",
|
||||||
|
contentType: "application/yaml",
|
||||||
|
body: "name: gateway\ntemplate_format: go-template\ncontent_template: '{{ .Device.ID }}'\n",
|
||||||
|
profile: bootstrap.Profile{
|
||||||
|
Name: "gateway",
|
||||||
|
TemplateFormat: bootstrap.TemplateFormatGoTemplate,
|
||||||
|
ContentTemplate: "{{ .Device.ID }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "upload TOML profile",
|
||||||
|
contentType: "application/toml",
|
||||||
|
body: "name = 'gateway'\ntemplate_format = 'go-template'\ncontent_template = '{{ .Device.ID }}'\n",
|
||||||
|
profile: bootstrap.Profile{
|
||||||
|
Name: "gateway",
|
||||||
|
TemplateFormat: bootstrap.TemplateFormatGoTemplate,
|
||||||
|
ContentTemplate: "{{ .Device.ID }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
authCall := auth.On("Authenticate", mock.Anything, validToken).Return(session, nil)
|
||||||
|
svcCall := svc.On("CreateProfile", mock.Anything, session, tc.profile).Return(saved, nil)
|
||||||
|
req := testRequest{
|
||||||
|
client: bs.Client(),
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: fmt.Sprintf("%s/%s/clients/bootstrap/profiles/upload", bs.URL, domainID),
|
||||||
|
contentType: tc.contentType,
|
||||||
|
token: validToken,
|
||||||
|
body: strings.NewReader(tc.body),
|
||||||
|
}
|
||||||
|
res, err := req.make()
|
||||||
|
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||||
|
assert.Equal(t, http.StatusCreated, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, http.StatusCreated, res.StatusCode))
|
||||||
|
assert.Equal(t, "/bootstrap/profiles/"+saved.ID, res.Header.Get("Location"))
|
||||||
|
svcCall.Unset()
|
||||||
|
authCall.Unset()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProfileSlots(t *testing.T) {
|
||||||
|
bs, svc, auth := newBootstrapServer()
|
||||||
|
defer bs.Close()
|
||||||
|
|
||||||
|
session := smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||||
|
profileID := testsutil.GenerateUUID(t)
|
||||||
|
slots := []bootstrap.BindingSlot{
|
||||||
|
{Name: "mqtt_client", Type: "client", Required: true, Fields: []string{"id", "secret"}},
|
||||||
|
{Name: "telemetry", Type: "channel", Required: true, Fields: []string{"id", "topic"}},
|
||||||
|
}
|
||||||
|
profile := bootstrap.Profile{
|
||||||
|
ID: profileID,
|
||||||
|
Name: "gateway",
|
||||||
|
BindingSlots: slots,
|
||||||
|
}
|
||||||
|
authCall := auth.On("Authenticate", mock.Anything, validToken).Return(session, nil)
|
||||||
|
svcCall := svc.On("ViewProfile", mock.Anything, session, profileID).Return(profile, nil)
|
||||||
|
|
||||||
|
req := testRequest{
|
||||||
|
client: bs.Client(),
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: fmt.Sprintf("%s/%s/clients/bootstrap/profiles/%s/slots", bs.URL, domainID, profileID),
|
||||||
|
token: validToken,
|
||||||
|
}
|
||||||
|
res, err := req.make()
|
||||||
|
assert.Nil(t, err, fmt.Sprintf("profile slots unexpected error %s", err))
|
||||||
|
assert.Equal(t, http.StatusOK, res.StatusCode, fmt.Sprintf("expected status code %d got %d", http.StatusOK, res.StatusCode))
|
||||||
|
|
||||||
|
var got struct {
|
||||||
|
BindingSlots []bootstrap.BindingSlot `json:"binding_slots"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&got)
|
||||||
|
assert.Nil(t, err, fmt.Sprintf("decoding profile slots expected to succeed: %s", err))
|
||||||
|
assert.ElementsMatch(t, slots, got.BindingSlots)
|
||||||
|
|
||||||
|
svcCall.Unset()
|
||||||
|
authCall.Unset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderPreview(t *testing.T) {
|
||||||
|
bs, svc, auth := newBootstrapServer()
|
||||||
|
defer bs.Close()
|
||||||
|
|
||||||
|
session := smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||||
|
profileID := testsutil.GenerateUUID(t)
|
||||||
|
configID := testsutil.GenerateUUID(t)
|
||||||
|
profile := bootstrap.Profile{
|
||||||
|
ID: profileID,
|
||||||
|
Name: "gateway",
|
||||||
|
TemplateFormat: bootstrap.TemplateFormatGoTemplate,
|
||||||
|
ContentTemplate: `device={{ .Device.ID }} site={{ .Vars.site }} topic={{ index (index .Bindings "telemetry").Snapshot "topic" }}`,
|
||||||
|
}
|
||||||
|
authCall := auth.On("Authenticate", mock.Anything, validToken).Return(session, nil)
|
||||||
|
svcCall := svc.On("ViewProfile", mock.Anything, session, profileID).Return(profile, nil)
|
||||||
|
|
||||||
|
reqBody := struct {
|
||||||
|
Config bootstrap.Config `json:"config"`
|
||||||
|
Bindings []bootstrap.BindingSnapshot `json:"bindings"`
|
||||||
|
}{
|
||||||
|
Config: bootstrap.Config{
|
||||||
|
ID: configID,
|
||||||
|
ExternalID: "gw-001",
|
||||||
|
RenderContext: map[string]any{
|
||||||
|
"site": "warehouse-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bindings: []bootstrap.BindingSnapshot{
|
||||||
|
{
|
||||||
|
Slot: "telemetry",
|
||||||
|
Type: "channel",
|
||||||
|
ResourceID: "ch-1",
|
||||||
|
Snapshot: map[string]any{
|
||||||
|
"topic": "devices/gw-001/telemetry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := testRequest{
|
||||||
|
client: bs.Client(),
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: fmt.Sprintf("%s/%s/clients/bootstrap/profiles/%s/render-preview", bs.URL, domainID, profileID),
|
||||||
|
contentType: contentType,
|
||||||
|
token: validToken,
|
||||||
|
body: strings.NewReader(toJSON(reqBody)),
|
||||||
|
}
|
||||||
|
res, err := req.make()
|
||||||
|
assert.Nil(t, err, fmt.Sprintf("render preview unexpected error %s", err))
|
||||||
|
assert.Equal(t, http.StatusOK, res.StatusCode, fmt.Sprintf("expected status code %d got %d", http.StatusOK, res.StatusCode))
|
||||||
|
|
||||||
|
var got struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&got)
|
||||||
|
assert.Nil(t, err, fmt.Sprintf("decoding render preview expected to succeed: %s", err))
|
||||||
|
assert.Equal(t, "device="+configID+" site=warehouse-1 topic=devices/gw-001/telemetry", got.Content)
|
||||||
|
|
||||||
|
svcCall.Unset()
|
||||||
|
authCall.Unset()
|
||||||
}
|
}
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
ClientID string `json:"client_id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
ClientSecret string `json:"client_secret,omitempty"`
|
ExternalID string `json:"external_id"`
|
||||||
Channels []channel `json:"channels,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
ExternalID string `json:"external_id"`
|
Name string `json:"name"`
|
||||||
ExternalKey string `json:"external_key,omitempty"`
|
Status bootstrap.Status `json:"status"`
|
||||||
Content string `json:"content,omitempty"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
State bootstrap.State `json:"state"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type configPage struct {
|
type configPage struct {
|
||||||
|
|||||||
+162
-48
@@ -11,16 +11,16 @@ import (
|
|||||||
const maxLimitSize = 100
|
const maxLimitSize = 100
|
||||||
|
|
||||||
type addReq struct {
|
type addReq struct {
|
||||||
token string
|
token string
|
||||||
ClientID string `json:"client_id"`
|
ExternalID string `json:"external_id"`
|
||||||
ExternalID string `json:"external_id"`
|
ExternalKey string `json:"external_key"`
|
||||||
ExternalKey string `json:"external_key"`
|
Name string `json:"name"`
|
||||||
Channels []string `json:"channels"`
|
Content string `json:"content"`
|
||||||
Name string `json:"name"`
|
ClientCert string `json:"client_cert"`
|
||||||
Content string `json:"content"`
|
ClientKey string `json:"client_key"`
|
||||||
ClientCert string `json:"client_cert"`
|
CACert string `json:"ca_cert"`
|
||||||
ClientKey string `json:"client_key"`
|
ProfileID string `json:"profile_id"`
|
||||||
CACert string `json:"ca_cert"`
|
RenderContext map[string]any `json:"render_context"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req addReq) validate() error {
|
func (req addReq) validate() error {
|
||||||
@@ -36,16 +36,6 @@ func (req addReq) validate() error {
|
|||||||
return apiutil.ErrBearerKey
|
return apiutil.ErrBearerKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.Channels) == 0 {
|
|
||||||
return apiutil.ErrEmptyList
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, channel := range req.Channels {
|
|
||||||
if channel == "" {
|
|
||||||
return apiutil.ErrMissingID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,32 +66,14 @@ func (req updateReq) validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type updateCertReq struct {
|
type updateCertReq struct {
|
||||||
clientID string
|
configID string
|
||||||
ClientCert string `json:"client_cert"`
|
ClientCert string `json:"client_cert"`
|
||||||
ClientKey string `json:"client_key"`
|
ClientKey string `json:"client_key"`
|
||||||
CACert string `json:"ca_cert"`
|
CACert string `json:"ca_cert"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req updateCertReq) validate() error {
|
func (req updateCertReq) validate() error {
|
||||||
if req.clientID == "" {
|
if req.configID == "" {
|
||||||
return apiutil.ErrMissingID
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type updateConnReq struct {
|
|
||||||
token string
|
|
||||||
id string
|
|
||||||
Channels []string `json:"channels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (req updateConnReq) validate() error {
|
|
||||||
if req.token == "" {
|
|
||||||
return apiutil.ErrBearerToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.id == "" {
|
|
||||||
return apiutil.ErrMissingID
|
return apiutil.ErrMissingID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,13 +111,12 @@ func (req bootstrapReq) validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type changeStateReq struct {
|
type changeConfigStatusReq struct {
|
||||||
token string
|
token string
|
||||||
id string
|
id string
|
||||||
State bootstrap.State `json:"state"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req changeStateReq) validate() error {
|
func (req changeConfigStatusReq) validate() error {
|
||||||
if req.token == "" {
|
if req.token == "" {
|
||||||
return apiutil.ErrBearerToken
|
return apiutil.ErrBearerToken
|
||||||
}
|
}
|
||||||
@@ -154,10 +125,153 @@ func (req changeStateReq) validate() error {
|
|||||||
return apiutil.ErrMissingID
|
return apiutil.ErrMissingID
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.State != bootstrap.Inactive &&
|
return nil
|
||||||
req.State != bootstrap.Active {
|
}
|
||||||
return bootstrap.ErrBootstrapState
|
|
||||||
}
|
// --- Profile requests ---
|
||||||
|
|
||||||
|
type createProfileReq struct {
|
||||||
|
bootstrap.Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req createProfileReq) validate() error {
|
||||||
|
if req.Name == "" {
|
||||||
|
return apiutil.ErrMissingName
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type uploadProfileReq struct {
|
||||||
|
bootstrap.Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req uploadProfileReq) validate() error {
|
||||||
|
if req.Name == "" {
|
||||||
|
return apiutil.ErrMissingName
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type viewProfileReq struct {
|
||||||
|
profileID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req viewProfileReq) validate() error {
|
||||||
|
if req.profileID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type updateProfileReq struct {
|
||||||
|
profileID string
|
||||||
|
bootstrap.Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req updateProfileReq) validate() error {
|
||||||
|
if req.profileID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type renderPreviewReq struct {
|
||||||
|
profileID string
|
||||||
|
Config bootstrap.Config `json:"config"`
|
||||||
|
RenderContext map[string]any `json:"render_context,omitempty"`
|
||||||
|
Bindings []bootstrap.BindingSnapshot `json:"bindings,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req renderPreviewReq) validate() error {
|
||||||
|
if req.profileID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type deleteProfileReq struct {
|
||||||
|
profileID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req deleteProfileReq) validate() error {
|
||||||
|
if req.profileID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type listProfilesReq struct {
|
||||||
|
offset uint64
|
||||||
|
limit uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req listProfilesReq) validate() error {
|
||||||
|
if req.limit == 0 || req.limit > maxLimitSize {
|
||||||
|
return apiutil.ErrLimitSize
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Enrollment binding requests ---
|
||||||
|
|
||||||
|
type assignProfileReq struct {
|
||||||
|
configID string
|
||||||
|
ProfileID string `json:"profile_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req assignProfileReq) validate() error {
|
||||||
|
if req.configID == "" || req.ProfileID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bindResourcesReq struct {
|
||||||
|
token string
|
||||||
|
configID string
|
||||||
|
Bindings []bootstrap.BindingRequest `json:"bindings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req bindResourcesReq) validate() error {
|
||||||
|
if req.token == "" {
|
||||||
|
return apiutil.ErrBearerToken
|
||||||
|
}
|
||||||
|
if req.configID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
|
if len(req.Bindings) == 0 {
|
||||||
|
return apiutil.ErrEmptyList
|
||||||
|
}
|
||||||
|
for _, b := range req.Bindings {
|
||||||
|
if b.Slot == "" || b.Type == "" || b.ResourceID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type listBindingsReq struct {
|
||||||
|
configID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req listBindingsReq) validate() error {
|
||||||
|
if req.configID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type refreshBindingsReq struct {
|
||||||
|
token string
|
||||||
|
configID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req refreshBindingsReq) validate() error {
|
||||||
|
if req.token == "" {
|
||||||
|
return apiutil.ErrBearerToken
|
||||||
|
}
|
||||||
|
if req.configID == "" {
|
||||||
|
return apiutil.ErrMissingID
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,23 +8,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||||
"github.com/absmach/magistrala/bootstrap"
|
|
||||||
"github.com/absmach/magistrala/internal/testsutil"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
channel1 = testsutil.GenerateUUID(&testing.T{})
|
|
||||||
channel2 = testsutil.GenerateUUID(&testing.T{})
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddReqValidation(t *testing.T) {
|
func TestAddReqValidation(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
token string
|
token string
|
||||||
externalID string
|
externalID string
|
||||||
externalKey string
|
externalKey string
|
||||||
channels []string
|
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -32,7 +24,6 @@ func TestAddReqValidation(t *testing.T) {
|
|||||||
token: "token",
|
token: "token",
|
||||||
externalID: "external-id",
|
externalID: "external-id",
|
||||||
externalKey: "external-key",
|
externalKey: "external-key",
|
||||||
channels: []string{channel1, channel2},
|
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -40,7 +31,6 @@ func TestAddReqValidation(t *testing.T) {
|
|||||||
token: "",
|
token: "",
|
||||||
externalID: "external-id",
|
externalID: "external-id",
|
||||||
externalKey: "external-key",
|
externalKey: "external-key",
|
||||||
channels: []string{channel1, channel2},
|
|
||||||
err: apiutil.ErrBearerToken,
|
err: apiutil.ErrBearerToken,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -48,7 +38,6 @@ func TestAddReqValidation(t *testing.T) {
|
|||||||
token: "token",
|
token: "token",
|
||||||
externalID: "",
|
externalID: "",
|
||||||
externalKey: "external-key",
|
externalKey: "external-key",
|
||||||
channels: []string{channel1, channel2},
|
|
||||||
err: apiutil.ErrMissingID,
|
err: apiutil.ErrMissingID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -56,7 +45,6 @@ func TestAddReqValidation(t *testing.T) {
|
|||||||
token: "token",
|
token: "token",
|
||||||
externalID: "external-id",
|
externalID: "external-id",
|
||||||
externalKey: "",
|
externalKey: "",
|
||||||
channels: []string{channel1, channel2},
|
|
||||||
err: apiutil.ErrBearerKey,
|
err: apiutil.ErrBearerKey,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -64,23 +52,6 @@ func TestAddReqValidation(t *testing.T) {
|
|||||||
token: "token",
|
token: "token",
|
||||||
externalID: "",
|
externalID: "",
|
||||||
externalKey: "",
|
externalKey: "",
|
||||||
channels: []string{channel1, channel2},
|
|
||||||
err: apiutil.ErrMissingID,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "empty channels",
|
|
||||||
token: "token",
|
|
||||||
externalID: "external-id",
|
|
||||||
externalKey: "external-key",
|
|
||||||
channels: []string{},
|
|
||||||
err: apiutil.ErrEmptyList,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "empty channel value",
|
|
||||||
token: "token",
|
|
||||||
externalID: "external-id",
|
|
||||||
externalKey: "external-key",
|
|
||||||
channels: []string{channel1, ""},
|
|
||||||
err: apiutil.ErrMissingID,
|
err: apiutil.ErrMissingID,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -90,7 +61,6 @@ func TestAddReqValidation(t *testing.T) {
|
|||||||
token: tc.token,
|
token: tc.token,
|
||||||
ExternalID: tc.externalID,
|
ExternalID: tc.externalID,
|
||||||
ExternalKey: tc.externalKey,
|
ExternalKey: tc.externalKey,
|
||||||
Channels: tc.channels,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := req.validate()
|
err := req.validate()
|
||||||
@@ -152,52 +122,19 @@ func TestUpdateReqValidation(t *testing.T) {
|
|||||||
func TestUpdateCertReqValidation(t *testing.T) {
|
func TestUpdateCertReqValidation(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
clientID string
|
configID string
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "empty client id",
|
desc: "empty config id",
|
||||||
clientID: "",
|
configID: "",
|
||||||
err: apiutil.ErrMissingID,
|
err: apiutil.ErrMissingID,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
req := updateCertReq{
|
req := updateCertReq{
|
||||||
clientID: tc.clientID,
|
configID: tc.configID,
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
id string
|
|
||||||
token string
|
|
||||||
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "empty token",
|
|
||||||
token: "",
|
|
||||||
id: "id",
|
|
||||||
err: apiutil.ErrBearerToken,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "empty id",
|
|
||||||
token: "token",
|
|
||||||
id: "",
|
|
||||||
err: apiutil.ErrMissingID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
req := updateConnReq{
|
|
||||||
token: tc.token,
|
|
||||||
id: tc.id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := req.validate()
|
err := req.validate()
|
||||||
@@ -269,42 +206,37 @@ func TestBootstrapReqValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChangeStateReqValidation(t *testing.T) {
|
func TestChangeConfigStatusReqValidation(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
token string
|
token string
|
||||||
id string
|
id string
|
||||||
state bootstrap.State
|
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "empty token",
|
desc: "empty token",
|
||||||
token: "",
|
token: "",
|
||||||
id: "id",
|
id: "id",
|
||||||
state: bootstrap.State(1),
|
|
||||||
err: apiutil.ErrBearerToken,
|
err: apiutil.ErrBearerToken,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "empty id",
|
desc: "empty id",
|
||||||
token: "token",
|
token: "token",
|
||||||
id: "",
|
id: "",
|
||||||
state: bootstrap.State(0),
|
|
||||||
err: apiutil.ErrMissingID,
|
err: apiutil.ErrMissingID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "invalid state",
|
desc: "valid request",
|
||||||
token: "token",
|
token: "token",
|
||||||
id: "id",
|
id: "id",
|
||||||
state: bootstrap.State(14),
|
err: nil,
|
||||||
err: bootstrap.ErrBootstrapState,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
req := changeStateReq{
|
req := changeConfigStatusReq{
|
||||||
token: tc.token,
|
token: tc.token,
|
||||||
id: tc.id,
|
id: tc.id,
|
||||||
State: tc.state,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := req.validate()
|
err := req.validate()
|
||||||
|
|||||||
+106
-27
@@ -14,7 +14,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
_ magistrala.Response = (*removeRes)(nil)
|
_ magistrala.Response = (*removeRes)(nil)
|
||||||
_ magistrala.Response = (*configRes)(nil)
|
_ magistrala.Response = (*configRes)(nil)
|
||||||
_ magistrala.Response = (*stateRes)(nil)
|
_ magistrala.Response = (*changeConfigStatusRes)(nil)
|
||||||
_ magistrala.Response = (*viewRes)(nil)
|
_ magistrala.Response = (*viewRes)(nil)
|
||||||
_ magistrala.Response = (*listRes)(nil)
|
_ magistrala.Response = (*listRes)(nil)
|
||||||
)
|
)
|
||||||
@@ -33,9 +33,32 @@ func (res removeRes) Empty() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type updateRes struct{}
|
||||||
|
|
||||||
|
func (res updateRes) Code() int {
|
||||||
|
return http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res updateRes) Headers() map[string]string {
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res updateRes) Empty() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type configRes struct {
|
type configRes struct {
|
||||||
id string
|
ID string `json:"id"`
|
||||||
created bool
|
ExternalID string `json:"external_id"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
Status bootstrap.Status `json:"status"`
|
||||||
|
ProfileID string `json:"profile_id,omitempty"`
|
||||||
|
RenderContext map[string]any `json:"render_context,omitempty"`
|
||||||
|
ClientCert string `json:"client_cert,omitempty"`
|
||||||
|
CACert string `json:"ca_cert,omitempty"`
|
||||||
|
ClientKey string `json:"client_key,omitempty"`
|
||||||
|
created bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (res configRes) Code() int {
|
func (res configRes) Code() int {
|
||||||
@@ -49,7 +72,7 @@ func (res configRes) Code() int {
|
|||||||
func (res configRes) Headers() map[string]string {
|
func (res configRes) Headers() map[string]string {
|
||||||
if res.created {
|
if res.created {
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"Location": fmt.Sprintf("/clients/configs/%s", res.id),
|
"Location": fmt.Sprintf("/clients/configs/%s", res.ID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,26 +80,20 @@ func (res configRes) Headers() map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (res configRes) Empty() bool {
|
func (res configRes) Empty() bool {
|
||||||
return true
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
type channelRes struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Metadata any `json:"metadata,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewRes struct {
|
type viewRes struct {
|
||||||
ClientID string `json:"client_id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
CLientSecret string `json:"client_secret,omitempty"`
|
ExternalID string `json:"external_id"`
|
||||||
Channels []channelRes `json:"channels,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
ExternalID string `json:"external_id"`
|
Name string `json:"name,omitempty"`
|
||||||
ExternalKey string `json:"external_key,omitempty"`
|
Status bootstrap.Status `json:"status"`
|
||||||
Content string `json:"content,omitempty"`
|
ProfileID string `json:"profile_id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
RenderContext map[string]any `json:"render_context,omitempty"`
|
||||||
State bootstrap.State `json:"state"`
|
ClientCert string `json:"client_cert,omitempty"`
|
||||||
ClientCert string `json:"client_cert,omitempty"`
|
CACert string `json:"ca_cert,omitempty"`
|
||||||
CACert string `json:"ca_cert,omitempty"`
|
ClientKey string `json:"client_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (res viewRes) Code() int {
|
func (res viewRes) Code() int {
|
||||||
@@ -110,22 +127,24 @@ func (res listRes) Empty() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type stateRes struct{}
|
type changeConfigStatusRes struct {
|
||||||
|
bootstrap.Config
|
||||||
|
}
|
||||||
|
|
||||||
func (res stateRes) Code() int {
|
func (res changeConfigStatusRes) Code() int {
|
||||||
return http.StatusOK
|
return http.StatusOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (res stateRes) Headers() map[string]string {
|
func (res changeConfigStatusRes) Headers() map[string]string {
|
||||||
return map[string]string{}
|
return map[string]string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (res stateRes) Empty() bool {
|
func (res changeConfigStatusRes) Empty() bool {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type updateConfigRes struct {
|
type updateConfigRes struct {
|
||||||
ClientID string `json:"client_id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
CACert string `json:"ca_cert,omitempty"`
|
CACert string `json:"ca_cert,omitempty"`
|
||||||
ClientCert string `json:"client_cert,omitempty"`
|
ClientCert string `json:"client_cert,omitempty"`
|
||||||
ClientKey string `json:"client_key,omitempty"`
|
ClientKey string `json:"client_key,omitempty"`
|
||||||
@@ -142,3 +161,63 @@ func (res updateConfigRes) Headers() map[string]string {
|
|||||||
func (res updateConfigRes) Empty() bool {
|
func (res updateConfigRes) Empty() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// profileRes is returned on create (201) or update (200).
|
||||||
|
type profileRes struct {
|
||||||
|
bootstrap.Profile
|
||||||
|
created bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res profileRes) Code() int {
|
||||||
|
if res.created {
|
||||||
|
return http.StatusCreated
|
||||||
|
}
|
||||||
|
return http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res profileRes) Headers() map[string]string {
|
||||||
|
if res.created {
|
||||||
|
return map[string]string{
|
||||||
|
"Location": fmt.Sprintf("/bootstrap/profiles/%s", res.ID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res profileRes) Empty() bool { return false }
|
||||||
|
|
||||||
|
// profilesPageRes is returned by ListProfiles.
|
||||||
|
type profilesPageRes struct {
|
||||||
|
bootstrap.ProfilesPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res profilesPageRes) Code() int { return http.StatusOK }
|
||||||
|
func (res profilesPageRes) Headers() map[string]string { return map[string]string{} }
|
||||||
|
func (res profilesPageRes) Empty() bool { return false }
|
||||||
|
|
||||||
|
// profileSlotsRes is returned by profile slots endpoint.
|
||||||
|
type profileSlotsRes struct {
|
||||||
|
BindingSlots []bootstrap.BindingSlot `json:"binding_slots"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res profileSlotsRes) Code() int { return http.StatusOK }
|
||||||
|
func (res profileSlotsRes) Headers() map[string]string { return map[string]string{} }
|
||||||
|
func (res profileSlotsRes) Empty() bool { return false }
|
||||||
|
|
||||||
|
// renderPreviewRes is returned by profile render-preview endpoint.
|
||||||
|
type renderPreviewRes struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res renderPreviewRes) Code() int { return http.StatusOK }
|
||||||
|
func (res renderPreviewRes) Headers() map[string]string { return map[string]string{} }
|
||||||
|
func (res renderPreviewRes) Empty() bool { return false }
|
||||||
|
|
||||||
|
// bindingsRes is returned by ListBindings.
|
||||||
|
type bindingsRes struct {
|
||||||
|
Bindings []bootstrap.BindingSnapshot `json:"bindings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res bindingsRes) Code() int { return http.StatusOK }
|
||||||
|
func (res bindingsRes) Headers() map[string]string { return map[string]string{} }
|
||||||
|
func (res bindingsRes) Empty() bool { return false }
|
||||||
|
|||||||
+259
-42
@@ -6,6 +6,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -19,12 +20,16 @@ import (
|
|||||||
"github.com/absmach/magistrala/pkg/errors"
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
kithttp "github.com/go-kit/kit/transport/http"
|
kithttp "github.com/go-kit/kit/transport/http"
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
contentType = "application/json"
|
contentType = "application/json"
|
||||||
|
yamlContentType = "yaml"
|
||||||
|
tomlContentType = "toml"
|
||||||
byteContentType = "application/octet-stream"
|
byteContentType = "application/octet-stream"
|
||||||
offsetKey = "offset"
|
offsetKey = "offset"
|
||||||
limitKey = "limit"
|
limitKey = "limit"
|
||||||
@@ -33,7 +38,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fullMatch = []string{"state", "external_id", "client_id", "client_key"}
|
fullMatch = []string{"status", "external_id", "id"}
|
||||||
partialMatch = []string{"name"}
|
partialMatch = []string{"name"}
|
||||||
// ErrBootstrap indicates error in getting bootstrap configuration.
|
// ErrBootstrap indicates error in getting bootstrap configuration.
|
||||||
ErrBootstrap = errors.New("failed to read bootstrap configuration")
|
ErrBootstrap = errors.New("failed to read bootstrap configuration")
|
||||||
@@ -69,7 +74,7 @@ func MakeHandler(svc bootstrap.Service, authn smqauthn.AuthNMiddleware, reader b
|
|||||||
api.EncodeResponse,
|
api.EncodeResponse,
|
||||||
opts...), "view").ServeHTTP)
|
opts...), "view").ServeHTTP)
|
||||||
|
|
||||||
r.Put("/{configID}", otelhttp.NewHandler(kithttp.NewServer(
|
r.Patch("/{configID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
updateEndpoint(svc),
|
updateEndpoint(svc),
|
||||||
decodeUpdateRequest,
|
decodeUpdateRequest,
|
||||||
api.EncodeResponse,
|
api.EncodeResponse,
|
||||||
@@ -81,25 +86,106 @@ func MakeHandler(svc bootstrap.Service, authn smqauthn.AuthNMiddleware, reader b
|
|||||||
api.EncodeResponse,
|
api.EncodeResponse,
|
||||||
opts...), "remove").ServeHTTP)
|
opts...), "remove").ServeHTTP)
|
||||||
|
|
||||||
r.Patch("/certs/{certID}", otelhttp.NewHandler(kithttp.NewServer(
|
r.Patch("/certs/{configID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
updateCertEndpoint(svc),
|
updateCertEndpoint(svc),
|
||||||
decodeUpdateCertRequest,
|
decodeUpdateCertRequest,
|
||||||
api.EncodeResponse,
|
api.EncodeResponse,
|
||||||
opts...), "update_cert").ServeHTTP)
|
opts...), "update_cert").ServeHTTP)
|
||||||
|
|
||||||
r.Put("/connections/{connID}", otelhttp.NewHandler(kithttp.NewServer(
|
r.Post("/{configID}/enable", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
updateConnEndpoint(svc),
|
enableConfigEndpoint(svc),
|
||||||
decodeUpdateConnRequest,
|
decodeChangeConfigStatusRequest,
|
||||||
api.EncodeResponse,
|
api.EncodeResponse,
|
||||||
opts...), "update_connections").ServeHTTP)
|
opts...), "enable_config").ServeHTTP)
|
||||||
|
|
||||||
|
r.Post("/{configID}/disable", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
disableConfigEndpoint(svc),
|
||||||
|
decodeChangeConfigStatusRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "disable_config").ServeHTTP)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
r.With(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware()).Put("/state/{clientID}", otelhttp.NewHandler(kithttp.NewServer(
|
// Profile and enrollment binding endpoints.
|
||||||
stateEndpoint(svc),
|
r.Route("/bootstrap", func(r chi.Router) {
|
||||||
decodeStateRequest,
|
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
|
||||||
api.EncodeResponse,
|
|
||||||
opts...), "update_state").ServeHTTP)
|
r.Route("/profiles", func(r chi.Router) {
|
||||||
|
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
createProfileEndpoint(svc),
|
||||||
|
decodeCreateProfileRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "create_profile").ServeHTTP)
|
||||||
|
|
||||||
|
r.Post("/upload", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
uploadProfileEndpoint(svc),
|
||||||
|
decodeUploadProfileRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "upload_profile").ServeHTTP)
|
||||||
|
|
||||||
|
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
listProfilesEndpoint(svc),
|
||||||
|
decodeListProfilesRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "list_profiles").ServeHTTP)
|
||||||
|
|
||||||
|
r.Get("/{profileID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
viewProfileEndpoint(svc),
|
||||||
|
decodeProfileEntityRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "view_profile").ServeHTTP)
|
||||||
|
|
||||||
|
r.Get("/{profileID}/slots", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
profileSlotsEndpoint(svc),
|
||||||
|
decodeProfileEntityRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "profile_slots").ServeHTTP)
|
||||||
|
|
||||||
|
r.Post("/{profileID}/render-preview", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
renderPreviewEndpoint(svc),
|
||||||
|
decodeRenderPreviewRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "render_preview").ServeHTTP)
|
||||||
|
|
||||||
|
r.Patch("/{profileID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
updateProfileEndpoint(svc),
|
||||||
|
decodeUpdateProfileRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "update_profile").ServeHTTP)
|
||||||
|
|
||||||
|
r.Delete("/{profileID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
deleteProfileEndpoint(svc),
|
||||||
|
decodeDeleteProfileRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "delete_profile").ServeHTTP)
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Route("/enrollments", func(r chi.Router) {
|
||||||
|
r.Patch("/{configID}/profile", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
assignProfileEndpoint(svc),
|
||||||
|
decodeAssignProfileRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "assign_profile").ServeHTTP)
|
||||||
|
|
||||||
|
r.Put("/{configID}/bindings", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
bindResourcesEndpoint(svc),
|
||||||
|
decodeBindResourcesRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "bind_resources").ServeHTTP)
|
||||||
|
|
||||||
|
r.Get("/{configID}/bindings", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
listBindingsEndpoint(svc),
|
||||||
|
decodeEnrollmentEntityRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "list_bindings").ServeHTTP)
|
||||||
|
|
||||||
|
r.Post("/{configID}/bindings/refresh", otelhttp.NewHandler(kithttp.NewServer(
|
||||||
|
refreshBindingsEndpoint(svc),
|
||||||
|
decodeRefreshBindingsRequest,
|
||||||
|
api.EncodeResponse,
|
||||||
|
opts...), "refresh_bindings").ServeHTTP)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Route("/clients/bootstrap", func(r chi.Router) {
|
r.Route("/clients/bootstrap", func(r chi.Router) {
|
||||||
@@ -162,23 +248,7 @@ func decodeUpdateCertRequest(_ context.Context, r *http.Request) (any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req := updateCertReq{
|
req := updateCertReq{
|
||||||
clientID: chi.URLParam(r, "certID"),
|
configID: chi.URLParam(r, "configID"),
|
||||||
}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeUpdateConnRequest(_ context.Context, r *http.Request) (any, error) {
|
|
||||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
|
||||||
return nil, apiutil.ErrUnsupportedContentType
|
|
||||||
}
|
|
||||||
|
|
||||||
req := updateConnReq{
|
|
||||||
token: apiutil.ExtractBearerToken(r),
|
|
||||||
id: chi.URLParam(r, "connID"),
|
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
@@ -209,6 +279,17 @@ func decodeListRequest(_ context.Context, r *http.Request) (any, error) {
|
|||||||
limit: l,
|
limit: l,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rawStatus := q.Get("status")
|
||||||
|
parsed, err := bootstrap.ToStatus(rawStatus)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidQueryParams)
|
||||||
|
}
|
||||||
|
if parsed == bootstrap.AllStatus {
|
||||||
|
delete(req.filter.FullMatch, "status")
|
||||||
|
} else {
|
||||||
|
req.filter.FullMatch["status"] = parsed.String()
|
||||||
|
}
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,20 +302,11 @@ func decodeBootstrapRequest(_ context.Context, r *http.Request) (any, error) {
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeStateRequest(_ context.Context, r *http.Request) (any, error) {
|
func decodeChangeConfigStatusRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
return changeConfigStatusReq{
|
||||||
return nil, apiutil.ErrUnsupportedContentType
|
|
||||||
}
|
|
||||||
|
|
||||||
req := changeStateReq{
|
|
||||||
token: apiutil.ExtractBearerToken(r),
|
token: apiutil.ExtractBearerToken(r),
|
||||||
id: chi.URLParam(r, "clientID"),
|
id: chi.URLParam(r, "configID"),
|
||||||
}
|
}, nil
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeEntityRequest(_ context.Context, r *http.Request) (any, error) {
|
func decodeEntityRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
@@ -281,3 +353,148 @@ func contains(l []string, s string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeCreateProfileRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||||
|
return nil, apiutil.ErrUnsupportedContentType
|
||||||
|
}
|
||||||
|
var req createProfileReq
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req.Profile); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUploadProfileRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
var req uploadProfileReq
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(contentType, "json"):
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req.Profile); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
case strings.Contains(contentType, yamlContentType):
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
if err := decodeYAMLProfile(body, &req.Profile); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
case strings.Contains(contentType, tomlContentType):
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
if err := decodeTOMLProfile(body, &req.Profile); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, apiutil.ErrUnsupportedContentType
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeYAMLProfile(body []byte, profile *bootstrap.Profile) error {
|
||||||
|
var raw map[string]any
|
||||||
|
if err := yaml.Unmarshal(body, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return decodeProfileMap(raw, profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeTOMLProfile(body []byte, profile *bootstrap.Profile) error {
|
||||||
|
var raw map[string]any
|
||||||
|
if err := toml.Unmarshal(body, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return decodeProfileMap(raw, profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeProfileMap(raw map[string]any, profile *bootstrap.Profile) error {
|
||||||
|
body, err := json.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(body, profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeListProfilesRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
o, err := apiutil.ReadNumQuery[uint64](r, offsetKey, defOffset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
l, err := apiutil.ReadNumQuery[uint64](r, limitKey, defLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||||
|
}
|
||||||
|
return listProfilesReq{offset: o, limit: l}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeProfileEntityRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
return viewProfileReq{profileID: chi.URLParam(r, "profileID")}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDeleteProfileRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
return deleteProfileReq{profileID: chi.URLParam(r, "profileID")}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUpdateProfileRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||||
|
return nil, apiutil.ErrUnsupportedContentType
|
||||||
|
}
|
||||||
|
req := updateProfileReq{profileID: chi.URLParam(r, "profileID")}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req.Profile); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeRenderPreviewRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||||
|
return nil, apiutil.ErrUnsupportedContentType
|
||||||
|
}
|
||||||
|
req := renderPreviewReq{profileID: chi.URLParam(r, "profileID")}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeAssignProfileRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||||
|
return nil, apiutil.ErrUnsupportedContentType
|
||||||
|
}
|
||||||
|
req := assignProfileReq{configID: chi.URLParam(r, "configID")}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeBindResourcesRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||||
|
return nil, apiutil.ErrUnsupportedContentType
|
||||||
|
}
|
||||||
|
req := bindResourcesReq{
|
||||||
|
token: apiutil.ExtractBearerToken(r),
|
||||||
|
configID: chi.URLParam(r, "configID"),
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeEnrollmentEntityRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
return listBindingsReq{configID: chi.URLParam(r, "configID")}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeRefreshBindingsRequest(_ context.Context, r *http.Request) (any, error) {
|
||||||
|
return refreshBindingsReq{
|
||||||
|
token: apiutil.ExtractBearerToken(r),
|
||||||
|
configID: chi.URLParam(r, "configID"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errBindingSlot = errors.New("invalid binding slot")
|
||||||
|
|
||||||
|
func validateProfileBindingSlots(profile Profile) error {
|
||||||
|
seen := make(map[string]struct{}, len(profile.BindingSlots))
|
||||||
|
for _, slot := range profile.BindingSlots {
|
||||||
|
if slot.Name == "" {
|
||||||
|
return fmt.Errorf("%w: slot name is required", errBindingSlot)
|
||||||
|
}
|
||||||
|
if slot.Type == "" {
|
||||||
|
return fmt.Errorf("%w: slot %q type is required", errBindingSlot, slot.Name)
|
||||||
|
}
|
||||||
|
if _, ok := seen[slot.Name]; ok {
|
||||||
|
return fmt.Errorf("%w: duplicate slot %q", errBindingSlot, slot.Name)
|
||||||
|
}
|
||||||
|
seen[slot.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRequestedBindings(profile Profile, requested []BindingRequest) error {
|
||||||
|
if len(profile.BindingSlots) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slots := make(map[string]BindingSlot, len(profile.BindingSlots))
|
||||||
|
for _, slot := range profile.BindingSlots {
|
||||||
|
slots[slot.Name] = slot
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(requested))
|
||||||
|
for _, binding := range requested {
|
||||||
|
slot, ok := slots[binding.Slot]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: unknown slot %q", errBindingSlot, binding.Slot)
|
||||||
|
}
|
||||||
|
if slot.Type != binding.Type {
|
||||||
|
return fmt.Errorf("%w: slot %q expects %q, got %q", errBindingSlot, binding.Slot, slot.Type, binding.Type)
|
||||||
|
}
|
||||||
|
if _, ok := seen[binding.Slot]; ok {
|
||||||
|
return fmt.Errorf("%w: duplicate binding for slot %q", errBindingSlot, binding.Slot)
|
||||||
|
}
|
||||||
|
seen[binding.Slot] = struct{}{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRequiredBindings(profile Profile, bindings []BindingSnapshot) error {
|
||||||
|
if len(profile.BindingSlots) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bound := make(map[string]BindingSnapshot, len(bindings))
|
||||||
|
for _, binding := range bindings {
|
||||||
|
bound[binding.Slot] = binding
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, slot := range profile.BindingSlots {
|
||||||
|
binding, ok := bound[slot.Name]
|
||||||
|
if !slot.Required && !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if slot.Required && !ok {
|
||||||
|
return fmt.Errorf("%w: required slot %q is not bound", errBindingSlot, slot.Name)
|
||||||
|
}
|
||||||
|
if binding.Type != slot.Type {
|
||||||
|
return fmt.Errorf("%w: slot %q expects %q, got %q", errBindingSlot, slot.Name, slot.Type, binding.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeBindingSnapshots(existing, updated []BindingSnapshot) []BindingSnapshot {
|
||||||
|
merged := make(map[string]BindingSnapshot, len(existing)+len(updated))
|
||||||
|
for _, binding := range existing {
|
||||||
|
merged[binding.Slot] = binding
|
||||||
|
}
|
||||||
|
for _, binding := range updated {
|
||||||
|
merged[binding.Slot] = binding
|
||||||
|
}
|
||||||
|
|
||||||
|
bindings := make([]BindingSnapshot, 0, len(merged))
|
||||||
|
for _, binding := range merged {
|
||||||
|
bindings = append(bindings, binding)
|
||||||
|
}
|
||||||
|
return bindings
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateProfileTemplate(p Profile) error {
|
||||||
|
if p.ContentTemplate == "" || p.TemplateFormat == TemplateFormatRaw {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := template.New("bootstrap").Funcs(allowlistedFuncs()).Parse(p.ContentTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(ErrRenderFailed, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BindingRequest carries a user's intent to bind a named profile slot to
|
||||||
|
// a concrete resource.
|
||||||
|
type BindingRequest struct {
|
||||||
|
Slot string `json:"slot"`
|
||||||
|
Type string `json:"type"` // "client" | "channel" | "cert"
|
||||||
|
ResourceID string `json:"resource_id"` // ID of the resource in its owning service
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingSnapshot is a Bootstrap-owned point-in-time copy of the resource
|
||||||
|
// fields needed for template rendering. It is populated at binding time so
|
||||||
|
// that the render path never calls external services.
|
||||||
|
type BindingSnapshot struct {
|
||||||
|
ConfigID string `json:"config_id"`
|
||||||
|
Slot string `json:"slot"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
ResourceID string `json:"resource_id"`
|
||||||
|
Snapshot map[string]any `json:"snapshot,omitempty"`
|
||||||
|
SecretSnapshot map[string]any `json:"secret_snapshot,omitempty"` // encrypted at rest
|
||||||
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingStore is the persistence interface for BindingSnapshots.
|
||||||
|
type BindingStore interface {
|
||||||
|
// Save upserts all given snapshots for the config.
|
||||||
|
Save(ctx context.Context, configID string, bindings []BindingSnapshot) error
|
||||||
|
|
||||||
|
// Retrieve returns all snapshots for the given config.
|
||||||
|
Retrieve(ctx context.Context, configID string) ([]BindingSnapshot, error)
|
||||||
|
|
||||||
|
// Delete removes the snapshot for a specific slot of a config.
|
||||||
|
Delete(ctx context.Context, configID, slot string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveRequest carries everything the BindingResolver needs to snapshot a
|
||||||
|
// set of resource bindings.
|
||||||
|
type ResolveRequest struct {
|
||||||
|
Enrollment Config
|
||||||
|
Token string
|
||||||
|
Requested []BindingRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingResolver validates that requested resources exist in their owning
|
||||||
|
// services, verifies type and slot compatibility, and returns snapshots ready
|
||||||
|
// for storage. It is called at binding time only; the render path must not
|
||||||
|
// call it.
|
||||||
|
type BindingResolver interface {
|
||||||
|
Resolve(ctx context.Context, req ResolveRequest) ([]BindingSnapshot, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderContext is the typed value injected into Go templates during rendering.
|
||||||
|
type RenderContext struct {
|
||||||
|
Device DeviceContext
|
||||||
|
Vars map[string]any
|
||||||
|
Bindings map[string]BindingContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceContext holds enrollment identity fields available inside templates.
|
||||||
|
type DeviceContext struct {
|
||||||
|
ID string
|
||||||
|
ExternalID string
|
||||||
|
DomainID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingContext holds the resolved resource data available inside templates
|
||||||
|
// for a specific slot.
|
||||||
|
type BindingContext struct {
|
||||||
|
Type string
|
||||||
|
ID string
|
||||||
|
Snapshot map[string]any
|
||||||
|
Secret map[string]any
|
||||||
|
}
|
||||||
+24
-69
@@ -3,45 +3,22 @@
|
|||||||
|
|
||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import "context"
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/absmach/magistrala/clients"
|
// Config represents a bootstrap enrollment.
|
||||||
)
|
|
||||||
|
|
||||||
// Config represents Configuration entity. It wraps information about external entity
|
|
||||||
// as well as info about corresponding Magistrala entities.
|
|
||||||
// MGClient represents corresponding Magistrala Client ID.
|
|
||||||
// MGKey is key of corresponding Magistrala Client.
|
|
||||||
// MGChannels is a list of Magistrala Channels corresponding Magistrala Client connects to.
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ClientID string `json:"client_id"`
|
ID string `json:"id"`
|
||||||
ClientSecret string `json:"client_secret"`
|
DomainID string `json:"domain_id,omitempty"`
|
||||||
DomainID string `json:"domain_id,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
ClientCert string `json:"client_cert,omitempty"`
|
||||||
ClientCert string `json:"client_cert,omitempty"`
|
ClientKey string `json:"client_key,omitempty"`
|
||||||
ClientKey string `json:"client_key,omitempty"`
|
CACert string `json:"ca_cert,omitempty"`
|
||||||
CACert string `json:"ca_cert,omitempty"`
|
ExternalID string `json:"external_id"`
|
||||||
Channels []Channel `json:"channels,omitempty"`
|
ExternalKey string `json:"external_key"`
|
||||||
ExternalID string `json:"external_id"`
|
Content string `json:"content,omitempty"`
|
||||||
ExternalKey string `json:"external_key"`
|
Status Status `json:"status"`
|
||||||
Content string `json:"content,omitempty"`
|
ProfileID string `json:"profile_id,omitempty"`
|
||||||
State State `json:"state"`
|
RenderContext map[string]any `json:"render_context,omitempty"`
|
||||||
}
|
|
||||||
|
|
||||||
// Channel represents Magistrala channel corresponding Magistrala Client is connected to.
|
|
||||||
type Channel struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Metadata map[string]any `json:"metadata,omitempty"`
|
|
||||||
DomainID string `json:"domain_id"`
|
|
||||||
Parent string `json:"parent_id,omitempty"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
|
||||||
UpdatedBy string `json:"updated_by,omitempty"`
|
|
||||||
Status clients.Status `json:"status"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter is used for the search filters.
|
// Filter is used for the search filters.
|
||||||
@@ -63,15 +40,15 @@ type ConfigsPage struct {
|
|||||||
type ConfigRepository interface {
|
type ConfigRepository interface {
|
||||||
// Save persists the Config. Successful operation is indicated by non-nil
|
// Save persists the Config. Successful operation is indicated by non-nil
|
||||||
// error response.
|
// error response.
|
||||||
Save(ctx context.Context, cfg Config, chsConnIDs []string) (string, error)
|
Save(ctx context.Context, cfg Config) (string, error)
|
||||||
|
|
||||||
// RetrieveByID retrieves the Config having the provided identifier, that is owned
|
// RetrieveByID retrieves the Config having the provided identifier, that is owned
|
||||||
// by the specified user.
|
// by the specified user.
|
||||||
RetrieveByID(ctx context.Context, domainID, id string) (Config, error)
|
RetrieveByID(ctx context.Context, domainID, id string) (Config, error)
|
||||||
|
|
||||||
// RetrieveAll retrieves a subset of Configs that are owned
|
// RetrieveAll retrieves a subset of Configs that belong to the given domain,
|
||||||
// by the specific user, with given filter parameters.
|
// with given filter parameters.
|
||||||
RetrieveAll(ctx context.Context, domainID string, clientIDs []string, filter Filter, offset, limit uint64) ConfigsPage
|
RetrieveAll(ctx context.Context, domainID string, filter Filter, offset, limit uint64) ConfigsPage
|
||||||
|
|
||||||
// RetrieveByExternalID returns Config for given external ID.
|
// RetrieveByExternalID returns Config for given external ID.
|
||||||
RetrieveByExternalID(ctx context.Context, externalID string) (Config, error)
|
RetrieveByExternalID(ctx context.Context, externalID string) (Config, error)
|
||||||
@@ -80,39 +57,17 @@ type ConfigRepository interface {
|
|||||||
// to indicate operation failure.
|
// to indicate operation failure.
|
||||||
Update(ctx context.Context, cfg Config) error
|
Update(ctx context.Context, cfg Config) error
|
||||||
|
|
||||||
|
// AssignProfile sets the profile reference for the given Config.
|
||||||
|
AssignProfile(ctx context.Context, domainID, id, profileID string) error
|
||||||
|
|
||||||
// UpdateCerts updates and returns an existing Config certificate and domainID.
|
// UpdateCerts updates and returns an existing Config certificate and domainID.
|
||||||
// A non-nil error is returned to indicate operation failure.
|
// A non-nil error is returned to indicate operation failure.
|
||||||
UpdateCert(ctx context.Context, domainID, clientID, clientCert, clientKey, caCert string) (Config, error)
|
UpdateCert(ctx context.Context, domainID, id, clientCert, clientKey, caCert string) (Config, error)
|
||||||
|
|
||||||
// UpdateConnections updates a list of Channels the Config is connected to
|
|
||||||
// adding new Channels if needed.
|
|
||||||
UpdateConnections(ctx context.Context, domainID, id string, channels []Channel, connections []string) error
|
|
||||||
|
|
||||||
// Remove removes the Config having the provided identifier, that is owned
|
// Remove removes the Config having the provided identifier, that is owned
|
||||||
// by the specified user.
|
// by the specified user.
|
||||||
Remove(ctx context.Context, domainID, id string) error
|
Remove(ctx context.Context, domainID, id string) error
|
||||||
|
|
||||||
// ChangeState changes of the Config, that is owned by the specific user.
|
// ChangeStatus changes the Status of the Config owned by the specific user.
|
||||||
ChangeState(ctx context.Context, domainID, id string, state State) error
|
ChangeStatus(ctx context.Context, domainID, id string, status Status) error
|
||||||
|
|
||||||
// ListExisting retrieves those channels from the given list that exist in DB.
|
|
||||||
ListExisting(ctx context.Context, domainID string, ids []string) ([]Channel, error)
|
|
||||||
|
|
||||||
// Methods RemoveClient, UpdateChannel, and RemoveChannel are related to
|
|
||||||
// event sourcing. That's why these methods surpass ownership check.
|
|
||||||
|
|
||||||
// RemoveClient removes Config of the Client with the given ID.
|
|
||||||
RemoveClient(ctx context.Context, id string) error
|
|
||||||
|
|
||||||
// UpdateChannel updates channel with the given ID.
|
|
||||||
UpdateChannel(ctx context.Context, c Channel) error
|
|
||||||
|
|
||||||
// RemoveChannel removes channel with the given ID.
|
|
||||||
RemoveChannel(ctx context.Context, id string) error
|
|
||||||
|
|
||||||
// ConnectClient changes state of the Config when the corresponding Client is connected to the Channel.
|
|
||||||
ConnectClient(ctx context.Context, channelID, clientID string) error
|
|
||||||
|
|
||||||
// DisconnectClient changes state of the Config when the corresponding Client is disconnected from the Channel.
|
|
||||||
DisconnectClient(ctx context.Context, channelID, clientID string) error
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright (c) Abstract Machines
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package consumer contains events consumer for events
|
|
||||||
// published by Bootstrap service.
|
|
||||||
package consumer
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright (c) Abstract Machines
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package consumer
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type removeEvent struct {
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
|
|
||||||
type updateChannelEvent struct {
|
|
||||||
id string
|
|
||||||
name string
|
|
||||||
metadata map[string]any
|
|
||||||
updatedAt time.Time
|
|
||||||
updatedBy string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connection event is either connect or disconnect event.
|
|
||||||
type connectionEvent struct {
|
|
||||||
clientIDs []string
|
|
||||||
channelID string
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
// Copyright (c) Abstract Machines
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package consumer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/absmach/magistrala/bootstrap"
|
|
||||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
|
||||||
"github.com/absmach/magistrala/pkg/events"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
clientRemove = "client.remove"
|
|
||||||
clientConnect = "group.assign"
|
|
||||||
clientDisconnect = "group.unassign"
|
|
||||||
|
|
||||||
channelPrefix = "channels."
|
|
||||||
channelUpdate = channelPrefix + "update"
|
|
||||||
channelRemove = channelPrefix + "remove"
|
|
||||||
|
|
||||||
memberKind = "client"
|
|
||||||
relation = "group"
|
|
||||||
)
|
|
||||||
|
|
||||||
type eventHandler struct {
|
|
||||||
svc bootstrap.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEventHandler returns new event store handler.
|
|
||||||
func NewEventHandler(svc bootstrap.Service) events.EventHandler {
|
|
||||||
return &eventHandler{
|
|
||||||
svc: svc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
|
|
||||||
msg, err := event.Encode()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg["operation"] {
|
|
||||||
case clientRemove:
|
|
||||||
rte := decodeRemoveClient(msg)
|
|
||||||
err = es.svc.RemoveConfigHandler(ctx, rte.id)
|
|
||||||
case clientConnect:
|
|
||||||
cte := decodeConnectClient(msg)
|
|
||||||
if cte.channelID == "" || len(cte.clientIDs) == 0 {
|
|
||||||
return svcerr.ErrMalformedEntity
|
|
||||||
}
|
|
||||||
for _, clientID := range cte.clientIDs {
|
|
||||||
if clientID == "" {
|
|
||||||
return svcerr.ErrMalformedEntity
|
|
||||||
}
|
|
||||||
if err := es.svc.ConnectClientHandler(ctx, cte.channelID, clientID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case clientDisconnect:
|
|
||||||
dte := decodeDisconnectClient(msg)
|
|
||||||
if dte.channelID == "" || len(dte.clientIDs) == 0 {
|
|
||||||
return svcerr.ErrMalformedEntity
|
|
||||||
}
|
|
||||||
for _, clientID := range dte.clientIDs {
|
|
||||||
if clientID == "" {
|
|
||||||
return svcerr.ErrMalformedEntity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range dte.clientIDs {
|
|
||||||
if err = es.svc.DisconnectClientHandler(ctx, dte.channelID, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case channelUpdate:
|
|
||||||
uce := decodeUpdateChannel(msg)
|
|
||||||
err = es.handleUpdateChannel(ctx, uce)
|
|
||||||
case channelRemove:
|
|
||||||
rce := decodeRemoveChannel(msg)
|
|
||||||
err = es.svc.RemoveChannelHandler(ctx, rce.id)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeRemoveClient(event map[string]any) removeEvent {
|
|
||||||
return removeEvent{
|
|
||||||
id: events.Read(event, "id", ""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeUpdateChannel(event map[string]any) updateChannelEvent {
|
|
||||||
metadata := events.Read(event, "metadata", map[string]any{})
|
|
||||||
|
|
||||||
return updateChannelEvent{
|
|
||||||
id: events.Read(event, "id", ""),
|
|
||||||
name: events.Read(event, "name", ""),
|
|
||||||
metadata: metadata,
|
|
||||||
updatedAt: events.Read(event, "updated_at", time.Now()),
|
|
||||||
updatedBy: events.Read(event, "updated_by", ""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeRemoveChannel(event map[string]any) removeEvent {
|
|
||||||
return removeEvent{
|
|
||||||
id: events.Read(event, "id", ""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeConnectClient(event map[string]any) connectionEvent {
|
|
||||||
if events.Read(event, "memberKind", "") != memberKind && events.Read(event, "relation", "") != relation {
|
|
||||||
return connectionEvent{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectionEvent{
|
|
||||||
channelID: events.Read(event, "group_id", ""),
|
|
||||||
clientIDs: events.ReadStringSlice(event, "member_ids"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeDisconnectClient(event map[string]any) connectionEvent {
|
|
||||||
if events.Read(event, "memberKind", "") != memberKind && events.Read(event, "relation", "") != relation {
|
|
||||||
return connectionEvent{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectionEvent{
|
|
||||||
channelID: events.Read(event, "group_id", ""),
|
|
||||||
clientIDs: events.ReadStringSlice(event, "member_ids"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *eventHandler) handleUpdateChannel(ctx context.Context, uce updateChannelEvent) error {
|
|
||||||
channel := bootstrap.Channel{
|
|
||||||
ID: uce.id,
|
|
||||||
Name: uce.name,
|
|
||||||
Metadata: uce.metadata,
|
|
||||||
UpdatedAt: uce.updatedAt,
|
|
||||||
UpdatedBy: uce.updatedBy,
|
|
||||||
}
|
|
||||||
|
|
||||||
return es.svc.UpdateChannelHandler(ctx, channel)
|
|
||||||
}
|
|
||||||
@@ -9,37 +9,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configPrefix = "bootstrap.config."
|
configPrefix = "bootstrap.config."
|
||||||
configCreate = configPrefix + "create"
|
configCreate = configPrefix + "create"
|
||||||
configUpdate = configPrefix + "update"
|
configUpdate = configPrefix + "update"
|
||||||
configRemove = configPrefix + "remove"
|
configRemove = configPrefix + "remove"
|
||||||
configView = configPrefix + "view"
|
configView = configPrefix + "view"
|
||||||
configList = configPrefix + "list"
|
configList = configPrefix + "list"
|
||||||
configHandlerRemove = configPrefix + "remove_handler"
|
clientPrefix = "bootstrap.client."
|
||||||
|
clientBootstrap = clientPrefix + "bootstrap"
|
||||||
|
configEnable = configPrefix + "enable"
|
||||||
|
configDisable = configPrefix + "disable"
|
||||||
|
certUpdate = "bootstrap.cert.update"
|
||||||
|
|
||||||
clientPrefix = "bootstrap.client."
|
profilePrefix = "bootstrap.profile."
|
||||||
clientBootstrap = clientPrefix + "bootstrap"
|
profileCreate = profilePrefix + "create"
|
||||||
clientStateChange = clientPrefix + "change_state"
|
profileView = profilePrefix + "view"
|
||||||
clientUpdateConnections = clientPrefix + "update_connections"
|
profileUpdate = profilePrefix + "update"
|
||||||
clientConnect = clientPrefix + "connect"
|
profileList = profilePrefix + "list"
|
||||||
clientDisconnect = clientPrefix + "disconnect"
|
profileDelete = profilePrefix + "delete"
|
||||||
|
profileAssign = profilePrefix + "assign"
|
||||||
channelPrefix = "bootstrap.channel."
|
bindingsPrefix = "bootstrap.bindings."
|
||||||
channelHandlerRemove = channelPrefix + "remove_handler"
|
bindingsBind = bindingsPrefix + "bind"
|
||||||
channelUpdateHandler = channelPrefix + "update_handler"
|
bindingsList = bindingsPrefix + "list"
|
||||||
|
bindingsRefresh = bindingsPrefix + "refresh"
|
||||||
certUpdate = "bootstrap.cert.update"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ events.Event = (*configEvent)(nil)
|
_ events.Event = (*configEvent)(nil)
|
||||||
_ events.Event = (*removeConfigEvent)(nil)
|
_ events.Event = (*removeConfigEvent)(nil)
|
||||||
_ events.Event = (*bootstrapEvent)(nil)
|
_ events.Event = (*bootstrapEvent)(nil)
|
||||||
_ events.Event = (*changeStateEvent)(nil)
|
_ events.Event = (*enableConfigEvent)(nil)
|
||||||
_ events.Event = (*updateConnectionsEvent)(nil)
|
_ events.Event = (*disableConfigEvent)(nil)
|
||||||
_ events.Event = (*updateCertEvent)(nil)
|
_ events.Event = (*updateCertEvent)(nil)
|
||||||
_ events.Event = (*listConfigsEvent)(nil)
|
_ events.Event = (*listConfigsEvent)(nil)
|
||||||
_ events.Event = (*removeHandlerEvent)(nil)
|
_ events.Event = (*profileEvent)(nil)
|
||||||
|
_ events.Event = (*deleteProfileEvent)(nil)
|
||||||
|
_ events.Event = (*assignProfileEvent)(nil)
|
||||||
|
_ events.Event = (*bindResourcesEvent)(nil)
|
||||||
|
_ events.Event = (*listBindingsEvent)(nil)
|
||||||
|
_ events.Event = (*refreshBindingsEvent)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type configEvent struct {
|
type configEvent struct {
|
||||||
@@ -49,17 +57,17 @@ type configEvent struct {
|
|||||||
|
|
||||||
func (ce configEvent) Encode() (map[string]any, error) {
|
func (ce configEvent) Encode() (map[string]any, error) {
|
||||||
val := map[string]any{
|
val := map[string]any{
|
||||||
"state": ce.State.String(),
|
"status": ce.Status.String(),
|
||||||
"operation": ce.operation,
|
"operation": ce.operation,
|
||||||
}
|
}
|
||||||
if ce.ClientID != "" {
|
if ce.ID != "" {
|
||||||
val["client_id"] = ce.ClientID
|
val["config_id"] = ce.ID
|
||||||
}
|
}
|
||||||
if ce.Content != "" {
|
if ce.Content != "" {
|
||||||
val["content"] = ce.Content
|
val["content"] = ce.Content
|
||||||
}
|
}
|
||||||
if ce.DomainID != "" {
|
if ce.DomainID != "" {
|
||||||
val["domain_id "] = ce.DomainID
|
val["domain_id"] = ce.DomainID
|
||||||
}
|
}
|
||||||
if ce.Name != "" {
|
if ce.Name != "" {
|
||||||
val["name"] = ce.Name
|
val["name"] = ce.Name
|
||||||
@@ -67,13 +75,6 @@ func (ce configEvent) Encode() (map[string]any, error) {
|
|||||||
if ce.ExternalID != "" {
|
if ce.ExternalID != "" {
|
||||||
val["external_id"] = ce.ExternalID
|
val["external_id"] = ce.ExternalID
|
||||||
}
|
}
|
||||||
if len(ce.Channels) > 0 {
|
|
||||||
channels := make([]string, len(ce.Channels))
|
|
||||||
for i, ch := range ce.Channels {
|
|
||||||
channels[i] = ch.ID
|
|
||||||
}
|
|
||||||
val["channels"] = channels
|
|
||||||
}
|
|
||||||
if ce.ClientCert != "" {
|
if ce.ClientCert != "" {
|
||||||
val["client_cert"] = ce.ClientCert
|
val["client_cert"] = ce.ClientCert
|
||||||
}
|
}
|
||||||
@@ -91,12 +92,12 @@ func (ce configEvent) Encode() (map[string]any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type removeConfigEvent struct {
|
type removeConfigEvent struct {
|
||||||
client string
|
config string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rce removeConfigEvent) Encode() (map[string]any, error) {
|
func (rce removeConfigEvent) Encode() (map[string]any, error) {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"client_id": rce.client,
|
"config_id": rce.config,
|
||||||
"operation": configRemove,
|
"operation": configRemove,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -137,14 +138,14 @@ func (be bootstrapEvent) Encode() (map[string]any, error) {
|
|||||||
"operation": clientBootstrap,
|
"operation": clientBootstrap,
|
||||||
}
|
}
|
||||||
|
|
||||||
if be.ClientID != "" {
|
if be.ID != "" {
|
||||||
val["client_id"] = be.ClientID
|
val["config_id"] = be.ID
|
||||||
}
|
}
|
||||||
if be.Content != "" {
|
if be.Content != "" {
|
||||||
val["content"] = be.Content
|
val["content"] = be.Content
|
||||||
}
|
}
|
||||||
if be.DomainID != "" {
|
if be.DomainID != "" {
|
||||||
val["domain_id "] = be.DomainID
|
val["domain_id"] = be.DomainID
|
||||||
}
|
}
|
||||||
if be.Name != "" {
|
if be.Name != "" {
|
||||||
val["name"] = be.Name
|
val["name"] = be.Name
|
||||||
@@ -152,13 +153,6 @@ func (be bootstrapEvent) Encode() (map[string]any, error) {
|
|||||||
if be.ExternalID != "" {
|
if be.ExternalID != "" {
|
||||||
val["external_id"] = be.ExternalID
|
val["external_id"] = be.ExternalID
|
||||||
}
|
}
|
||||||
if len(be.Channels) > 0 {
|
|
||||||
channels := make([]string, len(be.Channels))
|
|
||||||
for i, ch := range be.Channels {
|
|
||||||
channels[i] = ch.ID
|
|
||||||
}
|
|
||||||
val["channels"] = channels
|
|
||||||
}
|
|
||||||
if be.ClientCert != "" {
|
if be.ClientCert != "" {
|
||||||
val["client_cert"] = be.ClientCert
|
val["client_cert"] = be.ClientCert
|
||||||
}
|
}
|
||||||
@@ -174,34 +168,30 @@ func (be bootstrapEvent) Encode() (map[string]any, error) {
|
|||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type changeStateEvent struct {
|
type enableConfigEvent struct {
|
||||||
mgClient string
|
configID string
|
||||||
state bootstrap.State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cse changeStateEvent) Encode() (map[string]any, error) {
|
func (e enableConfigEvent) Encode() (map[string]any, error) {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"client_id": cse.mgClient,
|
"config_id": e.configID,
|
||||||
"state": cse.state.String(),
|
"operation": configEnable,
|
||||||
"operation": clientStateChange,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type updateConnectionsEvent struct {
|
type disableConfigEvent struct {
|
||||||
mgClient string
|
configID string
|
||||||
mgChannels []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uce updateConnectionsEvent) Encode() (map[string]any, error) {
|
func (e disableConfigEvent) Encode() (map[string]any, error) {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"client_id": uce.mgClient,
|
"config_id": e.configID,
|
||||||
"channels": uce.mgChannels,
|
"operation": configDisable,
|
||||||
"operation": clientUpdateConnections,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type updateCertEvent struct {
|
type updateCertEvent struct {
|
||||||
clientID string
|
configID string
|
||||||
clientCert string
|
clientCert string
|
||||||
clientKey string
|
clientKey string
|
||||||
caCert string
|
caCert string
|
||||||
@@ -209,7 +199,7 @@ type updateCertEvent struct {
|
|||||||
|
|
||||||
func (uce updateCertEvent) Encode() (map[string]any, error) {
|
func (uce updateCertEvent) Encode() (map[string]any, error) {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"client_id": uce.clientID,
|
"config_id": uce.configID,
|
||||||
"client_cert": uce.clientCert,
|
"client_cert": uce.clientCert,
|
||||||
"client_key": uce.clientKey,
|
"client_key": uce.clientKey,
|
||||||
"ca_cert": uce.caCert,
|
"ca_cert": uce.caCert,
|
||||||
@@ -217,61 +207,82 @@ func (uce updateCertEvent) Encode() (map[string]any, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type removeHandlerEvent struct {
|
type profileEvent struct {
|
||||||
id string
|
bootstrap.Profile
|
||||||
operation string
|
operation string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rhe removeHandlerEvent) Encode() (map[string]any, error) {
|
func (pe profileEvent) Encode() (map[string]any, error) {
|
||||||
return map[string]any{
|
|
||||||
"config_id": rhe.id,
|
|
||||||
"operation": rhe.operation,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type updateChannelHandlerEvent struct {
|
|
||||||
bootstrap.Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uche updateChannelHandlerEvent) Encode() (map[string]any, error) {
|
|
||||||
val := map[string]any{
|
val := map[string]any{
|
||||||
"operation": channelUpdateHandler,
|
"operation": pe.operation,
|
||||||
}
|
}
|
||||||
|
if pe.ID != "" {
|
||||||
if uche.ID != "" {
|
val["profile_id"] = pe.ID
|
||||||
val["channel_id"] = uche.ID
|
|
||||||
}
|
}
|
||||||
if uche.Name != "" {
|
if pe.DomainID != "" {
|
||||||
val["name"] = uche.Name
|
val["domain_id"] = pe.DomainID
|
||||||
}
|
}
|
||||||
if uche.Metadata != nil {
|
if pe.Name != "" {
|
||||||
val["metadata"] = uche.Metadata
|
val["name"] = pe.Name
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type connectClientEvent struct {
|
type deleteProfileEvent struct {
|
||||||
clientID string
|
profileID string
|
||||||
channelID string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cte connectClientEvent) Encode() (map[string]any, error) {
|
func (dpe deleteProfileEvent) Encode() (map[string]any, error) {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"client_id": cte.clientID,
|
"profile_id": dpe.profileID,
|
||||||
"channel_id": cte.channelID,
|
"operation": profileDelete,
|
||||||
"operation": clientConnect,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type disconnectClientEvent struct {
|
type assignProfileEvent struct {
|
||||||
clientID string
|
configID string
|
||||||
channelID string
|
profileID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dte disconnectClientEvent) Encode() (map[string]any, error) {
|
func (ape assignProfileEvent) Encode() (map[string]any, error) {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"client_id": dte.clientID,
|
"config_id": ape.configID,
|
||||||
"channel_id": dte.channelID,
|
"profile_id": ape.profileID,
|
||||||
"operation": clientDisconnect,
|
"operation": profileAssign,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bindResourcesEvent struct {
|
||||||
|
configID string
|
||||||
|
slots []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bre bindResourcesEvent) Encode() (map[string]any, error) {
|
||||||
|
return map[string]any{
|
||||||
|
"config_id": bre.configID,
|
||||||
|
"slots": bre.slots,
|
||||||
|
"operation": bindingsBind,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type listBindingsEvent struct {
|
||||||
|
configID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lbe listBindingsEvent) Encode() (map[string]any, error) {
|
||||||
|
return map[string]any{
|
||||||
|
"config_id": lbe.configID,
|
||||||
|
"operation": bindingsList,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type refreshBindingsEvent struct {
|
||||||
|
configID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rbe refreshBindingsEvent) Encode() (map[string]any, error) {
|
||||||
|
return map[string]any{
|
||||||
|
"config_id": rbe.configID,
|
||||||
|
"operation": bindingsRefresh,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,21 +14,23 @@ import (
|
|||||||
var _ bootstrap.Service = (*eventStore)(nil)
|
var _ bootstrap.Service = (*eventStore)(nil)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
magistralaPrefix = "magistrala."
|
magistralaPrefix = "magistrala."
|
||||||
createStream = magistralaPrefix + configCreate
|
createStream = magistralaPrefix + configCreate
|
||||||
viewStream = magistralaPrefix + configView
|
listStream = magistralaPrefix + configList
|
||||||
listStream = magistralaPrefix + configList
|
removeStream = magistralaPrefix + configRemove
|
||||||
updateStream = magistralaPrefix + configUpdate
|
updateCertStream = magistralaPrefix + certUpdate
|
||||||
removeStream = magistralaPrefix + configRemove
|
bootstrapStream = magistralaPrefix + clientBootstrap
|
||||||
updateCertStream = magistralaPrefix + certUpdate
|
enableConfigStream = magistralaPrefix + configEnable
|
||||||
updateConnectionsStream = magistralaPrefix + clientUpdateConnections
|
disableConfigStream = magistralaPrefix + configDisable
|
||||||
removeHandlerStream = magistralaPrefix + configHandlerRemove
|
createProfileStream = magistralaPrefix + profileCreate
|
||||||
bootstrapStream = magistralaPrefix + clientBootstrap
|
viewProfileStream = magistralaPrefix + profileView
|
||||||
stateChangeStream = magistralaPrefix + clientStateChange
|
updateProfileStream = magistralaPrefix + profileUpdate
|
||||||
connectStream = magistralaPrefix + clientConnect
|
listProfilesStream = magistralaPrefix + profileList
|
||||||
disconnectStream = magistralaPrefix + clientDisconnect
|
deleteProfileStream = magistralaPrefix + profileDelete
|
||||||
updateHandlerStream = magistralaPrefix + channelUpdateHandler
|
assignProfileStream = magistralaPrefix + profileAssign
|
||||||
removeChannelHandlerStream = magistralaPrefix + channelHandlerRemove
|
bindResourcesStream = magistralaPrefix + bindingsBind
|
||||||
|
listBindingsStream = magistralaPrefix + bindingsList
|
||||||
|
refreshBindingsStream = magistralaPrefix + bindingsRefresh
|
||||||
)
|
)
|
||||||
|
|
||||||
type eventStore struct {
|
type eventStore struct {
|
||||||
@@ -71,7 +73,7 @@ func (es *eventStore) View(ctx context.Context, session smqauthn.Session, id str
|
|||||||
cfg, configView,
|
cfg, configView,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := es.Publish(ctx, configView, ev); err != nil {
|
if err := es.Publish(ctx, magistralaPrefix+configView, ev); err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,17 +89,17 @@ func (es *eventStore) Update(ctx context.Context, session smqauthn.Session, cfg
|
|||||||
cfg, configUpdate,
|
cfg, configUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
return es.Publish(ctx, configUpdate, ev)
|
return es.Publish(ctx, magistralaPrefix+configUpdate, ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es eventStore) UpdateCert(ctx context.Context, session smqauthn.Session, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
func (es eventStore) UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
||||||
cfg, err := es.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert)
|
cfg, err := es.svc.UpdateCert(ctx, session, id, clientCert, clientKey, caCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ev := updateCertEvent{
|
ev := updateCertEvent{
|
||||||
clientID: clientID,
|
configID: id,
|
||||||
clientCert: clientCert,
|
clientCert: clientCert,
|
||||||
clientKey: clientKey,
|
clientKey: clientKey,
|
||||||
caCert: caCert,
|
caCert: caCert,
|
||||||
@@ -110,19 +112,6 @@ func (es eventStore) UpdateCert(ctx context.Context, session smqauthn.Session, c
|
|||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *eventStore) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error {
|
|
||||||
if err := es.svc.UpdateConnections(ctx, session, token, id, connections); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ev := updateConnectionsEvent{
|
|
||||||
mgClient: id,
|
|
||||||
mgChannels: connections,
|
|
||||||
}
|
|
||||||
|
|
||||||
return es.Publish(ctx, updateConnectionsStream, ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *eventStore) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
|
func (es *eventStore) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
|
||||||
bp, err := es.svc.List(ctx, session, filter, offset, limit)
|
bp, err := es.svc.List(ctx, session, filter, offset, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -149,7 +138,7 @@ func (es *eventStore) Remove(ctx context.Context, session smqauthn.Session, id s
|
|||||||
}
|
}
|
||||||
|
|
||||||
ev := removeConfigEvent{
|
ev := removeConfigEvent{
|
||||||
client: id,
|
config: id,
|
||||||
}
|
}
|
||||||
|
|
||||||
return es.Publish(ctx, removeStream, ev)
|
return es.Publish(ctx, removeStream, ev)
|
||||||
@@ -175,79 +164,120 @@ func (es *eventStore) Bootstrap(ctx context.Context, externalKey, externalID str
|
|||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *eventStore) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) error {
|
func (es *eventStore) EnableConfig(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
if err := es.svc.ChangeState(ctx, session, token, id, state); err != nil {
|
cfg, err := es.svc.EnableConfig(ctx, session, id)
|
||||||
return err
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ev := changeStateEvent{
|
ev := enableConfigEvent{configID: id}
|
||||||
mgClient: id,
|
if err := es.Publish(ctx, enableConfigStream, ev); err != nil {
|
||||||
state: state,
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
return cfg, nil
|
||||||
return es.Publish(ctx, stateChangeStream, ev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *eventStore) RemoveConfigHandler(ctx context.Context, id string) error {
|
func (es *eventStore) DisableConfig(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
if err := es.svc.RemoveConfigHandler(ctx, id); err != nil {
|
cfg, err := es.svc.DisableConfig(ctx, session, id)
|
||||||
return err
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ev := removeHandlerEvent{
|
ev := disableConfigEvent{configID: id}
|
||||||
id: id,
|
if err := es.Publish(ctx, disableConfigStream, ev); err != nil {
|
||||||
operation: configHandlerRemove,
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
return cfg, nil
|
||||||
return es.Publish(ctx, removeHandlerStream, ev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *eventStore) RemoveChannelHandler(ctx context.Context, id string) error {
|
func (es *eventStore) CreateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) (bootstrap.Profile, error) {
|
||||||
if err := es.svc.RemoveChannelHandler(ctx, id); err != nil {
|
saved, err := es.svc.CreateProfile(ctx, session, p)
|
||||||
return err
|
if err != nil {
|
||||||
|
return saved, err
|
||||||
}
|
}
|
||||||
|
ev := profileEvent{saved, profileCreate}
|
||||||
ev := removeHandlerEvent{
|
if err := es.Publish(ctx, createProfileStream, ev); err != nil {
|
||||||
id: id,
|
return saved, err
|
||||||
operation: channelHandlerRemove,
|
|
||||||
}
|
}
|
||||||
|
return saved, nil
|
||||||
return es.Publish(ctx, removeChannelHandlerStream, ev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *eventStore) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) error {
|
func (es *eventStore) ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (bootstrap.Profile, error) {
|
||||||
if err := es.svc.UpdateChannelHandler(ctx, channel); err != nil {
|
p, err := es.svc.ViewProfile(ctx, session, profileID)
|
||||||
return err
|
if err != nil {
|
||||||
|
return p, err
|
||||||
}
|
}
|
||||||
|
ev := profileEvent{p, profileView}
|
||||||
ev := updateChannelHandlerEvent{
|
if err := es.Publish(ctx, viewProfileStream, ev); err != nil {
|
||||||
channel,
|
return p, err
|
||||||
}
|
}
|
||||||
|
return p, nil
|
||||||
return es.Publish(ctx, updateStream, ev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *eventStore) ConnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
func (es *eventStore) UpdateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) error {
|
||||||
if err := es.svc.ConnectClientHandler(ctx, channelID, clientID); err != nil {
|
if err := es.svc.UpdateProfile(ctx, session, p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ev := profileEvent{p, profileUpdate}
|
||||||
ev := connectClientEvent{
|
return es.Publish(ctx, updateProfileStream, ev)
|
||||||
clientID: clientID,
|
|
||||||
channelID: channelID,
|
|
||||||
}
|
|
||||||
|
|
||||||
return es.Publish(ctx, connectStream, ev)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *eventStore) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
func (es *eventStore) ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64) (bootstrap.ProfilesPage, error) {
|
||||||
if err := es.svc.DisconnectClientHandler(ctx, channelID, clientID); err != nil {
|
pp, err := es.svc.ListProfiles(ctx, session, offset, limit)
|
||||||
|
if err != nil {
|
||||||
|
return pp, err
|
||||||
|
}
|
||||||
|
ev := profileEvent{operation: profileList}
|
||||||
|
if err := es.Publish(ctx, listProfilesStream, ev); err != nil {
|
||||||
|
return pp, err
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *eventStore) DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error {
|
||||||
|
if err := es.svc.DeleteProfile(ctx, session, profileID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ev := deleteProfileEvent{profileID: profileID}
|
||||||
ev := disconnectClientEvent{
|
return es.Publish(ctx, deleteProfileStream, ev)
|
||||||
clientID: clientID,
|
}
|
||||||
channelID: channelID,
|
|
||||||
}
|
func (es *eventStore) AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error {
|
||||||
|
if err := es.svc.AssignProfile(ctx, session, configID, profileID); err != nil {
|
||||||
return es.Publish(ctx, disconnectStream, ev)
|
return err
|
||||||
|
}
|
||||||
|
ev := assignProfileEvent{configID: configID, profileID: profileID}
|
||||||
|
return es.Publish(ctx, assignProfileStream, ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *eventStore) BindResources(ctx context.Context, session smqauthn.Session, token, configID string, bindings []bootstrap.BindingRequest) error {
|
||||||
|
if err := es.svc.BindResources(ctx, session, token, configID, bindings); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slots := make([]string, len(bindings))
|
||||||
|
for i, b := range bindings {
|
||||||
|
slots[i] = b.Slot
|
||||||
|
}
|
||||||
|
ev := bindResourcesEvent{configID: configID, slots: slots}
|
||||||
|
return es.Publish(ctx, bindResourcesStream, ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *eventStore) ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]bootstrap.BindingSnapshot, error) {
|
||||||
|
bs, err := es.svc.ListBindings(ctx, session, configID)
|
||||||
|
if err != nil {
|
||||||
|
return bs, err
|
||||||
|
}
|
||||||
|
ev := listBindingsEvent{configID: configID}
|
||||||
|
if err := es.Publish(ctx, listBindingsStream, ev); err != nil {
|
||||||
|
return bs, err
|
||||||
|
}
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *eventStore) RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error {
|
||||||
|
if err := es.svc.RefreshBindings(ctx, session, token, configID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ev := refreshBindingsEvent{configID: configID}
|
||||||
|
return es.Publish(ctx, refreshBindingsStream, ev)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
// Hasher specifies an API for generating hashes of arbitrary textual content.
|
||||||
|
type Hasher interface {
|
||||||
|
// Hash generates the hashed string from plain-text.
|
||||||
|
Hash(string) (string, error)
|
||||||
|
|
||||||
|
// Compare compares plain-text version to the hashed one. An error should
|
||||||
|
// indicate failed comparison.
|
||||||
|
Compare(string, string) error
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package hasher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cost = 10
|
||||||
|
legacyScryptPrefix = "scrypt$"
|
||||||
|
legacyScryptKeyN = 16384
|
||||||
|
legacyScryptKeyR = 8
|
||||||
|
legacyScryptKeyP = 1
|
||||||
|
legacyScryptKeySize = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errHashExternalKey = errors.NewServiceError("generate hash from external key failed")
|
||||||
|
errCompareExternalKey = errors.NewServiceError("compare external key and hash failed")
|
||||||
|
errInvalidHashStore = errors.New("invalid stored external key hash format")
|
||||||
|
errDecode = errors.New("failed to decode external key hash")
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ bootstrap.Hasher = (*bcryptHasher)(nil)
|
||||||
|
|
||||||
|
type bcryptHasher struct{}
|
||||||
|
|
||||||
|
// New instantiates a bcrypt-based hasher implementation.
|
||||||
|
func New() bootstrap.Hasher {
|
||||||
|
return &bcryptHasher{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*bcryptHasher) Hash(key string) (string, error) {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(key), cost)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(errHashExternalKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*bcryptHasher) Compare(plain, hashed string) error {
|
||||||
|
if strings.HasPrefix(hashed, legacyScryptPrefix) {
|
||||||
|
return compareLegacyScryptHash(plain, hashed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(plain)); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy rows may still contain plaintext external keys.
|
||||||
|
if subtle.ConstantTimeCompare([]byte(plain), []byte(hashed)) == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootstrap.ErrExternalKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareLegacyScryptHash(plain, hashed string) error {
|
||||||
|
parts := strings.Split(strings.TrimPrefix(hashed, legacyScryptPrefix), ".")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return errInvalidHashStore
|
||||||
|
}
|
||||||
|
|
||||||
|
actualHash, err := base64.StdEncoding.DecodeString(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errDecode, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, err := base64.StdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errDecode, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
derivedHash, err := scrypt.Key([]byte(plain), salt, legacyScryptKeyN, legacyScryptKeyR, legacyScryptKeyP, legacyScryptKeySize)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errCompareExternalKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if subtle.ConstantTimeCompare(derivedHash, actualHash) == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootstrap.ErrExternalKey
|
||||||
|
}
|
||||||
@@ -6,16 +6,22 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/auth"
|
||||||
"github.com/absmach/magistrala/bootstrap"
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
smqauthn "github.com/absmach/magistrala/pkg/authn"
|
||||||
"github.com/absmach/magistrala/pkg/authz"
|
"github.com/absmach/magistrala/pkg/authz"
|
||||||
|
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||||
"github.com/absmach/magistrala/pkg/policies"
|
"github.com/absmach/magistrala/pkg/policies"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
updatePermission = "update_permission"
|
createOperation = "create"
|
||||||
readPermission = "read_permission"
|
viewOperation = "view"
|
||||||
deletePermission = "delete_permission"
|
updateOperation = "update"
|
||||||
|
updateCertOperation = "update_cert"
|
||||||
|
listOperation = "list"
|
||||||
|
removeOperation = "remove"
|
||||||
|
changeStateOperation = "change_state"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ bootstrap.Service = (*authorizationMiddleware)(nil)
|
var _ bootstrap.Service = (*authorizationMiddleware)(nil)
|
||||||
@@ -34,7 +40,7 @@ func AuthorizationMiddleware(svc bootstrap.Service, authz authz.Authorization) b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) Add(ctx context.Context, session smqauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) {
|
func (am *authorizationMiddleware) Add(ctx context.Context, session smqauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) {
|
||||||
if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID); err != nil {
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, createOperation, auth.AnyIDs); err != nil {
|
||||||
return bootstrap.Config{}, err
|
return bootstrap.Config{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +48,7 @@ func (am *authorizationMiddleware) Add(ctx context.Context, session smqauthn.Ses
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) View(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
func (am *authorizationMiddleware) View(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, readPermission, policies.ClientType, id); err != nil {
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, viewOperation, id); err != nil {
|
||||||
return bootstrap.Config{}, err
|
return bootstrap.Config{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,34 +56,26 @@ func (am *authorizationMiddleware) View(ctx context.Context, session smqauthn.Se
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) Update(ctx context.Context, session smqauthn.Session, cfg bootstrap.Config) error {
|
func (am *authorizationMiddleware) Update(ctx context.Context, session smqauthn.Session, cfg bootstrap.Config) error {
|
||||||
if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, updatePermission, policies.ClientType, cfg.ClientID); err != nil {
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, updateOperation, cfg.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.svc.Update(ctx, session, cfg)
|
return am.svc.Update(ctx, session, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) UpdateCert(ctx context.Context, session smqauthn.Session, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
func (am *authorizationMiddleware) UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
||||||
if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, updatePermission, policies.ClientType, clientID); err != nil {
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, updateCertOperation, id); err != nil {
|
||||||
return bootstrap.Config{}, err
|
return bootstrap.Config{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert)
|
return am.svc.UpdateCert(ctx, session, id, clientCert, clientKey, caCert)
|
||||||
}
|
|
||||||
|
|
||||||
func (am *authorizationMiddleware) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error {
|
|
||||||
if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, updatePermission, policies.ClientType, id); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return am.svc.UpdateConnections(ctx, session, token, id, connections)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
|
func (am *authorizationMiddleware) List(ctx context.Context, session smqauthn.Session, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
|
||||||
if err := am.checkSuperAdmin(ctx, session.DomainUserID); err == nil {
|
if err := am.checkSuperAdmin(ctx, session); err == nil {
|
||||||
session.SuperAdmin = true
|
session.SuperAdmin = true
|
||||||
}
|
}
|
||||||
if err := am.authorize(ctx, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.AdminPermission, policies.DomainType, session.DomainID); err == nil {
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.AdminPermission, policies.DomainType, session.DomainID, listOperation, auth.AnyIDs); err == nil {
|
||||||
session.SuperAdmin = true
|
session.SuperAdmin = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +83,7 @@ func (am *authorizationMiddleware) List(ctx context.Context, session smqauthn.Se
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) Remove(ctx context.Context, session smqauthn.Session, id string) error {
|
func (am *authorizationMiddleware) Remove(ctx context.Context, session smqauthn.Session, id string) error {
|
||||||
if err := am.authorize(ctx, session.DomainID, policies.UserType, policies.UsersKind, session.DomainUserID, deletePermission, policies.ClientType, id); err != nil {
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, removeOperation, id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,34 +94,92 @@ func (am *authorizationMiddleware) Bootstrap(ctx context.Context, externalKey, e
|
|||||||
return am.svc.Bootstrap(ctx, externalKey, externalID, secure)
|
return am.svc.Bootstrap(ctx, externalKey, externalID, secure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) error {
|
func (am *authorizationMiddleware) EnableConfig(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
return am.svc.ChangeState(ctx, session, token, id, state)
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, changeStateOperation, id); err != nil {
|
||||||
|
return bootstrap.Config{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return am.svc.EnableConfig(ctx, session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) error {
|
func (am *authorizationMiddleware) DisableConfig(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
return am.svc.UpdateChannelHandler(ctx, channel)
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, changeStateOperation, id); err != nil {
|
||||||
|
return bootstrap.Config{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return am.svc.DisableConfig(ctx, session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) RemoveConfigHandler(ctx context.Context, id string) error {
|
func (am *authorizationMiddleware) CreateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) (bootstrap.Profile, error) {
|
||||||
return am.svc.RemoveConfigHandler(ctx, id)
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, createOperation, auth.AnyIDs); err != nil {
|
||||||
|
return bootstrap.Profile{}, err
|
||||||
|
}
|
||||||
|
return am.svc.CreateProfile(ctx, session, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) RemoveChannelHandler(ctx context.Context, id string) error {
|
func (am *authorizationMiddleware) ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (bootstrap.Profile, error) {
|
||||||
return am.svc.RemoveChannelHandler(ctx, id)
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, viewOperation, auth.AnyIDs); err != nil {
|
||||||
|
return bootstrap.Profile{}, err
|
||||||
|
}
|
||||||
|
return am.svc.ViewProfile(ctx, session, profileID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) ConnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
func (am *authorizationMiddleware) UpdateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) error {
|
||||||
return am.svc.ConnectClientHandler(ctx, channelID, clientID)
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, updateOperation, auth.AnyIDs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return am.svc.UpdateProfile(ctx, session, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
func (am *authorizationMiddleware) ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64) (bootstrap.ProfilesPage, error) {
|
||||||
return am.svc.DisconnectClientHandler(ctx, channelID, clientID)
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, listOperation, auth.AnyIDs); err != nil {
|
||||||
|
return bootstrap.ProfilesPage{}, err
|
||||||
|
}
|
||||||
|
return am.svc.ListProfiles(ctx, session, offset, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID string) error {
|
func (am *authorizationMiddleware) DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error {
|
||||||
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, removeOperation, auth.AnyIDs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return am.svc.DeleteProfile(ctx, session, profileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *authorizationMiddleware) AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error {
|
||||||
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, updateOperation, configID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return am.svc.AssignProfile(ctx, session, configID, profileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *authorizationMiddleware) BindResources(ctx context.Context, session smqauthn.Session, token, configID string, bindings []bootstrap.BindingRequest) error {
|
||||||
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, updateOperation, configID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return am.svc.BindResources(ctx, session, token, configID, bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *authorizationMiddleware) ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]bootstrap.BindingSnapshot, error) {
|
||||||
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, viewOperation, configID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return am.svc.ListBindings(ctx, session, configID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *authorizationMiddleware) RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error {
|
||||||
|
if err := am.authorize(ctx, session, "", policies.UserType, policies.UsersKind, session.DomainUserID, policies.MembershipPermission, policies.DomainType, session.DomainID, updateOperation, configID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return am.svc.RefreshBindings(ctx, session, token, configID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session smqauthn.Session) error {
|
||||||
|
if session.Role != smqauthn.SuperAdminRole {
|
||||||
|
return svcerr.ErrSuperAdminAction
|
||||||
|
}
|
||||||
if err := am.authz.Authorize(ctx, authz.PolicyReq{
|
if err := am.authz.Authorize(ctx, authz.PolicyReq{
|
||||||
SubjectType: policies.UserType,
|
SubjectType: policies.UserType,
|
||||||
Subject: adminID,
|
Subject: session.UserID,
|
||||||
Permission: policies.AdminPermission,
|
Permission: policies.AdminPermission,
|
||||||
ObjectType: policies.PlatformType,
|
ObjectType: policies.PlatformType,
|
||||||
Object: policies.MagistralaObject,
|
Object: policies.MagistralaObject,
|
||||||
@@ -133,7 +189,7 @@ func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, adminID
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) authorize(ctx context.Context, domain, subjType, subjKind, subj, perm, objType, obj string) error {
|
func (am *authorizationMiddleware) authorize(ctx context.Context, session smqauthn.Session, domain, subjType, subjKind, subj, perm, objType, obj, operation, entityID string) error {
|
||||||
req := authz.PolicyReq{
|
req := authz.PolicyReq{
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
SubjectType: subjType,
|
SubjectType: subjType,
|
||||||
@@ -143,7 +199,20 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, domain, subjTy
|
|||||||
ObjectType: objType,
|
ObjectType: objType,
|
||||||
Object: obj,
|
Object: obj,
|
||||||
}
|
}
|
||||||
if err := am.authz.Authorize(ctx, req, nil); err != nil {
|
|
||||||
|
var pat *authz.PATReq
|
||||||
|
if session.PatID != "" {
|
||||||
|
pat = &authz.PATReq{
|
||||||
|
UserID: session.UserID,
|
||||||
|
PatID: session.PatID,
|
||||||
|
EntityID: entityID,
|
||||||
|
EntityType: auth.BootstrapType.String(),
|
||||||
|
Operation: operation,
|
||||||
|
Domain: session.DomainID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := am.authz.Authorize(ctx, req, pat); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+125
-65
@@ -32,7 +32,7 @@ func (lm *loggingMiddleware) Add(ctx context.Context, session smqauthn.Session,
|
|||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("client_id", saved.ClientID),
|
slog.String("config_id", saved.ID),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
@@ -51,7 +51,7 @@ func (lm *loggingMiddleware) View(ctx context.Context, session smqauthn.Session,
|
|||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("client_id", id),
|
slog.String("config_id", id),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
@@ -71,7 +71,7 @@ func (lm *loggingMiddleware) Update(ctx context.Context, session smqauthn.Sessio
|
|||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.Group("config",
|
slog.Group("config",
|
||||||
slog.String("client_id", cfg.ClientID),
|
slog.String("config_id", cfg.ID),
|
||||||
slog.String("name", cfg.Name),
|
slog.String("name", cfg.Name),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -86,13 +86,13 @@ func (lm *loggingMiddleware) Update(ctx context.Context, session smqauthn.Sessio
|
|||||||
return lm.svc.Update(ctx, session, cfg)
|
return lm.svc.Update(ctx, session, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCert logs the update_cert request. It logs client ID and the time it took to complete the request.
|
// UpdateCert logs the update_cert request. It logs config ID and the time it took to complete the request.
|
||||||
// If the request fails, it logs the error.
|
// If the request fails, it logs the error.
|
||||||
func (lm *loggingMiddleware) UpdateCert(ctx context.Context, session smqauthn.Session, clientID, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) {
|
func (lm *loggingMiddleware) UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("client_id", cfg.ClientID),
|
slog.String("config_id", cfg.ID),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
@@ -102,27 +102,7 @@ func (lm *loggingMiddleware) UpdateCert(ctx context.Context, session smqauthn.Se
|
|||||||
lm.logger.Info("Update bootstrap config certificate completed successfully", args...)
|
lm.logger.Info("Update bootstrap config certificate completed successfully", args...)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return lm.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert)
|
return lm.svc.UpdateCert(ctx, session, id, clientCert, clientKey, caCert)
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateConnections logs the update_connections request. It logs bootstrap ID and the time it took to complete the request.
|
|
||||||
// If the request fails, it logs the error.
|
|
||||||
func (lm *loggingMiddleware) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
|
||||||
args := []any{
|
|
||||||
slog.String("duration", time.Since(begin).String()),
|
|
||||||
slog.String("client_id", id),
|
|
||||||
slog.Any("connections", connections),
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
args = append(args, slog.Any("error", err))
|
|
||||||
lm.logger.Warn("Update config connections failed", args...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lm.logger.Info("Update config connections completed successfully", args...)
|
|
||||||
}(time.Now())
|
|
||||||
|
|
||||||
return lm.svc.UpdateConnections(ctx, session, token, id, connections)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List logs the list request. It logs offset, limit and the time it took to complete the request.
|
// List logs the list request. It logs offset, limit and the time it took to complete the request.
|
||||||
@@ -155,7 +135,7 @@ func (lm *loggingMiddleware) Remove(ctx context.Context, session smqauthn.Sessio
|
|||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("client_id", id),
|
slog.String("config_id", id),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
@@ -175,7 +155,7 @@ func (lm *loggingMiddleware) Bootstrap(ctx context.Context, externalKey, externa
|
|||||||
slog.String("external_id", externalID),
|
slog.String("external_id", externalID),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.String("error", err.Error()))
|
||||||
lm.logger.Warn("View bootstrap config failed", args...)
|
lm.logger.Warn("View bootstrap config failed", args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -185,111 +165,191 @@ func (lm *loggingMiddleware) Bootstrap(ctx context.Context, externalKey, externa
|
|||||||
return lm.svc.Bootstrap(ctx, externalKey, externalID, secure)
|
return lm.svc.Bootstrap(ctx, externalKey, externalID, secure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lm *loggingMiddleware) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) (err error) {
|
func (lm *loggingMiddleware) EnableConfig(ctx context.Context, session smqauthn.Session, id string) (cfg bootstrap.Config, err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("id", id),
|
slog.String("id", id),
|
||||||
slog.Any("state", state),
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
lm.logger.Warn("Change client state failed", args...)
|
lm.logger.Warn("Enable config failed", args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lm.logger.Info("Change client state completed successfully", args...)
|
lm.logger.Info("Enable config completed successfully", args...)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return lm.svc.ChangeState(ctx, session, token, id, state)
|
return lm.svc.EnableConfig(ctx, session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lm *loggingMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) (err error) {
|
func (lm *loggingMiddleware) DisableConfig(ctx context.Context, session smqauthn.Session, id string) (cfg bootstrap.Config, err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.Group("channel",
|
slog.String("id", id),
|
||||||
slog.String("id", channel.ID),
|
|
||||||
slog.String("name", channel.Name),
|
|
||||||
slog.Any("metadata", channel.Metadata),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
lm.logger.Warn("Update channel handler failed", args...)
|
lm.logger.Warn("Disable config failed", args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lm.logger.Info("Update channel handler completed successfully", args...)
|
lm.logger.Info("Disable config completed successfully", args...)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return lm.svc.UpdateChannelHandler(ctx, channel)
|
return lm.svc.DisableConfig(ctx, session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lm *loggingMiddleware) RemoveConfigHandler(ctx context.Context, id string) (err error) {
|
func (lm *loggingMiddleware) CreateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) (saved bootstrap.Profile, err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("config_id", id),
|
slog.String("profile_id", saved.ID),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
lm.logger.Warn("Remove config handler failed", args...)
|
lm.logger.Warn("Create profile failed", args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lm.logger.Info("Remove config handler completed successfully", args...)
|
lm.logger.Info("Create profile completed successfully", args...)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return lm.svc.RemoveConfigHandler(ctx, id)
|
return lm.svc.CreateProfile(ctx, session, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lm *loggingMiddleware) RemoveChannelHandler(ctx context.Context, id string) (err error) {
|
func (lm *loggingMiddleware) ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (p bootstrap.Profile, err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("channel_id", id),
|
slog.String("profile_id", profileID),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
lm.logger.Warn("Remove channel handler failed", args...)
|
lm.logger.Warn("View profile failed", args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lm.logger.Info("Remove channel handler completed successfully", args...)
|
lm.logger.Info("View profile completed successfully", args...)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return lm.svc.RemoveChannelHandler(ctx, id)
|
return lm.svc.ViewProfile(ctx, session, profileID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lm *loggingMiddleware) ConnectClientHandler(ctx context.Context, channelID, clientID string) (err error) {
|
func (lm *loggingMiddleware) UpdateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) (err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("channel_id", channelID),
|
slog.String("profile_id", p.ID),
|
||||||
slog.String("client_id", clientID),
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
lm.logger.Warn("Connect client handler failed", args...)
|
lm.logger.Warn("Update profile failed", args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lm.logger.Info("Connect client handler completed successfully", args...)
|
lm.logger.Info("Update profile completed successfully", args...)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return lm.svc.ConnectClientHandler(ctx, channelID, clientID)
|
return lm.svc.UpdateProfile(ctx, session, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lm *loggingMiddleware) DisconnectClientHandler(ctx context.Context, channelID, clientID string) (err error) {
|
func (lm *loggingMiddleware) ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64) (page bootstrap.ProfilesPage, err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
args := []any{
|
args := []any{
|
||||||
slog.String("duration", time.Since(begin).String()),
|
slog.String("duration", time.Since(begin).String()),
|
||||||
slog.String("channel_id", channelID),
|
slog.Uint64("offset", offset),
|
||||||
slog.String("client_id", clientID),
|
slog.Uint64("limit", limit),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, slog.Any("error", err))
|
args = append(args, slog.Any("error", err))
|
||||||
lm.logger.Warn("Disconnect client handler failed", args...)
|
lm.logger.Warn("List profiles failed", args...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lm.logger.Info("Disconnect client handler completed successfully", args...)
|
lm.logger.Info("List profiles completed successfully", args...)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return lm.svc.DisconnectClientHandler(ctx, channelID, clientID)
|
return lm.svc.ListProfiles(ctx, session, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *loggingMiddleware) DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) (err error) {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
args := []any{
|
||||||
|
slog.String("duration", time.Since(begin).String()),
|
||||||
|
slog.String("profile_id", profileID),
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
args = append(args, slog.Any("error", err))
|
||||||
|
lm.logger.Warn("Delete profile failed", args...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm.logger.Info("Delete profile completed successfully", args...)
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
|
return lm.svc.DeleteProfile(ctx, session, profileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *loggingMiddleware) AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) (err error) {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
args := []any{
|
||||||
|
slog.String("duration", time.Since(begin).String()),
|
||||||
|
slog.String("config_id", configID),
|
||||||
|
slog.String("profile_id", profileID),
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
args = append(args, slog.Any("error", err))
|
||||||
|
lm.logger.Warn("Assign profile failed", args...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm.logger.Info("Assign profile completed successfully", args...)
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
|
return lm.svc.AssignProfile(ctx, session, configID, profileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *loggingMiddleware) BindResources(ctx context.Context, session smqauthn.Session, token, configID string, bindings []bootstrap.BindingRequest) (err error) {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
args := []any{
|
||||||
|
slog.String("duration", time.Since(begin).String()),
|
||||||
|
slog.String("config_id", configID),
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
args = append(args, slog.Any("error", err))
|
||||||
|
lm.logger.Warn("Bind resources failed", args...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm.logger.Info("Bind resources completed successfully", args...)
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
|
return lm.svc.BindResources(ctx, session, token, configID, bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *loggingMiddleware) ListBindings(ctx context.Context, session smqauthn.Session, configID string) (snapshots []bootstrap.BindingSnapshot, err error) {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
args := []any{
|
||||||
|
slog.String("duration", time.Since(begin).String()),
|
||||||
|
slog.String("config_id", configID),
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
args = append(args, slog.Any("error", err))
|
||||||
|
lm.logger.Warn("List bindings failed", args...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm.logger.Info("List bindings completed successfully", args...)
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
|
return lm.svc.ListBindings(ctx, session, configID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *loggingMiddleware) RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) (err error) {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
args := []any{
|
||||||
|
slog.String("duration", time.Since(begin).String()),
|
||||||
|
slog.String("config_id", configID),
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
args = append(args, slog.Any("error", err))
|
||||||
|
lm.logger.Warn("Refresh bindings failed", args...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm.logger.Info("Refresh bindings completed successfully", args...)
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
|
return lm.svc.RefreshBindings(ctx, session, token, configID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,23 +62,13 @@ func (mm *metricsMiddleware) Update(ctx context.Context, session smqauthn.Sessio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCert instruments UpdateCert method with metrics.
|
// UpdateCert instruments UpdateCert method with metrics.
|
||||||
func (mm *metricsMiddleware) UpdateCert(ctx context.Context, session smqauthn.Session, clientID, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) {
|
func (mm *metricsMiddleware) UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (cfg bootstrap.Config, err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
mm.counter.With("method", "update_cert").Add(1)
|
mm.counter.With("method", "update_cert").Add(1)
|
||||||
mm.latency.With("method", "update_cert").Observe(time.Since(begin).Seconds())
|
mm.latency.With("method", "update_cert").Observe(time.Since(begin).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return mm.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert)
|
return mm.svc.UpdateCert(ctx, session, id, clientCert, clientKey, caCert)
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateConnections instruments UpdateConnections method with metrics.
|
|
||||||
func (mm *metricsMiddleware) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
|
||||||
mm.counter.With("method", "update_connections").Add(1)
|
|
||||||
mm.latency.With("method", "update_connections").Observe(time.Since(begin).Seconds())
|
|
||||||
}(time.Now())
|
|
||||||
|
|
||||||
return mm.svc.UpdateConnections(ctx, session, token, id, connections)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List instruments List method with metrics.
|
// List instruments List method with metrics.
|
||||||
@@ -111,62 +101,92 @@ func (mm *metricsMiddleware) Bootstrap(ctx context.Context, externalKey, externa
|
|||||||
return mm.svc.Bootstrap(ctx, externalKey, externalID, secure)
|
return mm.svc.Bootstrap(ctx, externalKey, externalID, secure)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeState instruments ChangeState method with metrics.
|
func (mm *metricsMiddleware) EnableConfig(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
func (mm *metricsMiddleware) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
mm.counter.With("method", "change_state").Add(1)
|
mm.counter.With("method", "enable_config").Add(1)
|
||||||
mm.latency.With("method", "change_state").Observe(time.Since(begin).Seconds())
|
mm.latency.With("method", "enable_config").Observe(time.Since(begin).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return mm.svc.ChangeState(ctx, session, token, id, state)
|
return mm.svc.EnableConfig(ctx, session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateChannelHandler instruments UpdateChannelHandler method with metrics.
|
func (mm *metricsMiddleware) DisableConfig(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
func (mm *metricsMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
mm.counter.With("method", "update_channel").Add(1)
|
mm.counter.With("method", "disable_config").Add(1)
|
||||||
mm.latency.With("method", "update_channel").Observe(time.Since(begin).Seconds())
|
mm.latency.With("method", "disable_config").Observe(time.Since(begin).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
return mm.svc.UpdateChannelHandler(ctx, channel)
|
return mm.svc.DisableConfig(ctx, session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveConfigHandler instruments RemoveConfigHandler method with metrics.
|
func (mm *metricsMiddleware) CreateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) (bootstrap.Profile, error) {
|
||||||
func (mm *metricsMiddleware) RemoveConfigHandler(ctx context.Context, id string) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
mm.counter.With("method", "remove_config").Add(1)
|
mm.counter.With("method", "create_profile").Add(1)
|
||||||
mm.latency.With("method", "remove_config").Observe(time.Since(begin).Seconds())
|
mm.latency.With("method", "create_profile").Observe(time.Since(begin).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
return mm.svc.CreateProfile(ctx, session, p)
|
||||||
return mm.svc.RemoveConfigHandler(ctx, id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveChannelHandler instruments RemoveChannelHandler method with metrics.
|
func (mm *metricsMiddleware) ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (bootstrap.Profile, error) {
|
||||||
func (mm *metricsMiddleware) RemoveChannelHandler(ctx context.Context, id string) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
mm.counter.With("method", "remove_channel").Add(1)
|
mm.counter.With("method", "view_profile").Add(1)
|
||||||
mm.latency.With("method", "remove_channel").Observe(time.Since(begin).Seconds())
|
mm.latency.With("method", "view_profile").Observe(time.Since(begin).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
return mm.svc.ViewProfile(ctx, session, profileID)
|
||||||
return mm.svc.RemoveChannelHandler(ctx, id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectClientHandler instruments ConnectClientHandler method with metrics.
|
func (mm *metricsMiddleware) UpdateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) error {
|
||||||
func (mm *metricsMiddleware) ConnectClientHandler(ctx context.Context, channelID, clientID string) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
mm.counter.With("method", "connect_client_handler").Add(1)
|
mm.counter.With("method", "update_profile").Add(1)
|
||||||
mm.latency.With("method", "connect_client_handler").Observe(time.Since(begin).Seconds())
|
mm.latency.With("method", "update_profile").Observe(time.Since(begin).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
return mm.svc.UpdateProfile(ctx, session, p)
|
||||||
return mm.svc.ConnectClientHandler(ctx, channelID, clientID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisconnectClientHandler instruments DisconnectClientHandler method with metrics.
|
func (mm *metricsMiddleware) ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64) (bootstrap.ProfilesPage, error) {
|
||||||
func (mm *metricsMiddleware) DisconnectClientHandler(ctx context.Context, channelID, clientID string) (err error) {
|
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
mm.counter.With("method", "disconnect_client_handler").Add(1)
|
mm.counter.With("method", "list_profiles").Add(1)
|
||||||
mm.latency.With("method", "disconnect_client_handler").Observe(time.Since(begin).Seconds())
|
mm.latency.With("method", "list_profiles").Observe(time.Since(begin).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
return mm.svc.ListProfiles(ctx, session, offset, limit)
|
||||||
return mm.svc.DisconnectClientHandler(ctx, channelID, clientID)
|
}
|
||||||
|
|
||||||
|
func (mm *metricsMiddleware) DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
mm.counter.With("method", "delete_profile").Add(1)
|
||||||
|
mm.latency.With("method", "delete_profile").Observe(time.Since(begin).Seconds())
|
||||||
|
}(time.Now())
|
||||||
|
return mm.svc.DeleteProfile(ctx, session, profileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm *metricsMiddleware) AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
mm.counter.With("method", "assign_profile").Add(1)
|
||||||
|
mm.latency.With("method", "assign_profile").Observe(time.Since(begin).Seconds())
|
||||||
|
}(time.Now())
|
||||||
|
return mm.svc.AssignProfile(ctx, session, configID, profileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm *metricsMiddleware) BindResources(ctx context.Context, session smqauthn.Session, token, configID string, bindings []bootstrap.BindingRequest) error {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
mm.counter.With("method", "bind_resources").Add(1)
|
||||||
|
mm.latency.With("method", "bind_resources").Observe(time.Since(begin).Seconds())
|
||||||
|
}(time.Now())
|
||||||
|
return mm.svc.BindResources(ctx, session, token, configID, bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm *metricsMiddleware) ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]bootstrap.BindingSnapshot, error) {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
mm.counter.With("method", "list_bindings").Add(1)
|
||||||
|
mm.latency.With("method", "list_bindings").Observe(time.Since(begin).Seconds())
|
||||||
|
}(time.Now())
|
||||||
|
return mm.svc.ListBindings(ctx, session, configID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm *metricsMiddleware) RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error {
|
||||||
|
defer func(begin time.Time) {
|
||||||
|
mm.counter.With("method", "refresh_bindings").Add(1)
|
||||||
|
mm.latency.With("method", "refresh_bindings").Observe(time.Since(begin).Seconds())
|
||||||
|
}(time.Now())
|
||||||
|
return mm.svc.RefreshBindings(ctx, session, token, configID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Code generated by mockery; DO NOT EDIT.
|
||||||
|
// github.com/vektra/mockery
|
||||||
|
// template: testify
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewBindingResolver creates a new instance of BindingResolver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewBindingResolver(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *BindingResolver {
|
||||||
|
mock := &BindingResolver{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingResolver is an autogenerated mock type for the BindingResolver type
|
||||||
|
type BindingResolver struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type BindingResolver_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *BindingResolver) EXPECT() *BindingResolver_Expecter {
|
||||||
|
return &BindingResolver_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve provides a mock function for the type BindingResolver
|
||||||
|
func (_mock *BindingResolver) Resolve(ctx context.Context, req bootstrap.ResolveRequest) ([]bootstrap.BindingSnapshot, error) {
|
||||||
|
ret := _mock.Called(ctx, req)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Resolve")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 []bootstrap.BindingSnapshot
|
||||||
|
var r1 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.ResolveRequest) ([]bootstrap.BindingSnapshot, error)); ok {
|
||||||
|
return returnFunc(ctx, req)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.ResolveRequest) []bootstrap.BindingSnapshot); ok {
|
||||||
|
r0 = returnFunc(ctx, req)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]bootstrap.BindingSnapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, bootstrap.ResolveRequest) error); ok {
|
||||||
|
r1 = returnFunc(ctx, req)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingResolver_Resolve_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Resolve'
|
||||||
|
type BindingResolver_Resolve_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - req bootstrap.ResolveRequest
|
||||||
|
func (_e *BindingResolver_Expecter) Resolve(ctx interface{}, req interface{}) *BindingResolver_Resolve_Call {
|
||||||
|
return &BindingResolver_Resolve_Call{Call: _e.mock.On("Resolve", ctx, req)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingResolver_Resolve_Call) Run(run func(ctx context.Context, req bootstrap.ResolveRequest)) *BindingResolver_Resolve_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 bootstrap.ResolveRequest
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(bootstrap.ResolveRequest)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingResolver_Resolve_Call) Return(bindingSnapshots []bootstrap.BindingSnapshot, err error) *BindingResolver_Resolve_Call {
|
||||||
|
_c.Call.Return(bindingSnapshots, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingResolver_Resolve_Call) RunAndReturn(run func(ctx context.Context, req bootstrap.ResolveRequest) ([]bootstrap.BindingSnapshot, error)) *BindingResolver_Resolve_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Code generated by mockery; DO NOT EDIT.
|
||||||
|
// github.com/vektra/mockery
|
||||||
|
// template: testify
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewBindingStore creates a new instance of BindingStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewBindingStore(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *BindingStore {
|
||||||
|
mock := &BindingStore{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingStore is an autogenerated mock type for the BindingStore type
|
||||||
|
type BindingStore struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type BindingStore_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *BindingStore) EXPECT() *BindingStore_Expecter {
|
||||||
|
return &BindingStore_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete provides a mock function for the type BindingStore
|
||||||
|
func (_mock *BindingStore) Delete(ctx context.Context, configID string, slot string) error {
|
||||||
|
ret := _mock.Called(ctx, configID, slot)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
||||||
|
r0 = returnFunc(ctx, configID, slot)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingStore_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||||
|
type BindingStore_Delete_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - configID string
|
||||||
|
// - slot string
|
||||||
|
func (_e *BindingStore_Expecter) Delete(ctx interface{}, configID interface{}, slot interface{}) *BindingStore_Delete_Call {
|
||||||
|
return &BindingStore_Delete_Call{Call: _e.mock.On("Delete", ctx, configID, slot)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Delete_Call) Run(run func(ctx context.Context, configID string, slot string)) *BindingStore_Delete_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Delete_Call) Return(err error) *BindingStore_Delete_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Delete_Call) RunAndReturn(run func(ctx context.Context, configID string, slot string) error) *BindingStore_Delete_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve provides a mock function for the type BindingStore
|
||||||
|
func (_mock *BindingStore) Retrieve(ctx context.Context, configID string) ([]bootstrap.BindingSnapshot, error) {
|
||||||
|
ret := _mock.Called(ctx, configID)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Retrieve")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 []bootstrap.BindingSnapshot
|
||||||
|
var r1 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string) ([]bootstrap.BindingSnapshot, error)); ok {
|
||||||
|
return returnFunc(ctx, configID)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string) []bootstrap.BindingSnapshot); ok {
|
||||||
|
r0 = returnFunc(ctx, configID)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]bootstrap.BindingSnapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||||
|
r1 = returnFunc(ctx, configID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingStore_Retrieve_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Retrieve'
|
||||||
|
type BindingStore_Retrieve_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - configID string
|
||||||
|
func (_e *BindingStore_Expecter) Retrieve(ctx interface{}, configID interface{}) *BindingStore_Retrieve_Call {
|
||||||
|
return &BindingStore_Retrieve_Call{Call: _e.mock.On("Retrieve", ctx, configID)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Retrieve_Call) Run(run func(ctx context.Context, configID string)) *BindingStore_Retrieve_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Retrieve_Call) Return(bindingSnapshots []bootstrap.BindingSnapshot, err error) *BindingStore_Retrieve_Call {
|
||||||
|
_c.Call.Return(bindingSnapshots, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Retrieve_Call) RunAndReturn(run func(ctx context.Context, configID string) ([]bootstrap.BindingSnapshot, error)) *BindingStore_Retrieve_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save provides a mock function for the type BindingStore
|
||||||
|
func (_mock *BindingStore) Save(ctx context.Context, configID string, bindings []bootstrap.BindingSnapshot) error {
|
||||||
|
ret := _mock.Called(ctx, configID, bindings)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Save")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, []bootstrap.BindingSnapshot) error); ok {
|
||||||
|
r0 = returnFunc(ctx, configID, bindings)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingStore_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
|
||||||
|
type BindingStore_Save_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - configID string
|
||||||
|
// - bindings []bootstrap.BindingSnapshot
|
||||||
|
func (_e *BindingStore_Expecter) Save(ctx interface{}, configID interface{}, bindings interface{}) *BindingStore_Save_Call {
|
||||||
|
return &BindingStore_Save_Call{Call: _e.mock.On("Save", ctx, configID, bindings)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Save_Call) Run(run func(ctx context.Context, configID string, bindings []bootstrap.BindingSnapshot)) *BindingStore_Save_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 []bootstrap.BindingSnapshot
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].([]bootstrap.BindingSnapshot)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Save_Call) Return(err error) *BindingStore_Save_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *BindingStore_Save_Call) RunAndReturn(run func(ctx context.Context, configID string, bindings []bootstrap.BindingSnapshot) error) *BindingStore_Save_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
@@ -42,38 +42,38 @@ func (_m *ConfigRepository) EXPECT() *ConfigRepository_Expecter {
|
|||||||
return &ConfigRepository_Expecter{mock: &_m.Mock}
|
return &ConfigRepository_Expecter{mock: &_m.Mock}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeState provides a mock function for the type ConfigRepository
|
// AssignProfile provides a mock function for the type ConfigRepository
|
||||||
func (_mock *ConfigRepository) ChangeState(ctx context.Context, domainID string, id string, state bootstrap.State) error {
|
func (_mock *ConfigRepository) AssignProfile(ctx context.Context, domainID string, id string, profileID string) error {
|
||||||
ret := _mock.Called(ctx, domainID, id, state)
|
ret := _mock.Called(ctx, domainID, id, profileID)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for ChangeState")
|
panic("no return value specified for AssignProfile")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, bootstrap.State) error); ok {
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
|
||||||
r0 = returnFunc(ctx, domainID, id, state)
|
r0 = returnFunc(ctx, domainID, id, profileID)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigRepository_ChangeState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeState'
|
// ConfigRepository_AssignProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AssignProfile'
|
||||||
type ConfigRepository_ChangeState_Call struct {
|
type ConfigRepository_AssignProfile_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeState is a helper method to define mock.On call
|
// AssignProfile is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - domainID string
|
// - domainID string
|
||||||
// - id string
|
// - id string
|
||||||
// - state bootstrap.State
|
// - profileID string
|
||||||
func (_e *ConfigRepository_Expecter) ChangeState(ctx interface{}, domainID interface{}, id interface{}, state interface{}) *ConfigRepository_ChangeState_Call {
|
func (_e *ConfigRepository_Expecter) AssignProfile(ctx interface{}, domainID interface{}, id interface{}, profileID interface{}) *ConfigRepository_AssignProfile_Call {
|
||||||
return &ConfigRepository_ChangeState_Call{Call: _e.mock.On("ChangeState", ctx, domainID, id, state)}
|
return &ConfigRepository_AssignProfile_Call{Call: _e.mock.On("AssignProfile", ctx, domainID, id, profileID)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_ChangeState_Call) Run(run func(ctx context.Context, domainID string, id string, state bootstrap.State)) *ConfigRepository_ChangeState_Call {
|
func (_c *ConfigRepository_AssignProfile_Call) Run(run func(ctx context.Context, domainID string, id string, profileID string)) *ConfigRepository_AssignProfile_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
var arg0 context.Context
|
var arg0 context.Context
|
||||||
if args[0] != nil {
|
if args[0] != nil {
|
||||||
@@ -87,9 +87,9 @@ func (_c *ConfigRepository_ChangeState_Call) Run(run func(ctx context.Context, d
|
|||||||
if args[2] != nil {
|
if args[2] != nil {
|
||||||
arg2 = args[2].(string)
|
arg2 = args[2].(string)
|
||||||
}
|
}
|
||||||
var arg3 bootstrap.State
|
var arg3 string
|
||||||
if args[3] != nil {
|
if args[3] != nil {
|
||||||
arg3 = args[3].(bootstrap.State)
|
arg3 = args[3].(string)
|
||||||
}
|
}
|
||||||
run(
|
run(
|
||||||
arg0,
|
arg0,
|
||||||
@@ -101,184 +101,48 @@ func (_c *ConfigRepository_ChangeState_Call) Run(run func(ctx context.Context, d
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_ChangeState_Call) Return(err error) *ConfigRepository_ChangeState_Call {
|
func (_c *ConfigRepository_AssignProfile_Call) Return(err error) *ConfigRepository_AssignProfile_Call {
|
||||||
_c.Call.Return(err)
|
_c.Call.Return(err)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_ChangeState_Call) RunAndReturn(run func(ctx context.Context, domainID string, id string, state bootstrap.State) error) *ConfigRepository_ChangeState_Call {
|
func (_c *ConfigRepository_AssignProfile_Call) RunAndReturn(run func(ctx context.Context, domainID string, id string, profileID string) error) *ConfigRepository_AssignProfile_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectClient provides a mock function for the type ConfigRepository
|
// ChangeStatus provides a mock function for the type ConfigRepository
|
||||||
func (_mock *ConfigRepository) ConnectClient(ctx context.Context, channelID string, clientID string) error {
|
func (_mock *ConfigRepository) ChangeStatus(ctx context.Context, domainID string, id string, status bootstrap.Status) error {
|
||||||
ret := _mock.Called(ctx, channelID, clientID)
|
ret := _mock.Called(ctx, domainID, id, status)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for ConnectClient")
|
panic("no return value specified for ChangeStatus")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, bootstrap.Status) error); ok {
|
||||||
r0 = returnFunc(ctx, channelID, clientID)
|
r0 = returnFunc(ctx, domainID, id, status)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigRepository_ConnectClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ConnectClient'
|
// ConfigRepository_ChangeStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeStatus'
|
||||||
type ConfigRepository_ConnectClient_Call struct {
|
type ConfigRepository_ChangeStatus_Call struct {
|
||||||
*mock.Call
|
*mock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectClient is a helper method to define mock.On call
|
// ChangeStatus is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
|
||||||
// - channelID string
|
|
||||||
// - clientID string
|
|
||||||
func (_e *ConfigRepository_Expecter) ConnectClient(ctx interface{}, channelID interface{}, clientID interface{}) *ConfigRepository_ConnectClient_Call {
|
|
||||||
return &ConfigRepository_ConnectClient_Call{Call: _e.mock.On("ConnectClient", ctx, channelID, clientID)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_ConnectClient_Call) Run(run func(ctx context.Context, channelID string, clientID string)) *ConfigRepository_ConnectClient_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
var arg0 context.Context
|
|
||||||
if args[0] != nil {
|
|
||||||
arg0 = args[0].(context.Context)
|
|
||||||
}
|
|
||||||
var arg1 string
|
|
||||||
if args[1] != nil {
|
|
||||||
arg1 = args[1].(string)
|
|
||||||
}
|
|
||||||
var arg2 string
|
|
||||||
if args[2] != nil {
|
|
||||||
arg2 = args[2].(string)
|
|
||||||
}
|
|
||||||
run(
|
|
||||||
arg0,
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_ConnectClient_Call) Return(err error) *ConfigRepository_ConnectClient_Call {
|
|
||||||
_c.Call.Return(err)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_ConnectClient_Call) RunAndReturn(run func(ctx context.Context, channelID string, clientID string) error) *ConfigRepository_ConnectClient_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisconnectClient provides a mock function for the type ConfigRepository
|
|
||||||
func (_mock *ConfigRepository) DisconnectClient(ctx context.Context, channelID string, clientID string) error {
|
|
||||||
ret := _mock.Called(ctx, channelID, clientID)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for DisconnectClient")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
|
||||||
r0 = returnFunc(ctx, channelID, clientID)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigRepository_DisconnectClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectClient'
|
|
||||||
type ConfigRepository_DisconnectClient_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisconnectClient is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - channelID string
|
|
||||||
// - clientID string
|
|
||||||
func (_e *ConfigRepository_Expecter) DisconnectClient(ctx interface{}, channelID interface{}, clientID interface{}) *ConfigRepository_DisconnectClient_Call {
|
|
||||||
return &ConfigRepository_DisconnectClient_Call{Call: _e.mock.On("DisconnectClient", ctx, channelID, clientID)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_DisconnectClient_Call) Run(run func(ctx context.Context, channelID string, clientID string)) *ConfigRepository_DisconnectClient_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
var arg0 context.Context
|
|
||||||
if args[0] != nil {
|
|
||||||
arg0 = args[0].(context.Context)
|
|
||||||
}
|
|
||||||
var arg1 string
|
|
||||||
if args[1] != nil {
|
|
||||||
arg1 = args[1].(string)
|
|
||||||
}
|
|
||||||
var arg2 string
|
|
||||||
if args[2] != nil {
|
|
||||||
arg2 = args[2].(string)
|
|
||||||
}
|
|
||||||
run(
|
|
||||||
arg0,
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_DisconnectClient_Call) Return(err error) *ConfigRepository_DisconnectClient_Call {
|
|
||||||
_c.Call.Return(err)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_DisconnectClient_Call) RunAndReturn(run func(ctx context.Context, channelID string, clientID string) error) *ConfigRepository_DisconnectClient_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListExisting provides a mock function for the type ConfigRepository
|
|
||||||
func (_mock *ConfigRepository) ListExisting(ctx context.Context, domainID string, ids []string) ([]bootstrap.Channel, error) {
|
|
||||||
ret := _mock.Called(ctx, domainID, ids)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for ListExisting")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 []bootstrap.Channel
|
|
||||||
var r1 error
|
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, []string) ([]bootstrap.Channel, error)); ok {
|
|
||||||
return returnFunc(ctx, domainID, ids)
|
|
||||||
}
|
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, []string) []bootstrap.Channel); ok {
|
|
||||||
r0 = returnFunc(ctx, domainID, ids)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).([]bootstrap.Channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, []string) error); ok {
|
|
||||||
r1 = returnFunc(ctx, domainID, ids)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigRepository_ListExisting_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListExisting'
|
|
||||||
type ConfigRepository_ListExisting_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListExisting is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - domainID string
|
// - domainID string
|
||||||
// - ids []string
|
// - id string
|
||||||
func (_e *ConfigRepository_Expecter) ListExisting(ctx interface{}, domainID interface{}, ids interface{}) *ConfigRepository_ListExisting_Call {
|
// - status bootstrap.Status
|
||||||
return &ConfigRepository_ListExisting_Call{Call: _e.mock.On("ListExisting", ctx, domainID, ids)}
|
func (_e *ConfigRepository_Expecter) ChangeStatus(ctx interface{}, domainID interface{}, id interface{}, status interface{}) *ConfigRepository_ChangeStatus_Call {
|
||||||
|
return &ConfigRepository_ChangeStatus_Call{Call: _e.mock.On("ChangeStatus", ctx, domainID, id, status)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_ListExisting_Call) Run(run func(ctx context.Context, domainID string, ids []string)) *ConfigRepository_ListExisting_Call {
|
func (_c *ConfigRepository_ChangeStatus_Call) Run(run func(ctx context.Context, domainID string, id string, status bootstrap.Status)) *ConfigRepository_ChangeStatus_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
var arg0 context.Context
|
var arg0 context.Context
|
||||||
if args[0] != nil {
|
if args[0] != nil {
|
||||||
@@ -288,25 +152,30 @@ func (_c *ConfigRepository_ListExisting_Call) Run(run func(ctx context.Context,
|
|||||||
if args[1] != nil {
|
if args[1] != nil {
|
||||||
arg1 = args[1].(string)
|
arg1 = args[1].(string)
|
||||||
}
|
}
|
||||||
var arg2 []string
|
var arg2 string
|
||||||
if args[2] != nil {
|
if args[2] != nil {
|
||||||
arg2 = args[2].([]string)
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 bootstrap.Status
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(bootstrap.Status)
|
||||||
}
|
}
|
||||||
run(
|
run(
|
||||||
arg0,
|
arg0,
|
||||||
arg1,
|
arg1,
|
||||||
arg2,
|
arg2,
|
||||||
|
arg3,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_ListExisting_Call) Return(channels []bootstrap.Channel, err error) *ConfigRepository_ListExisting_Call {
|
func (_c *ConfigRepository_ChangeStatus_Call) Return(err error) *ConfigRepository_ChangeStatus_Call {
|
||||||
_c.Call.Return(channels, err)
|
_c.Call.Return(err)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_ListExisting_Call) RunAndReturn(run func(ctx context.Context, domainID string, ids []string) ([]bootstrap.Channel, error)) *ConfigRepository_ListExisting_Call {
|
func (_c *ConfigRepository_ChangeStatus_Call) RunAndReturn(run func(ctx context.Context, domainID string, id string, status bootstrap.Status) error) *ConfigRepository_ChangeStatus_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -374,131 +243,17 @@ func (_c *ConfigRepository_Remove_Call) RunAndReturn(run func(ctx context.Contex
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveChannel provides a mock function for the type ConfigRepository
|
|
||||||
func (_mock *ConfigRepository) RemoveChannel(ctx context.Context, id string) error {
|
|
||||||
ret := _mock.Called(ctx, id)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for RemoveChannel")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
|
||||||
r0 = returnFunc(ctx, id)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigRepository_RemoveChannel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveChannel'
|
|
||||||
type ConfigRepository_RemoveChannel_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveChannel is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - id string
|
|
||||||
func (_e *ConfigRepository_Expecter) RemoveChannel(ctx interface{}, id interface{}) *ConfigRepository_RemoveChannel_Call {
|
|
||||||
return &ConfigRepository_RemoveChannel_Call{Call: _e.mock.On("RemoveChannel", ctx, id)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_RemoveChannel_Call) Run(run func(ctx context.Context, id string)) *ConfigRepository_RemoveChannel_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
var arg0 context.Context
|
|
||||||
if args[0] != nil {
|
|
||||||
arg0 = args[0].(context.Context)
|
|
||||||
}
|
|
||||||
var arg1 string
|
|
||||||
if args[1] != nil {
|
|
||||||
arg1 = args[1].(string)
|
|
||||||
}
|
|
||||||
run(
|
|
||||||
arg0,
|
|
||||||
arg1,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_RemoveChannel_Call) Return(err error) *ConfigRepository_RemoveChannel_Call {
|
|
||||||
_c.Call.Return(err)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_RemoveChannel_Call) RunAndReturn(run func(ctx context.Context, id string) error) *ConfigRepository_RemoveChannel_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveClient provides a mock function for the type ConfigRepository
|
|
||||||
func (_mock *ConfigRepository) RemoveClient(ctx context.Context, id string) error {
|
|
||||||
ret := _mock.Called(ctx, id)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for RemoveClient")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
|
||||||
r0 = returnFunc(ctx, id)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigRepository_RemoveClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveClient'
|
|
||||||
type ConfigRepository_RemoveClient_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveClient is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - id string
|
|
||||||
func (_e *ConfigRepository_Expecter) RemoveClient(ctx interface{}, id interface{}) *ConfigRepository_RemoveClient_Call {
|
|
||||||
return &ConfigRepository_RemoveClient_Call{Call: _e.mock.On("RemoveClient", ctx, id)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_RemoveClient_Call) Run(run func(ctx context.Context, id string)) *ConfigRepository_RemoveClient_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
var arg0 context.Context
|
|
||||||
if args[0] != nil {
|
|
||||||
arg0 = args[0].(context.Context)
|
|
||||||
}
|
|
||||||
var arg1 string
|
|
||||||
if args[1] != nil {
|
|
||||||
arg1 = args[1].(string)
|
|
||||||
}
|
|
||||||
run(
|
|
||||||
arg0,
|
|
||||||
arg1,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_RemoveClient_Call) Return(err error) *ConfigRepository_RemoveClient_Call {
|
|
||||||
_c.Call.Return(err)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_RemoveClient_Call) RunAndReturn(run func(ctx context.Context, id string) error) *ConfigRepository_RemoveClient_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetrieveAll provides a mock function for the type ConfigRepository
|
// RetrieveAll provides a mock function for the type ConfigRepository
|
||||||
func (_mock *ConfigRepository) RetrieveAll(ctx context.Context, domainID string, clientIDs []string, filter bootstrap.Filter, offset uint64, limit uint64) bootstrap.ConfigsPage {
|
func (_mock *ConfigRepository) RetrieveAll(ctx context.Context, domainID string, filter bootstrap.Filter, offset uint64, limit uint64) bootstrap.ConfigsPage {
|
||||||
ret := _mock.Called(ctx, domainID, clientIDs, filter, offset, limit)
|
ret := _mock.Called(ctx, domainID, filter, offset, limit)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for RetrieveAll")
|
panic("no return value specified for RetrieveAll")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 bootstrap.ConfigsPage
|
var r0 bootstrap.ConfigsPage
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, []string, bootstrap.Filter, uint64, uint64) bootstrap.ConfigsPage); ok {
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, bootstrap.Filter, uint64, uint64) bootstrap.ConfigsPage); ok {
|
||||||
r0 = returnFunc(ctx, domainID, clientIDs, filter, offset, limit)
|
r0 = returnFunc(ctx, domainID, filter, offset, limit)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(bootstrap.ConfigsPage)
|
r0 = ret.Get(0).(bootstrap.ConfigsPage)
|
||||||
}
|
}
|
||||||
@@ -513,15 +268,14 @@ type ConfigRepository_RetrieveAll_Call struct {
|
|||||||
// RetrieveAll is a helper method to define mock.On call
|
// RetrieveAll is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - domainID string
|
// - domainID string
|
||||||
// - clientIDs []string
|
|
||||||
// - filter bootstrap.Filter
|
// - filter bootstrap.Filter
|
||||||
// - offset uint64
|
// - offset uint64
|
||||||
// - limit uint64
|
// - limit uint64
|
||||||
func (_e *ConfigRepository_Expecter) RetrieveAll(ctx interface{}, domainID interface{}, clientIDs interface{}, filter interface{}, offset interface{}, limit interface{}) *ConfigRepository_RetrieveAll_Call {
|
func (_e *ConfigRepository_Expecter) RetrieveAll(ctx interface{}, domainID interface{}, filter interface{}, offset interface{}, limit interface{}) *ConfigRepository_RetrieveAll_Call {
|
||||||
return &ConfigRepository_RetrieveAll_Call{Call: _e.mock.On("RetrieveAll", ctx, domainID, clientIDs, filter, offset, limit)}
|
return &ConfigRepository_RetrieveAll_Call{Call: _e.mock.On("RetrieveAll", ctx, domainID, filter, offset, limit)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_RetrieveAll_Call) Run(run func(ctx context.Context, domainID string, clientIDs []string, filter bootstrap.Filter, offset uint64, limit uint64)) *ConfigRepository_RetrieveAll_Call {
|
func (_c *ConfigRepository_RetrieveAll_Call) Run(run func(ctx context.Context, domainID string, filter bootstrap.Filter, offset uint64, limit uint64)) *ConfigRepository_RetrieveAll_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
var arg0 context.Context
|
var arg0 context.Context
|
||||||
if args[0] != nil {
|
if args[0] != nil {
|
||||||
@@ -531,29 +285,24 @@ func (_c *ConfigRepository_RetrieveAll_Call) Run(run func(ctx context.Context, d
|
|||||||
if args[1] != nil {
|
if args[1] != nil {
|
||||||
arg1 = args[1].(string)
|
arg1 = args[1].(string)
|
||||||
}
|
}
|
||||||
var arg2 []string
|
var arg2 bootstrap.Filter
|
||||||
if args[2] != nil {
|
if args[2] != nil {
|
||||||
arg2 = args[2].([]string)
|
arg2 = args[2].(bootstrap.Filter)
|
||||||
}
|
}
|
||||||
var arg3 bootstrap.Filter
|
var arg3 uint64
|
||||||
if args[3] != nil {
|
if args[3] != nil {
|
||||||
arg3 = args[3].(bootstrap.Filter)
|
arg3 = args[3].(uint64)
|
||||||
}
|
}
|
||||||
var arg4 uint64
|
var arg4 uint64
|
||||||
if args[4] != nil {
|
if args[4] != nil {
|
||||||
arg4 = args[4].(uint64)
|
arg4 = args[4].(uint64)
|
||||||
}
|
}
|
||||||
var arg5 uint64
|
|
||||||
if args[5] != nil {
|
|
||||||
arg5 = args[5].(uint64)
|
|
||||||
}
|
|
||||||
run(
|
run(
|
||||||
arg0,
|
arg0,
|
||||||
arg1,
|
arg1,
|
||||||
arg2,
|
arg2,
|
||||||
arg3,
|
arg3,
|
||||||
arg4,
|
arg4,
|
||||||
arg5,
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
@@ -564,7 +313,7 @@ func (_c *ConfigRepository_RetrieveAll_Call) Return(configsPage bootstrap.Config
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_RetrieveAll_Call) RunAndReturn(run func(ctx context.Context, domainID string, clientIDs []string, filter bootstrap.Filter, offset uint64, limit uint64) bootstrap.ConfigsPage) *ConfigRepository_RetrieveAll_Call {
|
func (_c *ConfigRepository_RetrieveAll_Call) RunAndReturn(run func(ctx context.Context, domainID string, filter bootstrap.Filter, offset uint64, limit uint64) bootstrap.ConfigsPage) *ConfigRepository_RetrieveAll_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -708,8 +457,8 @@ func (_c *ConfigRepository_RetrieveByID_Call) RunAndReturn(run func(ctx context.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save provides a mock function for the type ConfigRepository
|
// Save provides a mock function for the type ConfigRepository
|
||||||
func (_mock *ConfigRepository) Save(ctx context.Context, cfg bootstrap.Config, chsConnIDs []string) (string, error) {
|
func (_mock *ConfigRepository) Save(ctx context.Context, cfg bootstrap.Config) (string, error) {
|
||||||
ret := _mock.Called(ctx, cfg, chsConnIDs)
|
ret := _mock.Called(ctx, cfg)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for Save")
|
panic("no return value specified for Save")
|
||||||
@@ -717,16 +466,16 @@ func (_mock *ConfigRepository) Save(ctx context.Context, cfg bootstrap.Config, c
|
|||||||
|
|
||||||
var r0 string
|
var r0 string
|
||||||
var r1 error
|
var r1 error
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.Config, []string) (string, error)); ok {
|
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.Config) (string, error)); ok {
|
||||||
return returnFunc(ctx, cfg, chsConnIDs)
|
return returnFunc(ctx, cfg)
|
||||||
}
|
}
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.Config, []string) string); ok {
|
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.Config) string); ok {
|
||||||
r0 = returnFunc(ctx, cfg, chsConnIDs)
|
r0 = returnFunc(ctx, cfg)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(string)
|
r0 = ret.Get(0).(string)
|
||||||
}
|
}
|
||||||
if returnFunc, ok := ret.Get(1).(func(context.Context, bootstrap.Config, []string) error); ok {
|
if returnFunc, ok := ret.Get(1).(func(context.Context, bootstrap.Config) error); ok {
|
||||||
r1 = returnFunc(ctx, cfg, chsConnIDs)
|
r1 = returnFunc(ctx, cfg)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -741,12 +490,11 @@ type ConfigRepository_Save_Call struct {
|
|||||||
// Save is a helper method to define mock.On call
|
// Save is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - cfg bootstrap.Config
|
// - cfg bootstrap.Config
|
||||||
// - chsConnIDs []string
|
func (_e *ConfigRepository_Expecter) Save(ctx interface{}, cfg interface{}) *ConfigRepository_Save_Call {
|
||||||
func (_e *ConfigRepository_Expecter) Save(ctx interface{}, cfg interface{}, chsConnIDs interface{}) *ConfigRepository_Save_Call {
|
return &ConfigRepository_Save_Call{Call: _e.mock.On("Save", ctx, cfg)}
|
||||||
return &ConfigRepository_Save_Call{Call: _e.mock.On("Save", ctx, cfg, chsConnIDs)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_Save_Call) Run(run func(ctx context.Context, cfg bootstrap.Config, chsConnIDs []string)) *ConfigRepository_Save_Call {
|
func (_c *ConfigRepository_Save_Call) Run(run func(ctx context.Context, cfg bootstrap.Config)) *ConfigRepository_Save_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
var arg0 context.Context
|
var arg0 context.Context
|
||||||
if args[0] != nil {
|
if args[0] != nil {
|
||||||
@@ -756,14 +504,9 @@ func (_c *ConfigRepository_Save_Call) Run(run func(ctx context.Context, cfg boot
|
|||||||
if args[1] != nil {
|
if args[1] != nil {
|
||||||
arg1 = args[1].(bootstrap.Config)
|
arg1 = args[1].(bootstrap.Config)
|
||||||
}
|
}
|
||||||
var arg2 []string
|
|
||||||
if args[2] != nil {
|
|
||||||
arg2 = args[2].([]string)
|
|
||||||
}
|
|
||||||
run(
|
run(
|
||||||
arg0,
|
arg0,
|
||||||
arg1,
|
arg1,
|
||||||
arg2,
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
@@ -774,7 +517,7 @@ func (_c *ConfigRepository_Save_Call) Return(s string, err error) *ConfigReposit
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_Save_Call) RunAndReturn(run func(ctx context.Context, cfg bootstrap.Config, chsConnIDs []string) (string, error)) *ConfigRepository_Save_Call {
|
func (_c *ConfigRepository_Save_Call) RunAndReturn(run func(ctx context.Context, cfg bootstrap.Config) (string, error)) *ConfigRepository_Save_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -837,8 +580,8 @@ func (_c *ConfigRepository_Update_Call) RunAndReturn(run func(ctx context.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCert provides a mock function for the type ConfigRepository
|
// UpdateCert provides a mock function for the type ConfigRepository
|
||||||
func (_mock *ConfigRepository) UpdateCert(ctx context.Context, domainID string, clientID string, clientCert string, clientKey string, caCert string) (bootstrap.Config, error) {
|
func (_mock *ConfigRepository) UpdateCert(ctx context.Context, domainID string, id string, clientCert string, clientKey string, caCert string) (bootstrap.Config, error) {
|
||||||
ret := _mock.Called(ctx, domainID, clientID, clientCert, clientKey, caCert)
|
ret := _mock.Called(ctx, domainID, id, clientCert, clientKey, caCert)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for UpdateCert")
|
panic("no return value specified for UpdateCert")
|
||||||
@@ -847,15 +590,15 @@ func (_mock *ConfigRepository) UpdateCert(ctx context.Context, domainID string,
|
|||||||
var r0 bootstrap.Config
|
var r0 bootstrap.Config
|
||||||
var r1 error
|
var r1 error
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, string) (bootstrap.Config, error)); ok {
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, string) (bootstrap.Config, error)); ok {
|
||||||
return returnFunc(ctx, domainID, clientID, clientCert, clientKey, caCert)
|
return returnFunc(ctx, domainID, id, clientCert, clientKey, caCert)
|
||||||
}
|
}
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, string) bootstrap.Config); ok {
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, string) bootstrap.Config); ok {
|
||||||
r0 = returnFunc(ctx, domainID, clientID, clientCert, clientKey, caCert)
|
r0 = returnFunc(ctx, domainID, id, clientCert, clientKey, caCert)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(bootstrap.Config)
|
r0 = ret.Get(0).(bootstrap.Config)
|
||||||
}
|
}
|
||||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, string, string) error); ok {
|
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, string, string) error); ok {
|
||||||
r1 = returnFunc(ctx, domainID, clientID, clientCert, clientKey, caCert)
|
r1 = returnFunc(ctx, domainID, id, clientCert, clientKey, caCert)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -870,15 +613,15 @@ type ConfigRepository_UpdateCert_Call struct {
|
|||||||
// UpdateCert is a helper method to define mock.On call
|
// UpdateCert is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - domainID string
|
// - domainID string
|
||||||
// - clientID string
|
// - id string
|
||||||
// - clientCert string
|
// - clientCert string
|
||||||
// - clientKey string
|
// - clientKey string
|
||||||
// - caCert string
|
// - caCert string
|
||||||
func (_e *ConfigRepository_Expecter) UpdateCert(ctx interface{}, domainID interface{}, clientID interface{}, clientCert interface{}, clientKey interface{}, caCert interface{}) *ConfigRepository_UpdateCert_Call {
|
func (_e *ConfigRepository_Expecter) UpdateCert(ctx interface{}, domainID interface{}, id interface{}, clientCert interface{}, clientKey interface{}, caCert interface{}) *ConfigRepository_UpdateCert_Call {
|
||||||
return &ConfigRepository_UpdateCert_Call{Call: _e.mock.On("UpdateCert", ctx, domainID, clientID, clientCert, clientKey, caCert)}
|
return &ConfigRepository_UpdateCert_Call{Call: _e.mock.On("UpdateCert", ctx, domainID, id, clientCert, clientKey, caCert)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_UpdateCert_Call) Run(run func(ctx context.Context, domainID string, clientID string, clientCert string, clientKey string, caCert string)) *ConfigRepository_UpdateCert_Call {
|
func (_c *ConfigRepository_UpdateCert_Call) Run(run func(ctx context.Context, domainID string, id string, clientCert string, clientKey string, caCert string)) *ConfigRepository_UpdateCert_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
var arg0 context.Context
|
var arg0 context.Context
|
||||||
if args[0] != nil {
|
if args[0] != nil {
|
||||||
@@ -921,139 +664,7 @@ func (_c *ConfigRepository_UpdateCert_Call) Return(config bootstrap.Config, err
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ConfigRepository_UpdateCert_Call) RunAndReturn(run func(ctx context.Context, domainID string, clientID string, clientCert string, clientKey string, caCert string) (bootstrap.Config, error)) *ConfigRepository_UpdateCert_Call {
|
func (_c *ConfigRepository_UpdateCert_Call) RunAndReturn(run func(ctx context.Context, domainID string, id string, clientCert string, clientKey string, caCert string) (bootstrap.Config, error)) *ConfigRepository_UpdateCert_Call {
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateChannel provides a mock function for the type ConfigRepository
|
|
||||||
func (_mock *ConfigRepository) UpdateChannel(ctx context.Context, c bootstrap.Channel) error {
|
|
||||||
ret := _mock.Called(ctx, c)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for UpdateChannel")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.Channel) error); ok {
|
|
||||||
r0 = returnFunc(ctx, c)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigRepository_UpdateChannel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateChannel'
|
|
||||||
type ConfigRepository_UpdateChannel_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateChannel is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - c bootstrap.Channel
|
|
||||||
func (_e *ConfigRepository_Expecter) UpdateChannel(ctx interface{}, c interface{}) *ConfigRepository_UpdateChannel_Call {
|
|
||||||
return &ConfigRepository_UpdateChannel_Call{Call: _e.mock.On("UpdateChannel", ctx, c)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_UpdateChannel_Call) Run(run func(ctx context.Context, c bootstrap.Channel)) *ConfigRepository_UpdateChannel_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
var arg0 context.Context
|
|
||||||
if args[0] != nil {
|
|
||||||
arg0 = args[0].(context.Context)
|
|
||||||
}
|
|
||||||
var arg1 bootstrap.Channel
|
|
||||||
if args[1] != nil {
|
|
||||||
arg1 = args[1].(bootstrap.Channel)
|
|
||||||
}
|
|
||||||
run(
|
|
||||||
arg0,
|
|
||||||
arg1,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_UpdateChannel_Call) Return(err error) *ConfigRepository_UpdateChannel_Call {
|
|
||||||
_c.Call.Return(err)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_UpdateChannel_Call) RunAndReturn(run func(ctx context.Context, c bootstrap.Channel) error) *ConfigRepository_UpdateChannel_Call {
|
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateConnections provides a mock function for the type ConfigRepository
|
|
||||||
func (_mock *ConfigRepository) UpdateConnections(ctx context.Context, domainID string, id string, channels []bootstrap.Channel, connections []string) error {
|
|
||||||
ret := _mock.Called(ctx, domainID, id, channels, connections)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for UpdateConnections")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []bootstrap.Channel, []string) error); ok {
|
|
||||||
r0 = returnFunc(ctx, domainID, id, channels, connections)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigRepository_UpdateConnections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateConnections'
|
|
||||||
type ConfigRepository_UpdateConnections_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateConnections is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - domainID string
|
|
||||||
// - id string
|
|
||||||
// - channels []bootstrap.Channel
|
|
||||||
// - connections []string
|
|
||||||
func (_e *ConfigRepository_Expecter) UpdateConnections(ctx interface{}, domainID interface{}, id interface{}, channels interface{}, connections interface{}) *ConfigRepository_UpdateConnections_Call {
|
|
||||||
return &ConfigRepository_UpdateConnections_Call{Call: _e.mock.On("UpdateConnections", ctx, domainID, id, channels, connections)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_UpdateConnections_Call) Run(run func(ctx context.Context, domainID string, id string, channels []bootstrap.Channel, connections []string)) *ConfigRepository_UpdateConnections_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
var arg0 context.Context
|
|
||||||
if args[0] != nil {
|
|
||||||
arg0 = args[0].(context.Context)
|
|
||||||
}
|
|
||||||
var arg1 string
|
|
||||||
if args[1] != nil {
|
|
||||||
arg1 = args[1].(string)
|
|
||||||
}
|
|
||||||
var arg2 string
|
|
||||||
if args[2] != nil {
|
|
||||||
arg2 = args[2].(string)
|
|
||||||
}
|
|
||||||
var arg3 []bootstrap.Channel
|
|
||||||
if args[3] != nil {
|
|
||||||
arg3 = args[3].([]bootstrap.Channel)
|
|
||||||
}
|
|
||||||
var arg4 []string
|
|
||||||
if args[4] != nil {
|
|
||||||
arg4 = args[4].([]string)
|
|
||||||
}
|
|
||||||
run(
|
|
||||||
arg0,
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
arg3,
|
|
||||||
arg4,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_UpdateConnections_Call) Return(err error) *ConfigRepository_UpdateConnections_Call {
|
|
||||||
_c.Call.Return(err)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *ConfigRepository_UpdateConnections_Call) RunAndReturn(run func(ctx context.Context, domainID string, id string, channels []bootstrap.Channel, connections []string) error) *ConfigRepository_UpdateConnections_Call {
|
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,379 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Code generated by mockery; DO NOT EDIT.
|
||||||
|
// github.com/vektra/mockery
|
||||||
|
// template: testify
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewProfileRepository creates a new instance of ProfileRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewProfileRepository(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *ProfileRepository {
|
||||||
|
mock := &ProfileRepository{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileRepository is an autogenerated mock type for the ProfileRepository type
|
||||||
|
type ProfileRepository struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProfileRepository_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *ProfileRepository) EXPECT() *ProfileRepository_Expecter {
|
||||||
|
return &ProfileRepository_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete provides a mock function for the type ProfileRepository
|
||||||
|
func (_mock *ProfileRepository) Delete(ctx context.Context, domainID string, id string) error {
|
||||||
|
ret := _mock.Called(ctx, domainID, id)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
||||||
|
r0 = returnFunc(ctx, domainID, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||||
|
type ProfileRepository_Delete_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - domainID string
|
||||||
|
// - id string
|
||||||
|
func (_e *ProfileRepository_Expecter) Delete(ctx interface{}, domainID interface{}, id interface{}) *ProfileRepository_Delete_Call {
|
||||||
|
return &ProfileRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, domainID, id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Delete_Call) Run(run func(ctx context.Context, domainID string, id string)) *ProfileRepository_Delete_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Delete_Call) Return(err error) *ProfileRepository_Delete_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Delete_Call) RunAndReturn(run func(ctx context.Context, domainID string, id string) error) *ProfileRepository_Delete_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrieveAll provides a mock function for the type ProfileRepository
|
||||||
|
func (_mock *ProfileRepository) RetrieveAll(ctx context.Context, domainID string, offset uint64, limit uint64) (bootstrap.ProfilesPage, error) {
|
||||||
|
ret := _mock.Called(ctx, domainID, offset, limit)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for RetrieveAll")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 bootstrap.ProfilesPage
|
||||||
|
var r1 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) (bootstrap.ProfilesPage, error)); ok {
|
||||||
|
return returnFunc(ctx, domainID, offset, limit)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) bootstrap.ProfilesPage); ok {
|
||||||
|
r0 = returnFunc(ctx, domainID, offset, limit)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(bootstrap.ProfilesPage)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok {
|
||||||
|
r1 = returnFunc(ctx, domainID, offset, limit)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileRepository_RetrieveAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveAll'
|
||||||
|
type ProfileRepository_RetrieveAll_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrieveAll is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - domainID string
|
||||||
|
// - offset uint64
|
||||||
|
// - limit uint64
|
||||||
|
func (_e *ProfileRepository_Expecter) RetrieveAll(ctx interface{}, domainID interface{}, offset interface{}, limit interface{}) *ProfileRepository_RetrieveAll_Call {
|
||||||
|
return &ProfileRepository_RetrieveAll_Call{Call: _e.mock.On("RetrieveAll", ctx, domainID, offset, limit)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_RetrieveAll_Call) Run(run func(ctx context.Context, domainID string, offset uint64, limit uint64)) *ProfileRepository_RetrieveAll_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 uint64
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(uint64)
|
||||||
|
}
|
||||||
|
var arg3 uint64
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(uint64)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_RetrieveAll_Call) Return(profilesPage bootstrap.ProfilesPage, err error) *ProfileRepository_RetrieveAll_Call {
|
||||||
|
_c.Call.Return(profilesPage, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_RetrieveAll_Call) RunAndReturn(run func(ctx context.Context, domainID string, offset uint64, limit uint64) (bootstrap.ProfilesPage, error)) *ProfileRepository_RetrieveAll_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrieveByID provides a mock function for the type ProfileRepository
|
||||||
|
func (_mock *ProfileRepository) RetrieveByID(ctx context.Context, domainID string, id string) (bootstrap.Profile, error) {
|
||||||
|
ret := _mock.Called(ctx, domainID, id)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for RetrieveByID")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 bootstrap.Profile
|
||||||
|
var r1 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (bootstrap.Profile, error)); ok {
|
||||||
|
return returnFunc(ctx, domainID, id)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) bootstrap.Profile); ok {
|
||||||
|
r0 = returnFunc(ctx, domainID, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(bootstrap.Profile)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||||
|
r1 = returnFunc(ctx, domainID, id)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileRepository_RetrieveByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveByID'
|
||||||
|
type ProfileRepository_RetrieveByID_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrieveByID is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - domainID string
|
||||||
|
// - id string
|
||||||
|
func (_e *ProfileRepository_Expecter) RetrieveByID(ctx interface{}, domainID interface{}, id interface{}) *ProfileRepository_RetrieveByID_Call {
|
||||||
|
return &ProfileRepository_RetrieveByID_Call{Call: _e.mock.On("RetrieveByID", ctx, domainID, id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_RetrieveByID_Call) Run(run func(ctx context.Context, domainID string, id string)) *ProfileRepository_RetrieveByID_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_RetrieveByID_Call) Return(profile bootstrap.Profile, err error) *ProfileRepository_RetrieveByID_Call {
|
||||||
|
_c.Call.Return(profile, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_RetrieveByID_Call) RunAndReturn(run func(ctx context.Context, domainID string, id string) (bootstrap.Profile, error)) *ProfileRepository_RetrieveByID_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save provides a mock function for the type ProfileRepository
|
||||||
|
func (_mock *ProfileRepository) Save(ctx context.Context, p bootstrap.Profile) (bootstrap.Profile, error) {
|
||||||
|
ret := _mock.Called(ctx, p)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Save")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 bootstrap.Profile
|
||||||
|
var r1 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.Profile) (bootstrap.Profile, error)); ok {
|
||||||
|
return returnFunc(ctx, p)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.Profile) bootstrap.Profile); ok {
|
||||||
|
r0 = returnFunc(ctx, p)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(bootstrap.Profile)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, bootstrap.Profile) error); ok {
|
||||||
|
r1 = returnFunc(ctx, p)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileRepository_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
|
||||||
|
type ProfileRepository_Save_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - p bootstrap.Profile
|
||||||
|
func (_e *ProfileRepository_Expecter) Save(ctx interface{}, p interface{}) *ProfileRepository_Save_Call {
|
||||||
|
return &ProfileRepository_Save_Call{Call: _e.mock.On("Save", ctx, p)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Save_Call) Run(run func(ctx context.Context, p bootstrap.Profile)) *ProfileRepository_Save_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 bootstrap.Profile
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(bootstrap.Profile)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Save_Call) Return(profile bootstrap.Profile, err error) *ProfileRepository_Save_Call {
|
||||||
|
_c.Call.Return(profile, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Save_Call) RunAndReturn(run func(ctx context.Context, p bootstrap.Profile) (bootstrap.Profile, error)) *ProfileRepository_Save_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update provides a mock function for the type ProfileRepository
|
||||||
|
func (_mock *ProfileRepository) Update(ctx context.Context, p bootstrap.Profile) error {
|
||||||
|
ret := _mock.Called(ctx, p)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Update")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, bootstrap.Profile) error); ok {
|
||||||
|
r0 = returnFunc(ctx, p)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||||
|
type ProfileRepository_Update_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - p bootstrap.Profile
|
||||||
|
func (_e *ProfileRepository_Expecter) Update(ctx interface{}, p interface{}) *ProfileRepository_Update_Call {
|
||||||
|
return &ProfileRepository_Update_Call{Call: _e.mock.On("Update", ctx, p)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Update_Call) Run(run func(ctx context.Context, p bootstrap.Profile)) *ProfileRepository_Update_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 bootstrap.Profile
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(bootstrap.Profile)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Update_Call) Return(err error) *ProfileRepository_Update_Call {
|
||||||
|
_c.Call.Return(err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *ProfileRepository_Update_Call) RunAndReturn(run func(ctx context.Context, p bootstrap.Profile) error) *ProfileRepository_Update_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Code generated by mockery; DO NOT EDIT.
|
||||||
|
// github.com/vektra/mockery
|
||||||
|
// template: testify
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRenderer creates a new instance of Renderer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewRenderer(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *Renderer {
|
||||||
|
mock := &Renderer{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderer is an autogenerated mock type for the Renderer type
|
||||||
|
type Renderer struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type Renderer_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *Renderer) EXPECT() *Renderer_Expecter {
|
||||||
|
return &Renderer_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render provides a mock function for the type Renderer
|
||||||
|
func (_mock *Renderer) Render(profile bootstrap.Profile, enrollment bootstrap.Config, bindings []bootstrap.BindingSnapshot) ([]byte, error) {
|
||||||
|
ret := _mock.Called(profile, enrollment, bindings)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Render")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 []byte
|
||||||
|
var r1 error
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(bootstrap.Profile, bootstrap.Config, []bootstrap.BindingSnapshot) ([]byte, error)); ok {
|
||||||
|
return returnFunc(profile, enrollment, bindings)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(bootstrap.Profile, bootstrap.Config, []bootstrap.BindingSnapshot) []byte); ok {
|
||||||
|
r0 = returnFunc(profile, enrollment, bindings)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]byte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(bootstrap.Profile, bootstrap.Config, []bootstrap.BindingSnapshot) error); ok {
|
||||||
|
r1 = returnFunc(profile, enrollment, bindings)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderer_Render_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Render'
|
||||||
|
type Renderer_Render_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render is a helper method to define mock.On call
|
||||||
|
// - profile bootstrap.Profile
|
||||||
|
// - enrollment bootstrap.Config
|
||||||
|
// - bindings []bootstrap.BindingSnapshot
|
||||||
|
func (_e *Renderer_Expecter) Render(profile interface{}, enrollment interface{}, bindings interface{}) *Renderer_Render_Call {
|
||||||
|
return &Renderer_Render_Call{Call: _e.mock.On("Render", profile, enrollment, bindings)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Renderer_Render_Call) Run(run func(profile bootstrap.Profile, enrollment bootstrap.Config, bindings []bootstrap.BindingSnapshot)) *Renderer_Render_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 bootstrap.Profile
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(bootstrap.Profile)
|
||||||
|
}
|
||||||
|
var arg1 bootstrap.Config
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(bootstrap.Config)
|
||||||
|
}
|
||||||
|
var arg2 []bootstrap.BindingSnapshot
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].([]bootstrap.BindingSnapshot)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Renderer_Render_Call) Return(bytes []byte, err error) *Renderer_Render_Call {
|
||||||
|
_c.Call.Return(bytes, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Renderer_Render_Call) RunAndReturn(run func(profile bootstrap.Profile, enrollment bootstrap.Config, bindings []bootstrap.BindingSnapshot) ([]byte, error)) *Renderer_Render_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
+615
-283
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
|
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||||
|
"github.com/absmach/magistrala/pkg/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ bootstrap.BindingStore = (*bindingRepository)(nil)
|
||||||
|
|
||||||
|
type bindingRepository struct {
|
||||||
|
db postgres.Database
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBindingRepository instantiates a PostgreSQL implementation of BindingStore.
|
||||||
|
func NewBindingRepository(db postgres.Database, log *slog.Logger) bootstrap.BindingStore {
|
||||||
|
return &bindingRepository{db: db, log: log}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br bindingRepository) Save(ctx context.Context, configID string, bindings []bootstrap.BindingSnapshot) error {
|
||||||
|
if len(bindings) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
q := `INSERT INTO bindings (config_id, slot, type, resource_id, snapshot, secret_snapshot, updated_at)
|
||||||
|
VALUES (:config_id, :slot, :type, :resource_id, :snapshot, :secret_snapshot, :updated_at)
|
||||||
|
ON CONFLICT (config_id, slot) DO UPDATE SET
|
||||||
|
type = EXCLUDED.type,
|
||||||
|
resource_id = EXCLUDED.resource_id,
|
||||||
|
snapshot = EXCLUDED.snapshot,
|
||||||
|
secret_snapshot = EXCLUDED.secret_snapshot,
|
||||||
|
updated_at = EXCLUDED.updated_at`
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
dbBindings := make([]dbBindingSnapshot, 0, len(bindings))
|
||||||
|
for _, b := range bindings {
|
||||||
|
b.ConfigID = configID
|
||||||
|
b.UpdatedAt = now
|
||||||
|
dbb, err := toDBBindingSnapshot(b)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||||
|
}
|
||||||
|
dbBindings = append(dbBindings, dbb)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := br.db.NamedExecContext(ctx, q, dbBindings); err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br bindingRepository) Retrieve(ctx context.Context, configID string) ([]bootstrap.BindingSnapshot, error) {
|
||||||
|
q := `SELECT config_id, slot, type, resource_id, snapshot, secret_snapshot, updated_at
|
||||||
|
FROM bindings WHERE config_id = $1 ORDER BY slot`
|
||||||
|
|
||||||
|
rows, err := br.db.QueryxContext(ctx, q, configID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var snapshots []bootstrap.BindingSnapshot
|
||||||
|
for rows.Next() {
|
||||||
|
var dbb dbBindingSnapshot
|
||||||
|
if err := rows.StructScan(&dbb); err != nil {
|
||||||
|
br.log.Error(fmt.Sprintf("failed to scan binding snapshot: %s", err))
|
||||||
|
return nil, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
b, err := toBindingSnapshot(dbb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
snapshots = append(snapshots, b)
|
||||||
|
}
|
||||||
|
return snapshots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br bindingRepository) Delete(ctx context.Context, configID, slot string) error {
|
||||||
|
q := `DELETE FROM bindings WHERE config_id = $1 AND slot = $2`
|
||||||
|
if _, err := br.db.ExecContext(ctx, q, configID, slot); err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbBindingSnapshot is the database representation of a BindingSnapshot.
|
||||||
|
type dbBindingSnapshot struct {
|
||||||
|
ConfigID string `db:"config_id"`
|
||||||
|
Slot string `db:"slot"`
|
||||||
|
Type string `db:"type"`
|
||||||
|
ResourceID string `db:"resource_id"`
|
||||||
|
Snapshot []byte `db:"snapshot"`
|
||||||
|
SecretSnapshot []byte `db:"secret_snapshot"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func toDBBindingSnapshot(b bootstrap.BindingSnapshot) (dbBindingSnapshot, error) {
|
||||||
|
snap, err := json.Marshal(b.Snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return dbBindingSnapshot{}, err
|
||||||
|
}
|
||||||
|
secret, err := json.Marshal(b.SecretSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return dbBindingSnapshot{}, err
|
||||||
|
}
|
||||||
|
return dbBindingSnapshot{
|
||||||
|
ConfigID: b.ConfigID,
|
||||||
|
Slot: b.Slot,
|
||||||
|
Type: b.Type,
|
||||||
|
ResourceID: b.ResourceID,
|
||||||
|
Snapshot: snap,
|
||||||
|
SecretSnapshot: secret,
|
||||||
|
UpdatedAt: b.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBindingSnapshot(dbb dbBindingSnapshot) (bootstrap.BindingSnapshot, error) {
|
||||||
|
b := bootstrap.BindingSnapshot{
|
||||||
|
ConfigID: dbb.ConfigID,
|
||||||
|
Slot: dbb.Slot,
|
||||||
|
Type: dbb.Type,
|
||||||
|
ResourceID: dbb.ResourceID,
|
||||||
|
UpdatedAt: dbb.UpdatedAt,
|
||||||
|
}
|
||||||
|
if len(dbb.Snapshot) > 0 && string(dbb.Snapshot) != jsonNull {
|
||||||
|
if err := json.Unmarshal(dbb.Snapshot, &b.Snapshot); err != nil {
|
||||||
|
return bootstrap.BindingSnapshot{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(dbb.SecretSnapshot) > 0 && string(dbb.SecretSnapshot) != jsonNull {
|
||||||
|
if err := json.Unmarshal(dbb.SecretSnapshot, &b.SecretSnapshot); err != nil {
|
||||||
|
return bootstrap.BindingSnapshot{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
+136
-493
@@ -10,30 +10,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/absmach/magistrala/bootstrap"
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
"github.com/absmach/magistrala/clients"
|
|
||||||
"github.com/absmach/magistrala/pkg/errors"
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||||
"github.com/absmach/magistrala/pkg/postgres"
|
"github.com/absmach/magistrala/pkg/postgres"
|
||||||
"github.com/jackc/pgerrcode"
|
"github.com/jackc/pgerrcode"
|
||||||
"github.com/jackc/pgtype"
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const jsonNull = "null"
|
||||||
errSaveChannels = errors.New("failed to insert channels to database")
|
|
||||||
errSaveConnections = errors.New("failed to insert connections to database")
|
|
||||||
errUpdateChannels = errors.New("failed to update channels in bootstrap configuration database")
|
|
||||||
errRemoveChannels = errors.New("failed to remove channels from bootstrap configuration in database")
|
|
||||||
errConnectClient = errors.New("failed to connect client in bootstrap configuration in database")
|
|
||||||
errDisconnectClient = errors.New("failed to disconnect client in bootstrap configuration in database")
|
|
||||||
)
|
|
||||||
|
|
||||||
const cleanupQuery = `DELETE FROM channels ch WHERE NOT EXISTS (
|
|
||||||
SELECT channel_id FROM connections c WHERE ch.magistrala_channel = c.channel_id);`
|
|
||||||
|
|
||||||
var _ bootstrap.ConfigRepository = (*configRepository)(nil)
|
var _ bootstrap.ConfigRepository = (*configRepository)(nil)
|
||||||
|
|
||||||
@@ -48,54 +34,34 @@ func NewConfigRepository(db postgres.Database, log *slog.Logger) bootstrap.Confi
|
|||||||
return &configRepository{db: db, log: log}
|
return &configRepository{db: db, log: log}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) Save(ctx context.Context, cfg bootstrap.Config, chsConnIDs []string) (clientID string, err error) {
|
func (cr configRepository) Save(ctx context.Context, cfg bootstrap.Config) (string, error) {
|
||||||
q := `INSERT INTO configs (magistrala_client, domain_id, name, client_cert, client_key, ca_cert, magistrala_secret, external_id, external_key, content, state)
|
q := `INSERT INTO configs (id, domain_id, name, client_cert, client_key, ca_cert, external_id, external_key, content, status, profile_id, render_context)
|
||||||
VALUES (:magistrala_client, :domain_id, :name, :client_cert, :client_key, :ca_cert, :magistrala_secret, :external_id, :external_key, :content, :state)`
|
VALUES (:id, :domain_id, :name, :client_cert, :client_key, :ca_cert, :external_id, :external_key, :content, :status, :profile_id, :render_context)`
|
||||||
|
|
||||||
tx, err := cr.db.BeginTxx(ctx, nil)
|
dbcfg, err := toDBConfig(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(repoerr.ErrCreateEntity, err)
|
return "", errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||||
}
|
}
|
||||||
dbcfg := toDBConfig(cfg)
|
if _, err := cr.db.NamedExecContext(ctx, q, dbcfg); err != nil {
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
err = cr.rollback("Save method", err, tx)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err := tx.NamedExec(q, dbcfg); err != nil {
|
|
||||||
switch pgErr := err.(type) {
|
switch pgErr := err.(type) {
|
||||||
case *pgconn.PgError:
|
case *pgconn.PgError:
|
||||||
if pgErr.Code == pgerrcode.UniqueViolation {
|
if pgErr.Code == pgerrcode.UniqueViolation {
|
||||||
err = repoerr.ErrConflict
|
return "", repoerr.ErrConflict
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", err
|
return "", errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := insertChannels(cfg.DomainID, cfg.Channels, tx); err != nil {
|
return cfg.ID, nil
|
||||||
return "", errors.Wrap(errSaveChannels, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := insertConnections(ctx, cfg, chsConnIDs, tx); err != nil {
|
|
||||||
return "", errors.Wrap(errSaveConnections, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if commitErr := tx.Commit(); commitErr != nil {
|
|
||||||
return "", commitErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg.ClientID, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string) (bootstrap.Config, error) {
|
func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string) (bootstrap.Config, error) {
|
||||||
q := `SELECT magistrala_client, magistrala_secret, external_id, external_key, name, content, state, client_cert, ca_cert
|
q := `SELECT id, external_id, name, content, status, client_cert, client_key, ca_cert, profile_id, render_context
|
||||||
FROM configs
|
FROM configs
|
||||||
WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id`
|
WHERE id = :id AND domain_id = :domain_id`
|
||||||
|
|
||||||
dbcfg := dbConfig{
|
dbcfg := dbConfig{
|
||||||
ClientID: id,
|
ID: id,
|
||||||
DomainID: domainID,
|
DomainID: domainID,
|
||||||
}
|
}
|
||||||
row, err := cr.db.NamedQueryContext(ctx, q, dbcfg)
|
row, err := cr.db.NamedQueryContext(ctx, q, dbcfg)
|
||||||
@@ -111,46 +77,19 @@ func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string
|
|||||||
return bootstrap.Config{}, err
|
return bootstrap.Config{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
q = `SELECT magistrala_channel, name, metadata FROM channels ch
|
cfg, err := toConfig(dbcfg)
|
||||||
INNER JOIN connections conn
|
|
||||||
ON ch.magistrala_channel = conn.channel_id AND ch.domain_id = conn.domain_id
|
|
||||||
WHERE conn.config_id = :magistrala_client AND conn.domain_id = :domain_id`
|
|
||||||
|
|
||||||
rows, err := cr.db.NamedQueryContext(ctx, q, dbcfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err))
|
return bootstrap.Config{}, err
|
||||||
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
chans := []bootstrap.Channel{}
|
|
||||||
for rows.Next() {
|
|
||||||
dbch := dbChannel{}
|
|
||||||
if err := rows.StructScan(&dbch); err != nil {
|
|
||||||
cr.log.Error(fmt.Sprintf("Failed to read connected client due to %s", err))
|
|
||||||
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
|
||||||
}
|
|
||||||
dbch.DomainID = nullString(dbcfg.DomainID)
|
|
||||||
|
|
||||||
ch, err := toChannel(dbch)
|
|
||||||
if err != nil {
|
|
||||||
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
|
||||||
}
|
|
||||||
chans = append(chans, ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := toConfig(dbcfg)
|
|
||||||
cfg.Channels = chans
|
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) RetrieveAll(ctx context.Context, domainID string, clientIDs []string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage {
|
func (cr configRepository) RetrieveAll(ctx context.Context, domainID string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage {
|
||||||
search, params := buildRetrieveQueryParams(domainID, clientIDs, filter)
|
search, params := buildRetrieveQueryParams(domainID, filter)
|
||||||
n := len(params)
|
n := len(params)
|
||||||
|
|
||||||
q := `SELECT magistrala_client, magistrala_secret, external_id, external_key, name, content, state
|
q := `SELECT id, external_id, name, content, status, profile_id, render_context
|
||||||
FROM configs %s ORDER BY magistrala_client LIMIT $%d OFFSET $%d`
|
FROM configs %s ORDER BY id LIMIT $%d OFFSET $%d`
|
||||||
q = fmt.Sprintf(q, search, n+1, n+2)
|
q = fmt.Sprintf(q, search, n+1, n+2)
|
||||||
|
|
||||||
rows, err := cr.db.QueryContext(ctx, q, append(params, limit, offset)...)
|
rows, err := cr.db.QueryContext(ctx, q, append(params, limit, offset)...)
|
||||||
@@ -160,18 +99,28 @@ func (cr configRepository) RetrieveAll(ctx context.Context, domainID string, cli
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var name, content sql.NullString
|
var name, content, profileID sql.NullString
|
||||||
|
var renderContext []byte
|
||||||
configs := []bootstrap.Config{}
|
configs := []bootstrap.Config{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
c := bootstrap.Config{DomainID: domainID}
|
c := bootstrap.Config{DomainID: domainID}
|
||||||
if err := rows.Scan(&c.ClientID, &c.ClientSecret, &c.ExternalID, &c.ExternalKey, &name, &content, &c.State); err != nil {
|
if err := rows.Scan(&c.ID, &c.ExternalID, &name, &content, &c.Status, &profileID, &renderContext); err != nil {
|
||||||
cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err))
|
cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err))
|
||||||
return bootstrap.ConfigsPage{}
|
return bootstrap.ConfigsPage{}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Name = name.String
|
c.Name = name.String
|
||||||
c.Content = content.String
|
c.Content = content.String
|
||||||
|
if profileID.Valid {
|
||||||
|
c.ProfileID = profileID.String
|
||||||
|
}
|
||||||
|
if len(renderContext) > 0 && string(renderContext) != jsonNull {
|
||||||
|
if err := json.Unmarshal(renderContext, &c.RenderContext); err != nil {
|
||||||
|
cr.log.Error(fmt.Sprintf("Failed to decode render context due to %s", err))
|
||||||
|
return bootstrap.ConfigsPage{}
|
||||||
|
}
|
||||||
|
}
|
||||||
configs = append(configs, c)
|
configs = append(configs, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +141,7 @@ func (cr configRepository) RetrieveAll(ctx context.Context, domainID string, cli
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID string) (bootstrap.Config, error) {
|
func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID string) (bootstrap.Config, error) {
|
||||||
q := `SELECT magistrala_client, magistrala_secret, external_key, domain_id, name, client_cert, client_key, ca_cert, content, state
|
q := `SELECT id, external_key, domain_id, name, client_cert, client_key, ca_cert, content, status, profile_id, render_context
|
||||||
FROM configs
|
FROM configs
|
||||||
WHERE external_id = :external_id`
|
WHERE external_id = :external_id`
|
||||||
dbcfg := dbConfig{
|
dbcfg := dbConfig{
|
||||||
@@ -212,48 +161,20 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID
|
|||||||
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
q = `SELECT magistrala_channel, name, metadata FROM channels ch
|
cfg, err := toConfig(dbcfg)
|
||||||
INNER JOIN connections conn
|
|
||||||
ON ch.magistrala_channel = conn.channel_id AND ch.domain_id = conn.domain_id
|
|
||||||
WHERE conn.config_id = :magistrala_client AND conn.domain_id = :domain_id`
|
|
||||||
|
|
||||||
rows, err := cr.db.NamedQueryContext(ctx, q, dbcfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err))
|
return bootstrap.Config{}, err
|
||||||
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
channels := []bootstrap.Channel{}
|
|
||||||
for rows.Next() {
|
|
||||||
dbch := dbChannel{}
|
|
||||||
if err := rows.StructScan(&dbch); err != nil {
|
|
||||||
cr.log.Error(fmt.Sprintf("Failed to read connected client due to %s", err))
|
|
||||||
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ch, err := toChannel(dbch)
|
|
||||||
if err != nil {
|
|
||||||
cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err))
|
|
||||||
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
channels = append(channels, ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := toConfig(dbcfg)
|
|
||||||
cfg.Channels = channels
|
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) Update(ctx context.Context, cfg bootstrap.Config) error {
|
func (cr configRepository) Update(ctx context.Context, cfg bootstrap.Config) error {
|
||||||
q := `UPDATE configs SET name = :name, content = :content WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id `
|
q := `UPDATE configs SET name = :name, content = :content WHERE id = :id AND domain_id = :domain_id `
|
||||||
|
|
||||||
dbcfg := dbConfig{
|
dbcfg := dbConfig{
|
||||||
Name: nullString(cfg.Name),
|
Name: nullString(cfg.Name),
|
||||||
Content: nullString(cfg.Content),
|
Content: nullString(cfg.Content),
|
||||||
ClientID: cfg.ClientID,
|
ID: cfg.ID,
|
||||||
DomainID: cfg.DomainID,
|
DomainID: cfg.DomainID,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,12 +195,38 @@ func (cr configRepository) Update(ctx context.Context, cfg bootstrap.Config) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) UpdateCert(ctx context.Context, domainID, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
func (cr configRepository) AssignProfile(ctx context.Context, domainID, id, profileID string) error {
|
||||||
q := `UPDATE configs SET client_cert = :client_cert, client_key = :client_key, ca_cert = :ca_cert WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id
|
q := `UPDATE configs SET profile_id = :profile_id WHERE id = :id AND domain_id = :domain_id`
|
||||||
RETURNING magistrala_client, client_cert, client_key, ca_cert`
|
|
||||||
|
|
||||||
dbcfg := dbConfig{
|
dbcfg := dbConfig{
|
||||||
ClientID: clientID,
|
ID: id,
|
||||||
|
DomainID: domainID,
|
||||||
|
ProfileID: nullString(profileID),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := cr.db.NamedExecContext(ctx, q, dbcfg)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt == 0 {
|
||||||
|
return repoerr.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr configRepository) UpdateCert(ctx context.Context, domainID, id, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
||||||
|
q := `UPDATE configs SET client_cert = :client_cert, client_key = :client_key, ca_cert = :ca_cert WHERE id = :id AND domain_id = :domain_id
|
||||||
|
RETURNING id, client_cert, client_key, ca_cert, domain_id`
|
||||||
|
|
||||||
|
dbcfg := dbConfig{
|
||||||
|
ID: id,
|
||||||
ClientCert: nullString(clientCert),
|
ClientCert: nullString(clientCert),
|
||||||
DomainID: domainID,
|
DomainID: domainID,
|
||||||
ClientKey: nullString(clientKey),
|
ClientKey: nullString(clientKey),
|
||||||
@@ -300,47 +247,17 @@ func (cr configRepository) UpdateCert(ctx context.Context, domainID, clientID, c
|
|||||||
return bootstrap.Config{}, err
|
return bootstrap.Config{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return toConfig(dbcfg), nil
|
cfg, err := toConfig(dbcfg)
|
||||||
}
|
|
||||||
|
|
||||||
func (cr configRepository) UpdateConnections(ctx context.Context, domainID, id string, channels []bootstrap.Channel, connections []string) (err error) {
|
|
||||||
tx, err := cr.db.BeginTxx(ctx, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
return bootstrap.Config{}, err
|
||||||
}
|
}
|
||||||
|
return cfg, nil
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
err = cr.rollback("UpdateConnections method", err, tx)
|
|
||||||
} else {
|
|
||||||
if commitErr := tx.Commit(); commitErr != nil {
|
|
||||||
err = commitErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = insertChannels(domainID, channels, tx); err != nil {
|
|
||||||
err = errors.Wrap(repoerr.ErrUpdateEntity, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = updateConnections(domainID, id, connections, tx); err != nil {
|
|
||||||
if e, ok := err.(*pgconn.PgError); ok {
|
|
||||||
if e.Code == pgerrcode.ForeignKeyViolation {
|
|
||||||
err = repoerr.ErrNotFound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = errors.Wrap(repoerr.ErrUpdateEntity, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) Remove(ctx context.Context, domainID, id string) error {
|
func (cr configRepository) Remove(ctx context.Context, domainID, id string) error {
|
||||||
q := `DELETE FROM configs WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id`
|
q := `DELETE FROM configs WHERE id = :id AND domain_id = :domain_id`
|
||||||
dbcfg := dbConfig{
|
dbcfg := dbConfig{
|
||||||
ClientID: id,
|
ID: id,
|
||||||
DomainID: domainID,
|
DomainID: domainID,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,19 +265,15 @@ func (cr configRepository) Remove(ctx context.Context, domainID, id string) erro
|
|||||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := cr.db.ExecContext(ctx, cleanupQuery); err != nil {
|
|
||||||
cr.log.Warn("Failed to clean dangling channels after removal")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) ChangeState(ctx context.Context, domainID, id string, state bootstrap.State) error {
|
func (cr configRepository) ChangeStatus(ctx context.Context, domainID, id string, status bootstrap.Status) error {
|
||||||
q := `UPDATE configs SET state = :state WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id;`
|
q := `UPDATE configs SET status = :status WHERE id = :id AND domain_id = :domain_id;`
|
||||||
|
|
||||||
dbcfg := dbConfig{
|
dbcfg := dbConfig{
|
||||||
ClientID: id,
|
ID: id,
|
||||||
State: state,
|
Status: status,
|
||||||
DomainID: domainID,
|
DomainID: domainID,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,117 +294,30 @@ func (cr configRepository) ChangeState(ctx context.Context, domainID, id string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) ListExisting(ctx context.Context, domainID string, ids []string) ([]bootstrap.Channel, error) {
|
func buildRetrieveQueryParams(domainID string, filter bootstrap.Filter) (string, []any) {
|
||||||
var channels []bootstrap.Channel
|
|
||||||
if len(ids) == 0 {
|
|
||||||
return channels, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var chans pgtype.TextArray
|
|
||||||
if err := chans.Set(ids); err != nil {
|
|
||||||
return []bootstrap.Channel{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
q := "SELECT magistrala_channel, name, metadata FROM channels WHERE domain_id = $1 AND magistrala_channel = ANY ($2)"
|
|
||||||
rows, err := cr.db.QueryxContext(ctx, q, domainID, chans)
|
|
||||||
if err != nil {
|
|
||||||
return []bootstrap.Channel{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
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{}, errors.Wrap(repoerr.ErrViewEntity, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr configRepository) RemoveClient(ctx context.Context, id string) error {
|
|
||||||
q := `DELETE FROM configs WHERE magistrala_client = $1`
|
|
||||||
_, err := cr.db.ExecContext(ctx, q, id)
|
|
||||||
|
|
||||||
if _, err := cr.db.ExecContext(ctx, cleanupQuery); err != nil {
|
|
||||||
cr.log.Warn("Failed to clean dangling channels after removal")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr configRepository) UpdateChannel(ctx context.Context, c bootstrap.Channel) error {
|
|
||||||
dbch, err := toDBChannel("", c)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
q := `UPDATE channels SET name = :name, metadata = :metadata, updated_at = :updated_at, updated_by = :updated_by
|
|
||||||
WHERE magistrala_channel = :magistrala_channel`
|
|
||||||
if _, err = cr.db.NamedExecContext(ctx, q, dbch); err != nil {
|
|
||||||
return errors.Wrap(errUpdateChannels, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr configRepository) RemoveChannel(ctx context.Context, id string) error {
|
|
||||||
q := `DELETE FROM channels WHERE magistrala_channel = $1`
|
|
||||||
if _, err := cr.db.ExecContext(ctx, q, id); err != nil {
|
|
||||||
return errors.Wrap(errRemoveChannels, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr configRepository) ConnectClient(ctx context.Context, channelID, clientID string) error {
|
|
||||||
q := `UPDATE configs SET state = $1
|
|
||||||
WHERE magistrala_client = $2
|
|
||||||
AND EXISTS (SELECT 1 FROM connections WHERE config_id = $2 AND channel_id = $3)`
|
|
||||||
|
|
||||||
result, err := cr.db.ExecContext(ctx, q, bootstrap.Active, clientID, channelID)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(errConnectClient, err)
|
|
||||||
}
|
|
||||||
if rows, _ := result.RowsAffected(); rows == 0 {
|
|
||||||
return repoerr.ErrNotFound
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr configRepository) DisconnectClient(ctx context.Context, channelID, clientID string) error {
|
|
||||||
q := `UPDATE configs SET state = $1
|
|
||||||
WHERE magistrala_client = $2
|
|
||||||
AND EXISTS (SELECT 1 FROM connections WHERE config_id = $2 AND channel_id = $3)`
|
|
||||||
_, err := cr.db.ExecContext(ctx, q, bootstrap.Inactive, clientID, channelID)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(errDisconnectClient, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildRetrieveQueryParams(domainID string, clientIDs []string, filter bootstrap.Filter) (string, []any) {
|
|
||||||
params := []any{}
|
params := []any{}
|
||||||
queries := []string{}
|
queries := []string{}
|
||||||
|
|
||||||
if len(clientIDs) != 0 {
|
if domainID != "" {
|
||||||
queries = append(queries, fmt.Sprintf("magistrala_client IN ('%s')", strings.Join(clientIDs, "','")))
|
|
||||||
} else if domainID != "" {
|
|
||||||
params = append(params, domainID)
|
params = append(params, domainID)
|
||||||
queries = append(queries, fmt.Sprintf("domain_id = $%d", len(params)))
|
queries = append(queries, fmt.Sprintf("domain_id = $%d", len(params)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust the starting point for placeholders based on the current length of params
|
|
||||||
counter := len(params) + 1
|
counter := len(params) + 1
|
||||||
for k, v := range filter.FullMatch {
|
for k, v := range filter.FullMatch {
|
||||||
|
if k == "status" {
|
||||||
|
status, err := bootstrap.ToStatus(v)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if status == bootstrap.AllStatus {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
params = append(params, status)
|
||||||
|
queries = append(queries, fmt.Sprintf("%s = $%d", k, counter))
|
||||||
|
counter++
|
||||||
|
continue
|
||||||
|
}
|
||||||
params = append(params, v)
|
params = append(params, v)
|
||||||
queries = append(queries, fmt.Sprintf("%s = $%d", k, counter))
|
queries = append(queries, fmt.Sprintf("%s = $%d", k, counter))
|
||||||
counter++
|
counter++
|
||||||
@@ -508,264 +334,81 @@ func buildRetrieveQueryParams(domainID string, clientIDs []string, filter bootst
|
|||||||
return "", params
|
return "", params
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr configRepository) rollback(content string, defErr error, tx *sqlx.Tx) error {
|
|
||||||
if err := tx.Rollback(); err != nil {
|
|
||||||
return errors.Wrap(defErr, errors.Wrap(errors.New("failed to rollback at "+content), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return defErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertChannels(domainID string, channels []bootstrap.Channel, tx *sqlx.Tx) error {
|
|
||||||
if len(channels) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var chans []dbChannel
|
|
||||||
for _, ch := range channels {
|
|
||||||
dbch, err := toDBChannel(domainID, ch)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chans = append(chans, dbch)
|
|
||||||
}
|
|
||||||
q := `INSERT INTO channels (magistrala_channel, domain_id, name, metadata, parent_id, description, created_at, updated_at, updated_by, status)
|
|
||||||
VALUES (:magistrala_channel, :domain_id, :name, :metadata, :parent_id, :description, :created_at, :updated_at, :updated_by, :status)`
|
|
||||||
if _, err := tx.NamedExec(q, chans); err != nil {
|
|
||||||
e := err
|
|
||||||
if pqErr, ok := err.(*pgconn.PgError); ok && pqErr.Code == pgerrcode.UniqueViolation {
|
|
||||||
e = repoerr.ErrConflict
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertConnections(_ context.Context, cfg bootstrap.Config, connections []string, tx *sqlx.Tx) error {
|
|
||||||
if len(connections) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
q := `INSERT INTO connections (config_id, channel_id, domain_id)
|
|
||||||
VALUES (:config_id, :channel_id, :domain_id)`
|
|
||||||
|
|
||||||
conns := []dbConnection{}
|
|
||||||
for _, conn := range connections {
|
|
||||||
dbconn := dbConnection{
|
|
||||||
Config: cfg.ClientID,
|
|
||||||
Channel: conn,
|
|
||||||
DomainID: cfg.DomainID,
|
|
||||||
}
|
|
||||||
conns = append(conns, dbconn)
|
|
||||||
}
|
|
||||||
_, err := tx.NamedExec(q, conns)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateConnections(domainID, id string, connections []string, tx *sqlx.Tx) error {
|
|
||||||
if len(connections) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
q := `DELETE FROM connections
|
|
||||||
WHERE config_id = $1 AND domain_id = $2
|
|
||||||
AND channel_id NOT IN ($3)`
|
|
||||||
|
|
||||||
var conn pgtype.TextArray
|
|
||||||
if err := conn.Set(connections); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := tx.Exec(q, id, domainID, conn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cnt, err := res.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
q = `INSERT INTO connections (config_id, channel_id, domain_id)
|
|
||||||
VALUES (:config_id, :channel_id, :domain_id)`
|
|
||||||
|
|
||||||
conns := []dbConnection{}
|
|
||||||
for _, conn := range connections {
|
|
||||||
dbconn := dbConnection{
|
|
||||||
Config: id,
|
|
||||||
Channel: conn,
|
|
||||||
DomainID: domainID,
|
|
||||||
}
|
|
||||||
conns = append(conns, dbconn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := tx.NamedExec(q, conns); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cnt == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.Exec(cleanupQuery)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func nullString(s string) sql.NullString {
|
func nullString(s string) sql.NullString {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return sql.NullString{}
|
return sql.NullString{}
|
||||||
}
|
}
|
||||||
|
return sql.NullString{String: s, Valid: true}
|
||||||
return sql.NullString{
|
|
||||||
String: s,
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nullTime(t time.Time) sql.NullTime {
|
|
||||||
if t.IsZero() {
|
|
||||||
return sql.NullTime{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sql.NullTime{
|
|
||||||
Time: t,
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type dbConfig struct {
|
type dbConfig struct {
|
||||||
DomainID string `db:"domain_id"`
|
DomainID string `db:"domain_id"`
|
||||||
ClientID string `db:"magistrala_client"`
|
ID string `db:"id"`
|
||||||
ClientSecret string `db:"magistrala_secret"`
|
Name sql.NullString `db:"name"`
|
||||||
Name sql.NullString `db:"name"`
|
ClientCert sql.NullString `db:"client_cert"`
|
||||||
ClientCert sql.NullString `db:"client_cert"`
|
ClientKey sql.NullString `db:"client_key"`
|
||||||
ClientKey sql.NullString `db:"client_key"`
|
CaCert sql.NullString `db:"ca_cert"`
|
||||||
CaCert sql.NullString `db:"ca_cert"`
|
ExternalID string `db:"external_id"`
|
||||||
ExternalID string `db:"external_id"`
|
ExternalKey string `db:"external_key"`
|
||||||
ExternalKey string `db:"external_key"`
|
Content sql.NullString `db:"content"`
|
||||||
Content sql.NullString `db:"content"`
|
Status bootstrap.Status `db:"status"`
|
||||||
State bootstrap.State `db:"state"`
|
ProfileID sql.NullString `db:"profile_id"`
|
||||||
|
RenderContext []byte `db:"render_context"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDBConfig(cfg bootstrap.Config) dbConfig {
|
func toDBConfig(cfg bootstrap.Config) (dbConfig, error) {
|
||||||
return dbConfig{
|
renderContext, err := json.Marshal(cfg.RenderContext)
|
||||||
ClientID: cfg.ClientID,
|
if err != nil {
|
||||||
ClientSecret: cfg.ClientSecret,
|
return dbConfig{}, err
|
||||||
DomainID: cfg.DomainID,
|
|
||||||
Name: nullString(cfg.Name),
|
|
||||||
ClientCert: nullString(cfg.ClientCert),
|
|
||||||
ClientKey: nullString(cfg.ClientKey),
|
|
||||||
CaCert: nullString(cfg.CACert),
|
|
||||||
ExternalID: cfg.ExternalID,
|
|
||||||
ExternalKey: cfg.ExternalKey,
|
|
||||||
Content: nullString(cfg.Content),
|
|
||||||
State: cfg.State,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return dbConfig{
|
||||||
|
ID: cfg.ID,
|
||||||
|
DomainID: cfg.DomainID,
|
||||||
|
Name: nullString(cfg.Name),
|
||||||
|
ClientCert: nullString(cfg.ClientCert),
|
||||||
|
ClientKey: nullString(cfg.ClientKey),
|
||||||
|
CaCert: nullString(cfg.CACert),
|
||||||
|
ExternalID: cfg.ExternalID,
|
||||||
|
ExternalKey: cfg.ExternalKey,
|
||||||
|
Content: nullString(cfg.Content),
|
||||||
|
Status: cfg.Status,
|
||||||
|
ProfileID: nullString(cfg.ProfileID),
|
||||||
|
RenderContext: renderContext,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toConfig(dbcfg dbConfig) bootstrap.Config {
|
func toConfig(dbcfg dbConfig) (bootstrap.Config, error) {
|
||||||
cfg := bootstrap.Config{
|
cfg := bootstrap.Config{
|
||||||
ClientID: dbcfg.ClientID,
|
ID: dbcfg.ID,
|
||||||
ClientSecret: dbcfg.ClientSecret,
|
DomainID: dbcfg.DomainID,
|
||||||
DomainID: dbcfg.DomainID,
|
ExternalID: dbcfg.ExternalID,
|
||||||
ExternalID: dbcfg.ExternalID,
|
ExternalKey: dbcfg.ExternalKey,
|
||||||
ExternalKey: dbcfg.ExternalKey,
|
Status: dbcfg.Status,
|
||||||
State: dbcfg.State,
|
}
|
||||||
|
if dbcfg.ProfileID.Valid {
|
||||||
|
cfg.ProfileID = dbcfg.ProfileID.String
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbcfg.Name.Valid {
|
if dbcfg.Name.Valid {
|
||||||
cfg.Name = dbcfg.Name.String
|
cfg.Name = dbcfg.Name.String
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbcfg.Content.Valid {
|
if dbcfg.Content.Valid {
|
||||||
cfg.Content = dbcfg.Content.String
|
cfg.Content = dbcfg.Content.String
|
||||||
}
|
}
|
||||||
|
if len(dbcfg.RenderContext) > 0 && string(dbcfg.RenderContext) != jsonNull {
|
||||||
|
if err := json.Unmarshal(dbcfg.RenderContext, &cfg.RenderContext); err != nil {
|
||||||
|
return bootstrap.Config{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if dbcfg.ClientCert.Valid {
|
if dbcfg.ClientCert.Valid {
|
||||||
cfg.ClientCert = dbcfg.ClientCert.String
|
cfg.ClientCert = dbcfg.ClientCert.String
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbcfg.ClientKey.Valid {
|
if dbcfg.ClientKey.Valid {
|
||||||
cfg.ClientKey = dbcfg.ClientKey.String
|
cfg.ClientKey = dbcfg.ClientKey.String
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbcfg.CaCert.Valid {
|
if dbcfg.CaCert.Valid {
|
||||||
cfg.CACert = dbcfg.CaCert.String
|
cfg.CACert = dbcfg.CaCert.String
|
||||||
}
|
}
|
||||||
return cfg
|
return cfg, nil
|
||||||
}
|
|
||||||
|
|
||||||
type dbChannel struct {
|
|
||||||
ID string `db:"magistrala_channel"`
|
|
||||||
Name sql.NullString `db:"name"`
|
|
||||||
DomainID sql.NullString `db:"domain_id"`
|
|
||||||
Metadata string `db:"metadata"`
|
|
||||||
Parent sql.NullString `db:"parent_id,omitempty"`
|
|
||||||
Description string `db:"description,omitempty"`
|
|
||||||
CreatedAt time.Time `db:"created_at"`
|
|
||||||
UpdatedAt sql.NullTime `db:"updated_at,omitempty"`
|
|
||||||
UpdatedBy sql.NullString `db:"updated_by,omitempty"`
|
|
||||||
Status clients.Status `db:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func toDBChannel(domainID string, ch bootstrap.Channel) (dbChannel, error) {
|
|
||||||
dbch := dbChannel{
|
|
||||||
ID: ch.ID,
|
|
||||||
Name: nullString(ch.Name),
|
|
||||||
DomainID: nullString(domainID),
|
|
||||||
Parent: nullString(ch.Parent),
|
|
||||||
Description: ch.Description,
|
|
||||||
CreatedAt: ch.CreatedAt,
|
|
||||||
UpdatedAt: nullTime(ch.UpdatedAt),
|
|
||||||
UpdatedBy: nullString(ch.UpdatedBy),
|
|
||||||
Status: ch.Status,
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata, err := json.Marshal(ch.Metadata)
|
|
||||||
if err != nil {
|
|
||||||
return dbChannel{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dbch.Metadata = string(metadata)
|
|
||||||
return dbch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toChannel(dbch dbChannel) (bootstrap.Channel, error) {
|
|
||||||
ch := bootstrap.Channel{
|
|
||||||
ID: dbch.ID,
|
|
||||||
Description: dbch.Description,
|
|
||||||
CreatedAt: dbch.CreatedAt,
|
|
||||||
Status: dbch.Status,
|
|
||||||
}
|
|
||||||
|
|
||||||
if dbch.Name.Valid {
|
|
||||||
ch.Name = dbch.Name.String
|
|
||||||
}
|
|
||||||
if dbch.DomainID.Valid {
|
|
||||||
ch.DomainID = dbch.DomainID.String
|
|
||||||
}
|
|
||||||
if dbch.Parent.Valid {
|
|
||||||
ch.Parent = dbch.Parent.String
|
|
||||||
}
|
|
||||||
if dbch.UpdatedBy.Valid {
|
|
||||||
ch.UpdatedBy = dbch.UpdatedBy.String
|
|
||||||
}
|
|
||||||
if dbch.UpdatedAt.Valid {
|
|
||||||
ch.UpdatedAt = dbch.UpdatedAt.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(dbch.Metadata), &ch.Metadata); err != nil {
|
|
||||||
return bootstrap.Channel{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type dbConnection struct {
|
|
||||||
Config string `db:"config_id"`
|
|
||||||
Channel string `db:"channel_id"`
|
|
||||||
DomainID string `db:"domain_id"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,100 +21,67 @@ import (
|
|||||||
|
|
||||||
const numConfigs = 10
|
const numConfigs = 10
|
||||||
|
|
||||||
var (
|
var config = bootstrap.Config{
|
||||||
config = bootstrap.Config{
|
ID: "mg-client",
|
||||||
ClientID: "mg-client",
|
ExternalID: "external-id",
|
||||||
ClientSecret: "mg-key",
|
ExternalKey: "external-key",
|
||||||
ExternalID: "external-id",
|
DomainID: testsutil.GenerateUUID(&testing.T{}),
|
||||||
ExternalKey: "external-key",
|
Content: "content",
|
||||||
DomainID: testsutil.GenerateUUID(&testing.T{}),
|
Status: bootstrap.Inactive,
|
||||||
Channels: []bootstrap.Channel{
|
}
|
||||||
{ID: "1", Name: "name 1", Metadata: map[string]any{"meta": 1.0}},
|
|
||||||
{ID: "2", Name: "name 2", Metadata: map[string]any{"meta": 2.0}},
|
|
||||||
},
|
|
||||||
Content: "content",
|
|
||||||
State: bootstrap.Inactive,
|
|
||||||
}
|
|
||||||
|
|
||||||
channels = []string{"1", "2"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSave(t *testing.T) {
|
func TestSave(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
repo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
diff := "different"
|
diff := "different"
|
||||||
|
|
||||||
duplicateClient := config
|
duplicateClient := config
|
||||||
duplicateClient.ExternalID = diff
|
duplicateClient.ExternalID = diff
|
||||||
duplicateClient.ClientSecret = diff
|
|
||||||
duplicateClient.Channels = []bootstrap.Channel{}
|
|
||||||
|
|
||||||
duplicateExternal := config
|
duplicateExternal := config
|
||||||
duplicateExternal.ClientID = diff
|
duplicateExternal.ID = diff
|
||||||
duplicateExternal.ClientSecret = diff
|
|
||||||
duplicateExternal.Channels = []bootstrap.Channel{}
|
|
||||||
|
|
||||||
duplicateChannels := config
|
|
||||||
duplicateChannels.ExternalID = diff
|
|
||||||
duplicateChannels.ClientSecret = diff
|
|
||||||
duplicateChannels.ClientID = diff
|
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
config bootstrap.Config
|
config bootstrap.Config
|
||||||
connections []string
|
err error
|
||||||
err error
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "save a config",
|
desc: "save a config",
|
||||||
config: config,
|
config: config,
|
||||||
connections: channels,
|
err: nil,
|
||||||
err: nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "save config with same Client ID",
|
desc: "save config with same Client ID",
|
||||||
config: duplicateClient,
|
config: duplicateClient,
|
||||||
connections: nil,
|
err: repoerr.ErrConflict,
|
||||||
err: repoerr.ErrConflict,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "save config with same external ID",
|
desc: "save config with same external ID",
|
||||||
config: duplicateExternal,
|
config: duplicateExternal,
|
||||||
connections: nil,
|
err: repoerr.ErrConflict,
|
||||||
err: repoerr.ErrConflict,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "save config with same Channels",
|
|
||||||
config: duplicateChannels,
|
|
||||||
connections: channels,
|
|
||||||
err: repoerr.ErrConflict,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
id, err := repo.Save(context.Background(), tc.config, tc.connections)
|
id, err := repo.Save(context.Background(), tc.config)
|
||||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
assert.Equal(t, id, tc.config.ClientID, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.config.ClientID, id))
|
assert.Equal(t, id, tc.config.ID, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.config.ID, id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetrieveByID(t *testing.T) {
|
func TestRetrieveByID(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
repo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
c := config
|
||||||
// Use UUID to prevent conflicts.
|
// Use UUID to prevent conflicts.
|
||||||
uid, err := uuid.NewV4()
|
uid, err := uuid.NewV4()
|
||||||
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
||||||
c.ClientSecret = uid.String()
|
c.ID = uid.String()
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
c.ExternalID = uid.String()
|
||||||
c.ExternalKey = uid.String()
|
c.ExternalKey = uid.String()
|
||||||
id, err := repo.Save(context.Background(), c, channels)
|
id, err := repo.Save(context.Background(), c)
|
||||||
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
nonexistentConfID, err := uuid.NewV4()
|
nonexistentConfID, err := uuid.NewV4()
|
||||||
@@ -159,10 +126,6 @@ func TestRetrieveByID(t *testing.T) {
|
|||||||
|
|
||||||
func TestRetrieveAll(t *testing.T) {
|
func TestRetrieveAll(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
repo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
clientIDs := make([]string, numConfigs)
|
|
||||||
|
|
||||||
for i := 0; i < numConfigs; i++ {
|
for i := 0; i < numConfigs; i++ {
|
||||||
c := config
|
c := config
|
||||||
@@ -172,26 +135,18 @@ func TestRetrieveAll(t *testing.T) {
|
|||||||
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
||||||
c.ExternalID = uid.String()
|
c.ExternalID = uid.String()
|
||||||
c.Name = fmt.Sprintf("name %d", i)
|
c.Name = fmt.Sprintf("name %d", i)
|
||||||
c.ClientID = uid.String()
|
c.ID = uid.String()
|
||||||
c.ClientSecret = uid.String()
|
|
||||||
|
|
||||||
clientIDs[i] = c.ClientID
|
|
||||||
|
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
c.State = bootstrap.Active
|
c.Status = bootstrap.Active
|
||||||
}
|
}
|
||||||
|
|
||||||
if i > 0 {
|
_, err = repo.Save(context.Background(), c)
|
||||||
c.Channels = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = repo.Save(context.Background(), c, channels)
|
|
||||||
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
||||||
}
|
}
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
domainID string
|
domainID string
|
||||||
clientID []string
|
|
||||||
offset uint64
|
offset uint64
|
||||||
limit uint64
|
limit uint64
|
||||||
filter bootstrap.Filter
|
filter bootstrap.Filter
|
||||||
@@ -200,7 +155,6 @@ func TestRetrieveAll(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "retrieve all configs",
|
desc: "retrieve all configs",
|
||||||
domainID: config.DomainID,
|
domainID: config.DomainID,
|
||||||
clientID: []string{},
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: uint64(numConfigs),
|
limit: uint64(numConfigs),
|
||||||
size: numConfigs,
|
size: numConfigs,
|
||||||
@@ -208,7 +162,6 @@ func TestRetrieveAll(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "retrieve a subset of configs",
|
desc: "retrieve a subset of configs",
|
||||||
domainID: config.DomainID,
|
domainID: config.DomainID,
|
||||||
clientID: []string{},
|
|
||||||
offset: 5,
|
offset: 5,
|
||||||
limit: uint64(numConfigs - 5),
|
limit: uint64(numConfigs - 5),
|
||||||
size: numConfigs - 5,
|
size: numConfigs - 5,
|
||||||
@@ -216,7 +169,6 @@ func TestRetrieveAll(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "retrieve with wrong domain ID ",
|
desc: "retrieve with wrong domain ID ",
|
||||||
domainID: "2",
|
domainID: "2",
|
||||||
clientID: []string{},
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: uint64(numConfigs),
|
limit: uint64(numConfigs),
|
||||||
size: 0,
|
size: 0,
|
||||||
@@ -224,16 +176,14 @@ func TestRetrieveAll(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "retrieve all active configs ",
|
desc: "retrieve all active configs ",
|
||||||
domainID: config.DomainID,
|
domainID: config.DomainID,
|
||||||
clientID: []string{},
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: uint64(numConfigs),
|
limit: uint64(numConfigs),
|
||||||
filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}},
|
filter: bootstrap.Filter{FullMatch: map[string]string{"status": bootstrap.Active.String()}},
|
||||||
size: numConfigs / 2,
|
size: numConfigs / 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "retrieve all with partial match filter",
|
desc: "retrieve all with partial match filter",
|
||||||
domainID: config.DomainID,
|
domainID: config.DomainID,
|
||||||
clientID: []string{},
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: uint64(numConfigs),
|
limit: uint64(numConfigs),
|
||||||
filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}},
|
filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}},
|
||||||
@@ -242,31 +192,14 @@ func TestRetrieveAll(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "retrieve search by name",
|
desc: "retrieve search by name",
|
||||||
domainID: config.DomainID,
|
domainID: config.DomainID,
|
||||||
clientID: []string{},
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: uint64(numConfigs),
|
limit: uint64(numConfigs),
|
||||||
filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}},
|
filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "1"}},
|
||||||
size: 1,
|
size: 1,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "retrieve by valid clientIDs",
|
|
||||||
domainID: config.DomainID,
|
|
||||||
clientID: clientIDs,
|
|
||||||
offset: 0,
|
|
||||||
limit: uint64(numConfigs),
|
|
||||||
size: 10,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "retrieve by non-existing clientID",
|
|
||||||
domainID: config.DomainID,
|
|
||||||
clientID: []string{"non-existing"},
|
|
||||||
offset: 0,
|
|
||||||
limit: uint64(numConfigs),
|
|
||||||
size: 0,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
ret := repo.RetrieveAll(context.Background(), tc.domainID, tc.clientID, tc.filter, tc.offset, tc.limit)
|
ret := repo.RetrieveAll(context.Background(), tc.domainID, tc.filter, tc.offset, tc.limit)
|
||||||
size := len(ret.Configs)
|
size := len(ret.Configs)
|
||||||
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.size, size))
|
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.size, size))
|
||||||
}
|
}
|
||||||
@@ -274,18 +207,15 @@ func TestRetrieveAll(t *testing.T) {
|
|||||||
|
|
||||||
func TestRetrieveByExternalID(t *testing.T) {
|
func TestRetrieveByExternalID(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
repo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
c := config
|
||||||
// Use UUID to prevent conflicts.
|
// Use UUID to prevent conflicts.
|
||||||
uid, err := uuid.NewV4()
|
uid, err := uuid.NewV4()
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
||||||
c.ClientSecret = uid.String()
|
c.ID = uid.String()
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
c.ExternalID = uid.String()
|
||||||
c.ExternalKey = uid.String()
|
c.ExternalKey = uid.String()
|
||||||
_, err = repo.Save(context.Background(), c, channels)
|
_, err = repo.Save(context.Background(), c)
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -312,18 +242,15 @@ func TestRetrieveByExternalID(t *testing.T) {
|
|||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
repo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
c := config
|
||||||
// Use UUID to prevent conflicts.
|
// Use UUID to prevent conflicts.
|
||||||
uid, err := uuid.NewV4()
|
uid, err := uuid.NewV4()
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
||||||
c.ClientSecret = uid.String()
|
c.ID = uid.String()
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
c.ExternalID = uid.String()
|
||||||
c.ExternalKey = uid.String()
|
c.ExternalKey = uid.String()
|
||||||
_, err = repo.Save(context.Background(), c, channels)
|
_, err = repo.Save(context.Background(), c)
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
c.Content = "new content"
|
c.Content = "new content"
|
||||||
@@ -357,18 +284,15 @@ func TestUpdate(t *testing.T) {
|
|||||||
|
|
||||||
func TestUpdateCert(t *testing.T) {
|
func TestUpdateCert(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
repo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
c := config
|
||||||
// Use UUID to prevent conflicts.
|
// Use UUID to prevent conflicts.
|
||||||
uid, err := uuid.NewV4()
|
uid, err := uuid.NewV4()
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
||||||
c.ClientSecret = uid.String()
|
c.ID = uid.String()
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
c.ExternalID = uid.String()
|
||||||
c.ExternalKey = uid.String()
|
c.ExternalKey = uid.String()
|
||||||
_, err = repo.Save(context.Background(), c, channels)
|
_, err = repo.Save(context.Background(), c)
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
c.Content = "new content"
|
c.Content = "new content"
|
||||||
@@ -379,7 +303,7 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
clientID string
|
configID string
|
||||||
domainID string
|
domainID string
|
||||||
cert string
|
cert string
|
||||||
certKey string
|
certKey string
|
||||||
@@ -389,7 +313,7 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "update with wrong domain ID ",
|
desc: "update with wrong domain ID ",
|
||||||
clientID: "",
|
configID: "",
|
||||||
cert: "cert",
|
cert: "cert",
|
||||||
certKey: "certKey",
|
certKey: "certKey",
|
||||||
ca: "",
|
ca: "",
|
||||||
@@ -399,13 +323,13 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "update a config",
|
desc: "update a config",
|
||||||
clientID: c.ClientID,
|
configID: c.ID,
|
||||||
cert: "cert",
|
cert: "cert",
|
||||||
certKey: "certKey",
|
certKey: "certKey",
|
||||||
ca: "ca",
|
ca: "ca",
|
||||||
domainID: c.DomainID,
|
domainID: c.DomainID,
|
||||||
expectedConfig: bootstrap.Config{
|
expectedConfig: bootstrap.Config{
|
||||||
ClientID: c.ClientID,
|
ID: c.ID,
|
||||||
ClientCert: "cert",
|
ClientCert: "cert",
|
||||||
CACert: "ca",
|
CACert: "ca",
|
||||||
ClientKey: "certKey",
|
ClientKey: "certKey",
|
||||||
@@ -415,99 +339,23 @@ func TestUpdateCert(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
cfg, err := repo.UpdateCert(context.Background(), tc.domainID, tc.clientID, tc.cert, tc.certKey, tc.ca)
|
cfg, err := repo.UpdateCert(context.Background(), tc.domainID, tc.configID, tc.cert, tc.certKey, tc.ca)
|
||||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||||
assert.Equal(t, tc.expectedConfig, cfg, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.expectedConfig, cfg))
|
assert.Equal(t, tc.expectedConfig, cfg, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.expectedConfig, cfg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateConnections(t *testing.T) {
|
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
|
||||||
// Use UUID to prevent conflicts.
|
|
||||||
uid, err := uuid.NewV4()
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
|
||||||
c.ClientSecret = uid.String()
|
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
|
||||||
c.ExternalKey = uid.String()
|
|
||||||
_, err = repo.Save(context.Background(), c, channels)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
|
||||||
// Use UUID to prevent conflicts.
|
|
||||||
uid, err = uuid.NewV4()
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
|
||||||
c.ClientSecret = uid.String()
|
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
|
||||||
c.ExternalKey = uid.String()
|
|
||||||
c.Channels = []bootstrap.Channel{}
|
|
||||||
c2, err := repo.Save(context.Background(), c, []string{channels[0]})
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving a config expected to succeed: %s.\n", err))
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
domainID string
|
|
||||||
id string
|
|
||||||
channels []bootstrap.Channel
|
|
||||||
connections []string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "update connections of non-existing config",
|
|
||||||
domainID: config.DomainID,
|
|
||||||
id: "unknown",
|
|
||||||
channels: nil,
|
|
||||||
connections: []string{channels[1]},
|
|
||||||
err: repoerr.ErrNotFound,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update connections",
|
|
||||||
domainID: config.DomainID,
|
|
||||||
id: c.ClientID,
|
|
||||||
channels: nil,
|
|
||||||
connections: []string{channels[1]},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update connections with existing channels",
|
|
||||||
domainID: config.DomainID,
|
|
||||||
id: c2,
|
|
||||||
channels: nil,
|
|
||||||
connections: channels,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "update connections no channels",
|
|
||||||
domainID: config.DomainID,
|
|
||||||
id: c.ClientID,
|
|
||||||
channels: nil,
|
|
||||||
connections: nil,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range cases {
|
|
||||||
err := repo.UpdateConnections(context.Background(), tc.domainID, tc.id, tc.channels, tc.connections)
|
|
||||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemove(t *testing.T) {
|
func TestRemove(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
repo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
c := config
|
||||||
// Use UUID to prevent conflicts.
|
// Use UUID to prevent conflicts.
|
||||||
uid, err := uuid.NewV4()
|
uid, err := uuid.NewV4()
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
||||||
c.ClientSecret = uid.String()
|
c.ID = uid.String()
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
c.ExternalID = uid.String()
|
||||||
c.ExternalKey = uid.String()
|
c.ExternalKey = uid.String()
|
||||||
id, err := repo.Save(context.Background(), c, channels)
|
id, err := repo.Save(context.Background(), c)
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
// Removal works the same for both existing and non-existing
|
// Removal works the same for both existing and non-existing
|
||||||
@@ -521,393 +369,86 @@ func TestRemove(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChangeState(t *testing.T) {
|
func TestChangeStatus(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
repo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
c := config
|
||||||
// Use UUID to prevent conflicts.
|
// Use UUID to prevent conflicts.
|
||||||
uid, err := uuid.NewV4()
|
uid, err := uuid.NewV4()
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
||||||
c.ClientSecret = uid.String()
|
c.ID = uid.String()
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
c.ExternalID = uid.String()
|
||||||
c.ExternalKey = uid.String()
|
c.ExternalKey = uid.String()
|
||||||
saved, err := repo.Save(context.Background(), c, channels)
|
saved, err := repo.Save(context.Background(), c)
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
domainID string
|
domainID string
|
||||||
id string
|
id string
|
||||||
state bootstrap.State
|
status bootstrap.Status
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "change state with wrong domain ID ",
|
desc: "change status with wrong domain ID ",
|
||||||
id: saved,
|
id: saved,
|
||||||
domainID: "2",
|
domainID: "2",
|
||||||
err: repoerr.ErrNotFound,
|
err: repoerr.ErrNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "change state with wrong id",
|
desc: "change status with wrong id",
|
||||||
id: "wrong",
|
id: "wrong",
|
||||||
domainID: c.DomainID,
|
domainID: c.DomainID,
|
||||||
err: repoerr.ErrNotFound,
|
err: repoerr.ErrNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "change state to Active",
|
desc: "change status to Active",
|
||||||
id: saved,
|
id: saved,
|
||||||
domainID: c.DomainID,
|
domainID: c.DomainID,
|
||||||
state: bootstrap.Active,
|
status: bootstrap.Active,
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "change state to Inactive",
|
desc: "change status to Inactive",
|
||||||
id: saved,
|
id: saved,
|
||||||
domainID: c.DomainID,
|
domainID: c.DomainID,
|
||||||
state: bootstrap.Inactive,
|
status: bootstrap.Inactive,
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
err := repo.ChangeState(context.Background(), tc.domainID, tc.id, tc.state)
|
err := repo.ChangeStatus(context.Background(), tc.domainID, tc.id, tc.status)
|
||||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListExisting(t *testing.T) {
|
func TestAssignProfile(t *testing.T) {
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
configRepo := postgres.NewConfigRepository(db, testLog)
|
||||||
err := deleteChannels(context.Background(), repo)
|
profileRepo := postgres.NewProfileRepository(db, testLog)
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
|
||||||
// Use UUID to prevent conflicts.
|
|
||||||
uid, err := uuid.NewV4()
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
|
||||||
c.ClientSecret = uid.String()
|
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
|
||||||
c.ExternalKey = uid.String()
|
|
||||||
_, err = repo.Save(context.Background(), c, channels)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
|
||||||
|
|
||||||
var chs []bootstrap.Channel
|
|
||||||
chs = append(chs, config.Channels...)
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
domainID string
|
|
||||||
connections []string
|
|
||||||
existing []bootstrap.Channel
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "list all existing channels",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
connections: channels,
|
|
||||||
existing: chs,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "list a subset of existing channels",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
connections: []string{channels[0], "5"},
|
|
||||||
existing: []bootstrap.Channel{chs[0]},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "list a subset of existing channels empty",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
connections: []string{"5", "6"},
|
|
||||||
existing: []bootstrap.Channel{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range cases {
|
|
||||||
existing, err := repo.ListExisting(context.Background(), tc.domainID, tc.connections)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error: %s", tc.desc, err))
|
|
||||||
assert.ElementsMatch(t, tc.existing, existing, fmt.Sprintf("%s: Got non-matching elements.", tc.desc))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveClient(t *testing.T) {
|
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
|
||||||
// Use UUID to prevent conflicts.
|
|
||||||
uid, err := uuid.NewV4()
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
|
||||||
c.ClientSecret = uid.String()
|
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
|
||||||
c.ExternalKey = uid.String()
|
|
||||||
saved, err := repo.Save(context.Background(), c, channels)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
err := repo.RemoveClient(context.Background(), saved)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("an unexpected error occurred: %s\n", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateChannel(t *testing.T) {
|
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
|
||||||
// Use UUID to prevent conflicts.
|
|
||||||
uid, err := uuid.NewV4()
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
|
||||||
c.ClientSecret = uid.String()
|
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
|
||||||
c.ExternalKey = uid.String()
|
|
||||||
_, err = repo.Save(context.Background(), c, channels)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
|
||||||
|
|
||||||
id := c.Channels[0].ID
|
|
||||||
update := bootstrap.Channel{
|
|
||||||
ID: id,
|
|
||||||
Name: "update name",
|
|
||||||
Metadata: map[string]any{"update": "metadata update"},
|
|
||||||
}
|
|
||||||
err = repo.UpdateChannel(context.Background(), update)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("updating config expected to succeed: %s.\n", err))
|
|
||||||
|
|
||||||
cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
|
|
||||||
var retrieved bootstrap.Channel
|
|
||||||
for _, c := range cfg.Channels {
|
|
||||||
if c.ID == id {
|
|
||||||
retrieved = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update.DomainID = retrieved.DomainID
|
|
||||||
assert.Equal(t, update, retrieved, fmt.Sprintf("expected %s, go %s", update, retrieved))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveChannel(t *testing.T) {
|
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
c := config
|
||||||
uid, err := uuid.NewV4()
|
uid, err := uuid.NewV4()
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
||||||
c.ClientSecret = uid.String()
|
c.ID = uid.String()
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
c.ExternalID = uid.String()
|
||||||
c.ExternalKey = uid.String()
|
c.ExternalKey = uid.String()
|
||||||
_, err = repo.Save(context.Background(), c, channels)
|
saved, err := configRepo.Save(context.Background(), c)
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
err = repo.RemoveChannel(context.Background(), c.Channels[0].ID)
|
profileID := testsutil.GenerateUUID(t)
|
||||||
assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
|
_, err = profileRepo.Save(context.Background(), bootstrap.Profile{
|
||||||
|
ID: profileID,
|
||||||
|
DomainID: c.DomainID,
|
||||||
|
Name: "edge-gateway",
|
||||||
|
TemplateFormat: bootstrap.TemplateFormatGoTemplate,
|
||||||
|
Version: 1,
|
||||||
|
})
|
||||||
|
require.Nil(t, err, fmt.Sprintf("Saving profile expected to succeed: %s.\n", err))
|
||||||
|
|
||||||
cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID)
|
err = configRepo.AssignProfile(context.Background(), c.DomainID, saved, profileID)
|
||||||
assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
|
require.Nil(t, err, fmt.Sprintf("Assigning profile expected to succeed: %s.\n", err))
|
||||||
assert.NotContains(t, cfg.Channels, c.Channels[0], fmt.Sprintf("expected to remove channel %s from %s", c.Channels[0], cfg.Channels))
|
|
||||||
}
|
stored, err := configRepo.RetrieveByID(context.Background(), c.DomainID, saved)
|
||||||
|
require.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
|
||||||
func TestConnectClient(t *testing.T) {
|
assert.Equal(t, profileID, stored.ProfileID, "expected profile assignment to round-trip through the repository")
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
|
||||||
// Use UUID to prevent conflicts.
|
|
||||||
uid, err := uuid.NewV4()
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
|
||||||
c.ClientSecret = uid.String()
|
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
|
||||||
c.ExternalKey = uid.String()
|
|
||||||
c.State = bootstrap.Inactive
|
|
||||||
saved, err := repo.Save(context.Background(), c, channels)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
|
||||||
|
|
||||||
wrongID := testsutil.GenerateUUID(&testing.T{})
|
|
||||||
|
|
||||||
connectedClient := c
|
|
||||||
|
|
||||||
randomClient := c
|
|
||||||
randomClientID, _ := uuid.NewV4()
|
|
||||||
randomClient.ClientID = randomClientID.String()
|
|
||||||
|
|
||||||
emptyClient := c
|
|
||||||
emptyClient.ClientID = ""
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
domainID string
|
|
||||||
id string
|
|
||||||
state bootstrap.State
|
|
||||||
channels []bootstrap.Channel
|
|
||||||
connections []string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "connect disconnected client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: saved,
|
|
||||||
state: bootstrap.Inactive,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "connect already connected client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: connectedClient.ClientID,
|
|
||||||
state: connectedClient.State,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "connect non-existent client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: wrongID,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: repoerr.ErrNotFound,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "connect random client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: randomClient.ClientID,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: repoerr.ErrNotFound,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "connect empty client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: emptyClient.ClientID,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: repoerr.ErrNotFound,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range cases {
|
|
||||||
for i, ch := range tc.channels {
|
|
||||||
if i == 0 {
|
|
||||||
err = repo.ConnectClient(context.Background(), ch.ID, tc.id)
|
|
||||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: Expected error: %s, got: %s.\n", tc.desc, tc.err, err))
|
|
||||||
cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
|
|
||||||
assert.Equal(t, cfg.State, bootstrap.Active, fmt.Sprintf("expected to be active when a connection is added from %s", cfg))
|
|
||||||
} else {
|
|
||||||
_ = repo.ConnectClient(context.Background(), ch.ID, tc.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
|
|
||||||
assert.Equal(t, cfg.State, bootstrap.Active, fmt.Sprintf("expected to be active when a connection is added from %s", cfg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDisconnectClient(t *testing.T) {
|
|
||||||
repo := postgres.NewConfigRepository(db, testLog)
|
|
||||||
err := deleteChannels(context.Background(), repo)
|
|
||||||
require.Nil(t, err, "Channels cleanup expected to succeed.")
|
|
||||||
|
|
||||||
c := config
|
|
||||||
// Use UUID to prevent conflicts.
|
|
||||||
uid, err := uuid.NewV4()
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
|
|
||||||
c.ClientSecret = uid.String()
|
|
||||||
c.ClientID = uid.String()
|
|
||||||
c.ExternalID = uid.String()
|
|
||||||
c.ExternalKey = uid.String()
|
|
||||||
c.State = bootstrap.Inactive
|
|
||||||
saved, err := repo.Save(context.Background(), c, channels)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
|
|
||||||
|
|
||||||
wrongID := testsutil.GenerateUUID(&testing.T{})
|
|
||||||
|
|
||||||
connectedClient := c
|
|
||||||
|
|
||||||
randomClient := c
|
|
||||||
randomClientID, _ := uuid.NewV4()
|
|
||||||
randomClient.ClientID = randomClientID.String()
|
|
||||||
|
|
||||||
emptyClient := c
|
|
||||||
emptyClient.ClientID = ""
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
domainID string
|
|
||||||
id string
|
|
||||||
state bootstrap.State
|
|
||||||
channels []bootstrap.Channel
|
|
||||||
connections []string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "disconnect connected client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: connectedClient.ClientID,
|
|
||||||
state: connectedClient.State,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "disconnect already disconnected client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: saved,
|
|
||||||
state: bootstrap.Inactive,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "disconnect invalid client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: wrongID,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "disconnect random client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: randomClient.ClientID,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "disconnect empty client",
|
|
||||||
domainID: c.DomainID,
|
|
||||||
id: emptyClient.ClientID,
|
|
||||||
channels: c.Channels,
|
|
||||||
connections: channels,
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
for _, ch := range tc.channels {
|
|
||||||
err = repo.DisconnectClient(context.Background(), ch.ID, tc.id)
|
|
||||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: Expected error: %s, got: %s.\n", tc.desc, tc.err, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := repo.RetrieveByID(context.Background(), c.DomainID, c.ClientID)
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("Retrieving config expected to succeed: %s.\n", err))
|
|
||||||
assert.Equal(t, cfg.State, bootstrap.Inactive, fmt.Sprintf("expected to be inactive when a connection is removed from %s", cfg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteChannels(ctx context.Context, repo bootstrap.ConfigRepository) error {
|
|
||||||
for _, ch := range channels {
|
|
||||||
if err := repo.RemoveChannel(ctx, ch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,218 @@ func Migration() *migrate.MemoryMigrationSource {
|
|||||||
`ALTER TABLE IF EXISTS connections ADD FOREIGN KEY (config_id, domain_id) REFERENCES configs (magistrala_client, domain_id) ON DELETE CASCADE ON UPDATE CASCADE`,
|
`ALTER TABLE IF EXISTS connections ADD FOREIGN KEY (config_id, domain_id) REFERENCES configs (magistrala_client, domain_id) ON DELETE CASCADE ON UPDATE CASCADE`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Id: "configs_7",
|
||||||
|
Up: []string{
|
||||||
|
`ALTER TABLE IF EXISTS configs RENAME COLUMN magistrala_client TO client_id`,
|
||||||
|
`ALTER TABLE IF EXISTS configs RENAME COLUMN magistrala_secret TO client_secret`,
|
||||||
|
`CREATE UNIQUE INDEX IF NOT EXISTS configs_client_id_key ON configs (client_id)`,
|
||||||
|
`CREATE UNIQUE INDEX IF NOT EXISTS configs_client_id_domain_id_key ON configs (client_id, domain_id)`,
|
||||||
|
`DROP TABLE IF EXISTS connections`,
|
||||||
|
`DROP TABLE IF EXISTS channels`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`ALTER TABLE IF EXISTS configs RENAME COLUMN client_id TO magistrala_client`,
|
||||||
|
`ALTER TABLE IF EXISTS configs RENAME COLUMN client_secret TO magistrala_secret`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "configs_8",
|
||||||
|
Up: []string{
|
||||||
|
`DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'configs' AND column_name = 'client_id'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'configs' AND column_name = 'id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE configs RENAME COLUMN client_id TO id;
|
||||||
|
END IF;
|
||||||
|
END $$`,
|
||||||
|
`ALTER TABLE IF EXISTS configs DROP COLUMN IF EXISTS client_secret`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`ALTER TABLE IF EXISTS configs ADD COLUMN IF NOT EXISTS client_secret TEXT`,
|
||||||
|
`DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'configs' AND column_name = 'id'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'configs' AND column_name = 'client_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE configs RENAME COLUMN id TO client_id;
|
||||||
|
END IF;
|
||||||
|
END $$`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "configs_10",
|
||||||
|
Up: []string{
|
||||||
|
`CREATE TABLE IF NOT EXISTS profiles (
|
||||||
|
id VARCHAR(36) PRIMARY KEY,
|
||||||
|
domain_id VARCHAR(36) NOT NULL,
|
||||||
|
name VARCHAR(1024) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
template_format VARCHAR(64) NOT NULL DEFAULT 'go-template',
|
||||||
|
content_template TEXT,
|
||||||
|
defaults JSONB,
|
||||||
|
binding_slots JSONB,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE (domain_id, name)
|
||||||
|
)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_profiles_domain_id ON profiles (domain_id)`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`DROP TABLE IF EXISTS profiles`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "configs_11",
|
||||||
|
Up: []string{
|
||||||
|
`ALTER TABLE IF EXISTS configs ADD COLUMN IF NOT EXISTS profile_id VARCHAR(36) REFERENCES profiles (id) ON DELETE SET NULL`,
|
||||||
|
`ALTER TABLE IF EXISTS configs ADD COLUMN IF NOT EXISTS render_context JSONB`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`ALTER TABLE IF EXISTS configs DROP COLUMN IF EXISTS render_context`,
|
||||||
|
`ALTER TABLE IF EXISTS configs DROP COLUMN IF EXISTS profile_id`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "configs_12",
|
||||||
|
Up: []string{
|
||||||
|
`CREATE TABLE IF NOT EXISTS bindings (
|
||||||
|
config_id TEXT NOT NULL,
|
||||||
|
slot VARCHAR(256) NOT NULL,
|
||||||
|
type VARCHAR(64) NOT NULL,
|
||||||
|
resource_id TEXT NOT NULL,
|
||||||
|
snapshot JSONB,
|
||||||
|
secret_snapshot BYTEA,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
PRIMARY KEY (config_id, slot)
|
||||||
|
)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_bindings_config_id ON bindings (config_id)`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`DROP TABLE IF EXISTS bindings`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "configs_13",
|
||||||
|
Up: []string{
|
||||||
|
`DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'configs' AND column_name = 'state'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'configs' AND column_name = 'status'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE configs RENAME COLUMN state TO status;
|
||||||
|
END IF;
|
||||||
|
END $$`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'configs' AND column_name = 'status'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'configs' AND column_name = 'state'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE configs RENAME COLUMN status TO state;
|
||||||
|
END IF;
|
||||||
|
END $$`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "configs_14",
|
||||||
|
Up: []string{
|
||||||
|
`DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_name = 'binding_snapshots'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_name = 'bindings'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE binding_snapshots RENAME TO bindings;
|
||||||
|
END IF;
|
||||||
|
END $$`,
|
||||||
|
`DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_class
|
||||||
|
WHERE relname = 'idx_binding_snapshots_config_id'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_class
|
||||||
|
WHERE relname = 'idx_bindings_config_id'
|
||||||
|
) THEN
|
||||||
|
ALTER INDEX idx_binding_snapshots_config_id RENAME TO idx_bindings_config_id;
|
||||||
|
END IF;
|
||||||
|
END $$`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_name = 'bindings'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_name = 'binding_snapshots'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE bindings RENAME TO binding_snapshots;
|
||||||
|
END IF;
|
||||||
|
END $$`,
|
||||||
|
`DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_class
|
||||||
|
WHERE relname = 'idx_bindings_config_id'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_class
|
||||||
|
WHERE relname = 'idx_binding_snapshots_config_id'
|
||||||
|
) THEN
|
||||||
|
ALTER INDEX idx_bindings_config_id RENAME TO idx_binding_snapshots_config_id;
|
||||||
|
END IF;
|
||||||
|
END $$`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "configs_15",
|
||||||
|
Up: []string{
|
||||||
|
`ALTER TABLE IF EXISTS profiles ADD COLUMN IF NOT EXISTS binding_slots JSONB`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`ALTER TABLE IF EXISTS profiles DROP COLUMN IF EXISTS binding_slots`,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
|
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||||
|
"github.com/absmach/magistrala/pkg/postgres"
|
||||||
|
"github.com/jackc/pgerrcode"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ bootstrap.ProfileRepository = (*profileRepository)(nil)
|
||||||
|
|
||||||
|
type profileRepository struct {
|
||||||
|
db postgres.Database
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProfileRepository instantiates a PostgreSQL implementation of ProfileRepository.
|
||||||
|
func NewProfileRepository(db postgres.Database, log *slog.Logger) bootstrap.ProfileRepository {
|
||||||
|
return &profileRepository{db: db, log: log}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr profileRepository) Save(ctx context.Context, p bootstrap.Profile) (bootstrap.Profile, error) {
|
||||||
|
q := `INSERT INTO profiles (id, domain_id, name, description, template_format, content_template, defaults, binding_slots, version, created_at, updated_at)
|
||||||
|
VALUES (:id, :domain_id, :name, :description, :template_format, :content_template, :defaults, :binding_slots, :version, :created_at, :updated_at)`
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
p.CreatedAt = now
|
||||||
|
p.UpdatedAt = now
|
||||||
|
|
||||||
|
dbp, err := toDBProfile(p)
|
||||||
|
if err != nil {
|
||||||
|
return bootstrap.Profile{}, errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = pr.db.NamedExecContext(ctx, q, dbp); err != nil {
|
||||||
|
if pgErr, ok := err.(*pgconn.PgError); ok && pgErr.Code == pgerrcode.UniqueViolation {
|
||||||
|
return bootstrap.Profile{}, repoerr.ErrConflict
|
||||||
|
}
|
||||||
|
return bootstrap.Profile{}, errors.Wrap(repoerr.ErrCreateEntity, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr profileRepository) RetrieveByID(ctx context.Context, domainID, id string) (bootstrap.Profile, error) {
|
||||||
|
q := `SELECT id, domain_id, name, description, template_format, content_template, defaults, binding_slots, version, created_at, updated_at
|
||||||
|
FROM profiles WHERE id = $1 AND domain_id = $2`
|
||||||
|
|
||||||
|
var dbp dbProfile
|
||||||
|
if err := pr.db.QueryRowxContext(ctx, q, id, domainID).StructScan(&dbp); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return bootstrap.Profile{}, repoerr.ErrNotFound
|
||||||
|
}
|
||||||
|
return bootstrap.Profile{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return toProfile(dbp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr profileRepository) RetrieveAll(ctx context.Context, domainID string, offset, limit uint64) (bootstrap.ProfilesPage, error) {
|
||||||
|
q := `SELECT id, domain_id, name, description, template_format, content_template, defaults, binding_slots, version, created_at, updated_at
|
||||||
|
FROM profiles WHERE domain_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3`
|
||||||
|
|
||||||
|
rows, err := pr.db.QueryxContext(ctx, q, domainID, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return bootstrap.ProfilesPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var profiles []bootstrap.Profile
|
||||||
|
for rows.Next() {
|
||||||
|
var dbp dbProfile
|
||||||
|
if err := rows.StructScan(&dbp); err != nil {
|
||||||
|
pr.log.Error(fmt.Sprintf("failed to scan profile row: %s", err))
|
||||||
|
return bootstrap.ProfilesPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
p, err := toProfile(dbp)
|
||||||
|
if err != nil {
|
||||||
|
return bootstrap.ProfilesPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
profiles = append(profiles, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
var total uint64
|
||||||
|
if err := pr.db.QueryRowxContext(ctx, `SELECT COUNT(*) FROM profiles WHERE domain_id = $1`, domainID).Scan(&total); err != nil {
|
||||||
|
return bootstrap.ProfilesPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootstrap.ProfilesPage{
|
||||||
|
Total: total,
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Profiles: profiles,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr profileRepository) Update(ctx context.Context, p bootstrap.Profile) error {
|
||||||
|
q := `UPDATE profiles SET name = :name, description = :description, template_format = :template_format,
|
||||||
|
content_template = :content_template, defaults = :defaults, binding_slots = :binding_slots, version = version + 1, updated_at = :updated_at
|
||||||
|
WHERE id = :id AND domain_id = :domain_id`
|
||||||
|
|
||||||
|
p.UpdatedAt = time.Now().UTC()
|
||||||
|
dbp, err := toDBProfile(p)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := pr.db.NamedExecContext(ctx, q, dbp)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||||
|
}
|
||||||
|
cnt, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||||
|
}
|
||||||
|
if cnt == 0 {
|
||||||
|
return repoerr.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr profileRepository) Delete(ctx context.Context, domainID, id string) error {
|
||||||
|
q := `DELETE FROM profiles WHERE id = $1 AND domain_id = $2`
|
||||||
|
if _, err := pr.db.ExecContext(ctx, q, id, domainID); err != nil {
|
||||||
|
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbProfile is the database representation of a Profile.
|
||||||
|
type dbProfile struct {
|
||||||
|
ID string `db:"id"`
|
||||||
|
DomainID string `db:"domain_id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Description sql.NullString `db:"description"`
|
||||||
|
TemplateFormat string `db:"template_format"`
|
||||||
|
ContentTemplate sql.NullString `db:"content_template"`
|
||||||
|
Defaults []byte `db:"defaults"`
|
||||||
|
BindingSlots []byte `db:"binding_slots"`
|
||||||
|
Version int `db:"version"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func toDBProfile(p bootstrap.Profile) (dbProfile, error) {
|
||||||
|
defaults, err := json.Marshal(p.Defaults)
|
||||||
|
if err != nil {
|
||||||
|
return dbProfile{}, err
|
||||||
|
}
|
||||||
|
bindingSlots, err := json.Marshal(p.BindingSlots)
|
||||||
|
if err != nil {
|
||||||
|
return dbProfile{}, err
|
||||||
|
}
|
||||||
|
return dbProfile{
|
||||||
|
ID: p.ID,
|
||||||
|
DomainID: p.DomainID,
|
||||||
|
Name: p.Name,
|
||||||
|
Description: nullString(p.Description),
|
||||||
|
TemplateFormat: string(p.TemplateFormat),
|
||||||
|
ContentTemplate: nullString(p.ContentTemplate),
|
||||||
|
Defaults: defaults,
|
||||||
|
BindingSlots: bindingSlots,
|
||||||
|
Version: p.Version,
|
||||||
|
CreatedAt: p.CreatedAt,
|
||||||
|
UpdatedAt: p.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toProfile(dbp dbProfile) (bootstrap.Profile, error) {
|
||||||
|
p := bootstrap.Profile{
|
||||||
|
ID: dbp.ID,
|
||||||
|
DomainID: dbp.DomainID,
|
||||||
|
Name: dbp.Name,
|
||||||
|
TemplateFormat: bootstrap.TemplateFormat(dbp.TemplateFormat),
|
||||||
|
Version: dbp.Version,
|
||||||
|
CreatedAt: dbp.CreatedAt,
|
||||||
|
UpdatedAt: dbp.UpdatedAt,
|
||||||
|
}
|
||||||
|
if dbp.Description.Valid {
|
||||||
|
p.Description = dbp.Description.String
|
||||||
|
}
|
||||||
|
if dbp.ContentTemplate.Valid {
|
||||||
|
p.ContentTemplate = dbp.ContentTemplate.String
|
||||||
|
}
|
||||||
|
if len(dbp.Defaults) > 0 && string(dbp.Defaults) != jsonNull {
|
||||||
|
if err := json.Unmarshal(dbp.Defaults, &p.Defaults); err != nil {
|
||||||
|
return bootstrap.Profile{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(dbp.BindingSlots) > 0 && string(dbp.BindingSlots) != jsonNull {
|
||||||
|
if err := json.Unmarshal(dbp.BindingSlots, &p.BindingSlots); err != nil {
|
||||||
|
return bootstrap.Profile{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
@@ -70,7 +70,9 @@ func TestMain(m *testing.M) {
|
|||||||
SSLRootCert: "",
|
SSLRootCert: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
if db, err = pgclient.Setup(dbConfig, *postgres.Migration()); err != nil {
|
migration := postgres.Migration()
|
||||||
|
|
||||||
|
if db, err = pgclient.Setup(dbConfig, *migration); err != nil {
|
||||||
testLog.Error(fmt.Sprintf("Could not setup test DB connection: %s", err))
|
testLog.Error(fmt.Sprintf("Could not setup test DB connection: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TemplateFormat enumerates supported content template formats.
|
||||||
|
type TemplateFormat string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TemplateFormatGoTemplate TemplateFormat = "go-template"
|
||||||
|
TemplateFormatRaw TemplateFormat = "raw"
|
||||||
|
TemplateFormatJSON TemplateFormat = "json"
|
||||||
|
TemplateFormatYAML TemplateFormat = "yaml"
|
||||||
|
TemplateFormatTOML TemplateFormat = "toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Profile is a user-managed device configuration template.
|
||||||
|
type Profile struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
DomainID string `json:"domain_id,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
TemplateFormat TemplateFormat `json:"template_format"`
|
||||||
|
ContentTemplate string `json:"content_template,omitempty"`
|
||||||
|
Defaults map[string]any `json:"defaults,omitempty"`
|
||||||
|
BindingSlots []BindingSlot `json:"binding_slots,omitempty"`
|
||||||
|
Version int `json:"version,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingSlot declares a named resource placeholder that a profile template can use.
|
||||||
|
type BindingSlot struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
Fields []string `json:"fields,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfilesPage contains pagination metadata and a slice of Profiles.
|
||||||
|
type ProfilesPage struct {
|
||||||
|
Total uint64 `json:"total"`
|
||||||
|
Offset uint64 `json:"offset"`
|
||||||
|
Limit uint64 `json:"limit"`
|
||||||
|
Profiles []Profile `json:"profiles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileRepository specifies the persistence API for Profiles.
|
||||||
|
type ProfileRepository interface {
|
||||||
|
// Save persists a new Profile and returns it with server-assigned fields set.
|
||||||
|
Save(ctx context.Context, p Profile) (Profile, error)
|
||||||
|
|
||||||
|
// RetrieveByID returns the Profile with the given ID inside the given domain.
|
||||||
|
RetrieveByID(ctx context.Context, domainID, id string) (Profile, error)
|
||||||
|
|
||||||
|
// RetrieveAll returns a page of Profiles belonging to the given domain.
|
||||||
|
RetrieveAll(ctx context.Context, domainID string, offset, limit uint64) (ProfilesPage, error)
|
||||||
|
|
||||||
|
// Update updates editable fields of the given Profile.
|
||||||
|
Update(ctx context.Context, p Profile) error
|
||||||
|
|
||||||
|
// Delete removes the Profile with the given ID from the given domain.
|
||||||
|
Delete(ctx context.Context, domainID, id string) error
|
||||||
|
}
|
||||||
+12
-27
@@ -12,23 +12,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// bootstrapRes represent Magistrala Response to the Bootatrap request.
|
// bootstrapRes represent Magistrala Response to the Bootstrap request.
|
||||||
// This is used as a response from ConfigReader and can easily be
|
// This is used as a response from ConfigReader and can easily be
|
||||||
// replace with any other response format.
|
// replaced with any other response format.
|
||||||
type bootstrapRes struct {
|
type bootstrapRes struct {
|
||||||
ClientID string `json:"client_id"`
|
ID string `json:"id,omitempty"`
|
||||||
ClientSecret string `json:"client_secret"`
|
Content string `json:"content,omitempty"`
|
||||||
Channels []channelRes `json:"channels"`
|
ClientCert string `json:"client_cert,omitempty"`
|
||||||
Content string `json:"content,omitempty"`
|
ClientKey string `json:"client_key,omitempty"`
|
||||||
ClientCert string `json:"client_cert,omitempty"`
|
CACert string `json:"ca_cert,omitempty"`
|
||||||
ClientKey string `json:"client_key,omitempty"`
|
|
||||||
CACert string `json:"ca_cert,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type channelRes struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Metadata any `json:"metadata,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (res bootstrapRes) Code() int {
|
func (res bootstrapRes) Code() int {
|
||||||
@@ -54,19 +46,12 @@ func NewConfigReader(encKey []byte) ConfigReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r reader) ReadConfig(cfg Config, secure bool) (any, error) {
|
func (r reader) ReadConfig(cfg Config, secure bool) (any, error) {
|
||||||
var channels []channelRes
|
|
||||||
for _, ch := range cfg.Channels {
|
|
||||||
channels = append(channels, channelRes{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
|
|
||||||
}
|
|
||||||
|
|
||||||
res := bootstrapRes{
|
res := bootstrapRes{
|
||||||
ClientID: cfg.ClientID,
|
ID: cfg.ID,
|
||||||
ClientSecret: cfg.ClientSecret,
|
Content: cfg.Content,
|
||||||
Channels: channels,
|
ClientCert: cfg.ClientCert,
|
||||||
Content: cfg.Content,
|
ClientKey: cfg.ClientKey,
|
||||||
ClientCert: cfg.ClientCert,
|
CACert: cfg.CACert,
|
||||||
ClientKey: cfg.ClientKey,
|
|
||||||
CACert: cfg.CACert,
|
|
||||||
}
|
}
|
||||||
if secure {
|
if secure {
|
||||||
b, err := json.Marshal(res)
|
b, err := json.Marshal(res)
|
||||||
|
|||||||
+11
-35
@@ -17,20 +17,12 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type readChan struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Metadata any `json:"metadata,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type readResp struct {
|
type readResp struct {
|
||||||
ClientID string `json:"client_id"`
|
ID string `json:"id"`
|
||||||
ClientSecret string `json:"client_secret"`
|
Content string `json:"content,omitempty"`
|
||||||
Channels []readChan `json:"channels"`
|
ClientCert string `json:"client_cert,omitempty"`
|
||||||
Content string `json:"content,omitempty"`
|
ClientKey string `json:"client_key,omitempty"`
|
||||||
ClientCert string `json:"client_cert,omitempty"`
|
CACert string `json:"ca_cert,omitempty"`
|
||||||
ClientKey string `json:"client_key,omitempty"`
|
|
||||||
CACert string `json:"ca_cert,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dec(in []byte) ([]byte, error) {
|
func dec(in []byte) ([]byte, error) {
|
||||||
@@ -50,30 +42,14 @@ func dec(in []byte) ([]byte, error) {
|
|||||||
|
|
||||||
func TestReadConfig(t *testing.T) {
|
func TestReadConfig(t *testing.T) {
|
||||||
cfg := bootstrap.Config{
|
cfg := bootstrap.Config{
|
||||||
ClientID: "smq_id",
|
ID: "smq_id",
|
||||||
ClientCert: "client_cert",
|
ClientCert: "client_cert",
|
||||||
ClientKey: "client_key",
|
ClientKey: "client_key",
|
||||||
CACert: "ca_cert",
|
CACert: "ca_cert",
|
||||||
ClientSecret: "smq_key",
|
Content: "content",
|
||||||
Channels: []bootstrap.Channel{
|
|
||||||
{
|
|
||||||
ID: "smq_id",
|
|
||||||
Name: "smq_name",
|
|
||||||
Metadata: map[string]any{"key": "value}"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Content: "content",
|
|
||||||
}
|
}
|
||||||
ret := readResp{
|
ret := readResp{
|
||||||
ClientID: "smq_id",
|
ID: "smq_id",
|
||||||
ClientSecret: "smq_key",
|
|
||||||
Channels: []readChan{
|
|
||||||
{
|
|
||||||
ID: "smq_id",
|
|
||||||
Name: "smq_name",
|
|
||||||
Metadata: map[string]any{"key": "value}"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Content: "content",
|
Content: "content",
|
||||||
ClientCert: "client_cert",
|
ClientCert: "client_cert",
|
||||||
ClientKey: "client_key",
|
ClientKey: "client_key",
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Renderer renders a Profile's content template into a concrete device
|
||||||
|
// configuration. All input data must already be stored in Bootstrap — no
|
||||||
|
// external service calls are allowed inside Render.
|
||||||
|
type Renderer interface {
|
||||||
|
Render(profile Profile, enrollment Config, bindings []BindingSnapshot) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRenderFailed is returned when template execution or output validation fails.
|
||||||
|
var ErrRenderFailed = errors.New("failed to render profile template")
|
||||||
|
|
||||||
|
type renderer struct{}
|
||||||
|
|
||||||
|
// NewRenderer returns the default Renderer implementation using Go text/template.
|
||||||
|
func NewRenderer() Renderer {
|
||||||
|
return renderer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r renderer) Render(profile Profile, enrollment Config, bindings []BindingSnapshot) ([]byte, error) {
|
||||||
|
rctx := buildRenderContext(profile, enrollment, bindings)
|
||||||
|
|
||||||
|
switch profile.TemplateFormat {
|
||||||
|
case TemplateFormatRaw:
|
||||||
|
return []byte(profile.ContentTemplate), nil
|
||||||
|
case TemplateFormatGoTemplate, TemplateFormatJSON, TemplateFormatYAML, TemplateFormatTOML, "":
|
||||||
|
return r.renderTemplate(profile, rctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%w: unsupported template format %q", ErrRenderFailed, profile.TemplateFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r renderer) renderTemplate(profile Profile, rctx RenderContext) ([]byte, error) {
|
||||||
|
t, err := template.New("bootstrap").
|
||||||
|
Option("missingkey=error").
|
||||||
|
Funcs(allowlistedFuncs()).
|
||||||
|
Parse(profile.ContentTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %w", ErrRenderFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := t.Execute(&buf, rctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %w", ErrRenderFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := validateRenderedOutput(buf.Bytes(), profile.TemplateFormat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateRenderedOutput checks that the rendered bytes are valid for the
|
||||||
|
// declared output format. It returns the original bytes on success and wraps
|
||||||
|
// ErrRenderFailed on failure.
|
||||||
|
func validateRenderedOutput(out []byte, format TemplateFormat) ([]byte, error) {
|
||||||
|
// Unrecognised formats are passed through. Recognised structured formats
|
||||||
|
// must parse successfully so broken templates fail before reaching devices.
|
||||||
|
switch format {
|
||||||
|
case TemplateFormatJSON:
|
||||||
|
var v any
|
||||||
|
if err := json.Unmarshal(out, &v); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: invalid json output: %w", ErrRenderFailed, err)
|
||||||
|
}
|
||||||
|
case TemplateFormatYAML:
|
||||||
|
var v any
|
||||||
|
if err := yaml.Unmarshal(out, &v); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: invalid yaml output: %w", ErrRenderFailed, err)
|
||||||
|
}
|
||||||
|
case TemplateFormatTOML:
|
||||||
|
var v any
|
||||||
|
if err := toml.Unmarshal(out, &v); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: invalid toml output: %w", ErrRenderFailed, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRenderContext constructs the typed RenderContext from stored data.
|
||||||
|
// No external calls are made here.
|
||||||
|
func buildRenderContext(profile Profile, enrollment Config, bindings []BindingSnapshot) RenderContext {
|
||||||
|
vars := make(map[string]any)
|
||||||
|
for k, v := range profile.Defaults {
|
||||||
|
vars[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range enrollment.RenderContext {
|
||||||
|
vars[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
bctx := make(map[string]BindingContext, len(bindings))
|
||||||
|
for _, b := range bindings {
|
||||||
|
bctx[b.Slot] = BindingContext{
|
||||||
|
Type: b.Type,
|
||||||
|
ID: b.ResourceID,
|
||||||
|
Snapshot: b.Snapshot,
|
||||||
|
Secret: b.SecretSnapshot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RenderContext{
|
||||||
|
Device: DeviceContext{
|
||||||
|
ID: enrollment.ID,
|
||||||
|
ExternalID: enrollment.ExternalID,
|
||||||
|
DomainID: enrollment.DomainID,
|
||||||
|
},
|
||||||
|
Vars: vars,
|
||||||
|
Bindings: bctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// allowlistedFuncs returns the safe set of template helper functions.
|
||||||
|
// No function in this map may call an external service or perform I/O.
|
||||||
|
func allowlistedFuncs() template.FuncMap {
|
||||||
|
return template.FuncMap{
|
||||||
|
"toJSON": func(v any) (string, error) {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
},
|
||||||
|
"default": func(def, val any) any {
|
||||||
|
if val == nil || val == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
"required": func(key string, val any) (any, error) {
|
||||||
|
if val == nil || val == "" {
|
||||||
|
return nil, fmt.Errorf("required value %q is missing", key)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRendererStructuredOutputValidation(t *testing.T) {
|
||||||
|
renderer := bootstrap.NewRenderer()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
format bootstrap.TemplateFormat
|
||||||
|
template string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid JSON output",
|
||||||
|
format: bootstrap.TemplateFormatJSON,
|
||||||
|
template: `{"device_id":"{{ .Device.ID }}"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid JSON output",
|
||||||
|
format: bootstrap.TemplateFormatJSON,
|
||||||
|
template: `{"device_id":`,
|
||||||
|
err: bootstrap.ErrRenderFailed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid YAML output",
|
||||||
|
format: bootstrap.TemplateFormatYAML,
|
||||||
|
template: "device_id: {{ .Device.ID }}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid YAML output",
|
||||||
|
format: bootstrap.TemplateFormatYAML,
|
||||||
|
template: "device_id: [",
|
||||||
|
err: bootstrap.ErrRenderFailed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid TOML output",
|
||||||
|
format: bootstrap.TemplateFormatTOML,
|
||||||
|
template: `device_id = "{{ .Device.ID }}"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid TOML output",
|
||||||
|
format: bootstrap.TemplateFormatTOML,
|
||||||
|
template: `device_id = `,
|
||||||
|
err: bootstrap.ErrRenderFailed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
_, err := renderer.Render(
|
||||||
|
bootstrap.Profile{
|
||||||
|
TemplateFormat: tc.format,
|
||||||
|
ContentTemplate: tc.template,
|
||||||
|
},
|
||||||
|
bootstrap.Config{ID: "config-id"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.err, err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
|
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||||
|
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ BindingResolver = (*sdkResolver)(nil)
|
||||||
|
|
||||||
|
type sdkResolver struct {
|
||||||
|
sdk mgsdk.SDK
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSDKResolver returns a BindingResolver that validates resources against
|
||||||
|
// the Magistrala clients and channels services using the SDK. This resolver
|
||||||
|
// is called only at binding time; the render path must never call it.
|
||||||
|
func NewSDKResolver(sdk mgsdk.SDK) BindingResolver {
|
||||||
|
return &sdkResolver{sdk: sdk}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdkResolver) Resolve(ctx context.Context, req ResolveRequest) ([]BindingSnapshot, error) {
|
||||||
|
var snapshots []BindingSnapshot
|
||||||
|
|
||||||
|
for _, br := range req.Requested {
|
||||||
|
snap, err := r.resolveOne(ctx, req.Enrollment.DomainID, req.Token, br)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
snapshots = append(snapshots, snap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdkResolver) resolveOne(ctx context.Context, domainID, token string, br BindingRequest) (BindingSnapshot, error) {
|
||||||
|
switch br.Type {
|
||||||
|
case "client":
|
||||||
|
return r.resolveClient(ctx, domainID, token, br)
|
||||||
|
case "channel":
|
||||||
|
return r.resolveChannel(ctx, domainID, token, br)
|
||||||
|
default:
|
||||||
|
return BindingSnapshot{}, fmt.Errorf("unsupported binding type %q", br.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdkResolver) resolveClient(ctx context.Context, domainID, token string, br BindingRequest) (BindingSnapshot, error) {
|
||||||
|
client, sdkErr := r.sdk.Client(ctx, br.ResourceID, domainID, token)
|
||||||
|
if sdkErr != nil {
|
||||||
|
return BindingSnapshot{}, errors.Wrap(svcerr.ErrNotFound,
|
||||||
|
fmt.Errorf("client %q not found: %s", br.ResourceID, sdkErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot := map[string]any{
|
||||||
|
"id": client.ID,
|
||||||
|
"name": client.Name,
|
||||||
|
}
|
||||||
|
if client.Credentials.Identity != "" {
|
||||||
|
snapshot["identity"] = client.Credentials.Identity
|
||||||
|
}
|
||||||
|
if client.DomainID != "" {
|
||||||
|
snapshot["domain_id"] = client.DomainID
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := map[string]any{}
|
||||||
|
if client.Credentials.Secret != "" {
|
||||||
|
secret["secret"] = client.Credentials.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
return BindingSnapshot{
|
||||||
|
Slot: br.Slot,
|
||||||
|
Type: br.Type,
|
||||||
|
ResourceID: br.ResourceID,
|
||||||
|
Snapshot: snapshot,
|
||||||
|
SecretSnapshot: secret,
|
||||||
|
UpdatedAt: time.Now().UTC(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sdkResolver) resolveChannel(ctx context.Context, domainID, token string, br BindingRequest) (BindingSnapshot, error) {
|
||||||
|
channel, sdkErr := r.sdk.Channel(ctx, br.ResourceID, domainID, token)
|
||||||
|
if sdkErr != nil {
|
||||||
|
return BindingSnapshot{}, errors.Wrap(svcerr.ErrNotFound,
|
||||||
|
fmt.Errorf("channel %q not found: %s", br.ResourceID, sdkErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot := map[string]any{
|
||||||
|
"id": channel.ID,
|
||||||
|
"name": channel.Name,
|
||||||
|
}
|
||||||
|
if channel.Route != "" {
|
||||||
|
snapshot["topic"] = channel.Route
|
||||||
|
}
|
||||||
|
if channel.DomainID != "" {
|
||||||
|
snapshot["domain_id"] = channel.DomainID
|
||||||
|
}
|
||||||
|
if channel.Metadata != nil {
|
||||||
|
snapshot["metadata"] = channel.Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
return BindingSnapshot{
|
||||||
|
Slot: br.Slot,
|
||||||
|
Type: br.Type,
|
||||||
|
ResourceID: br.ResourceID,
|
||||||
|
Snapshot: snapshot,
|
||||||
|
UpdatedAt: time.Now().UTC(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
const secretSnapshotCiphertextKey = "ciphertext"
|
||||||
|
|
||||||
|
func (bs bootstrapService) encryptSecretSnapshots(bindings []BindingSnapshot) ([]BindingSnapshot, error) {
|
||||||
|
encrypted := make([]BindingSnapshot, len(bindings))
|
||||||
|
for i, binding := range bindings {
|
||||||
|
encrypted[i] = binding
|
||||||
|
if len(binding.SecretSnapshot) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
secret, err := json.Marshal(binding.SecretSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ciphertext, err := bs.encrypt(secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encrypted[i].SecretSnapshot = map[string]any{
|
||||||
|
secretSnapshotCiphertextKey: ciphertext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encrypted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) decryptSecretSnapshots(bindings []BindingSnapshot) ([]BindingSnapshot, error) {
|
||||||
|
decrypted := make([]BindingSnapshot, len(bindings))
|
||||||
|
for i, binding := range bindings {
|
||||||
|
decrypted[i] = binding
|
||||||
|
ciphertext, ok := binding.SecretSnapshot[secretSnapshotCiphertextKey].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
plain, err := bs.decrypt(ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var secret map[string]any
|
||||||
|
if err := json.Unmarshal(plain, &secret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decrypted[i].SecretSnapshot = secret
|
||||||
|
}
|
||||||
|
return decrypted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hideSecretSnapshots(bindings []BindingSnapshot) []BindingSnapshot {
|
||||||
|
hidden := make([]BindingSnapshot, len(bindings))
|
||||||
|
for i, binding := range bindings {
|
||||||
|
hidden[i] = binding
|
||||||
|
hidden[i].SecretSnapshot = nil
|
||||||
|
}
|
||||||
|
return hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) encrypt(plain []byte) (string, error) {
|
||||||
|
block, err := aes.NewCipher(bs.encKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ciphertext := make([]byte, aes.BlockSize+len(plain))
|
||||||
|
iv := ciphertext[:aes.BlockSize]
|
||||||
|
if _, err := rand.Read(iv); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
stream := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
stream.XORKeyStream(ciphertext[aes.BlockSize:], plain)
|
||||||
|
return hex.EncodeToString(ciphertext), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) decrypt(in string) ([]byte, error) {
|
||||||
|
ciphertext, err := hex.DecodeString(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
block, err := aes.NewCipher(bs.encKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(ciphertext) < aes.BlockSize {
|
||||||
|
return nil, ErrExternalKeySecure
|
||||||
|
}
|
||||||
|
iv := ciphertext[:aes.BlockSize]
|
||||||
|
ciphertext = ciphertext[aes.BlockSize:]
|
||||||
|
stream := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
stream.XORKeyStream(ciphertext, ciphertext)
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
+344
-313
@@ -14,15 +14,10 @@ import (
|
|||||||
"github.com/absmach/magistrala/pkg/errors"
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||||
"github.com/absmach/magistrala/pkg/policies"
|
|
||||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrClients indicates failure to communicate with Magistrala Clients service.
|
|
||||||
// It can be due to networking error or invalid/unauthenticated request.
|
|
||||||
ErrClients = errors.New("failed to receive response from Clients service")
|
|
||||||
|
|
||||||
// ErrExternalKey indicates a non-existent bootstrap configuration for given external key.
|
// ErrExternalKey indicates a non-existent bootstrap configuration for given external key.
|
||||||
ErrExternalKey = errors.NewAuthZError("failed to get bootstrap configuration for given external key")
|
ErrExternalKey = errors.NewAuthZError("failed to get bootstrap configuration for given external key")
|
||||||
|
|
||||||
@@ -35,25 +30,24 @@ var (
|
|||||||
// ErrAddBootstrap indicates error in adding bootstrap configuration.
|
// ErrAddBootstrap indicates error in adding bootstrap configuration.
|
||||||
ErrAddBootstrap = errors.NewServiceError("failed to add bootstrap configuration")
|
ErrAddBootstrap = errors.NewServiceError("failed to add bootstrap configuration")
|
||||||
|
|
||||||
// ErrBootstrapState indicates an invalid bootstrap state.
|
// ErrBootstrapStatus indicates an invalid bootstrap status.
|
||||||
ErrBootstrapState = errors.NewRequestError("invalid bootstrap state")
|
ErrBootstrapStatus = errors.NewRequestError("invalid bootstrap status")
|
||||||
|
|
||||||
// ErrNotInSameDomain indicates entities are not in the same domain.
|
errRemoveBootstrap = errors.New("failed to remove bootstrap configuration")
|
||||||
errNotInSameDomain = errors.New("entities are not in the same domain")
|
errEnableConfig = errors.New("failed to enable bootstrap configuration")
|
||||||
|
errDisableConfig = errors.New("failed to disable bootstrap configuration")
|
||||||
|
errUpdateCert = errors.New("failed to update cert")
|
||||||
|
|
||||||
errUpdateConnections = errors.New("failed to update connections")
|
errCreateProfile = errors.New("failed to create profile")
|
||||||
errRemoveBootstrap = errors.New("failed to remove bootstrap configuration")
|
errViewProfile = errors.New("failed to view profile")
|
||||||
errChangeState = errors.New("failed to change state of bootstrap configuration")
|
errUpdateProfile = errors.New("failed to update profile")
|
||||||
errUpdateChannel = errors.New("failed to update channel")
|
errDeleteProfile = errors.New("failed to delete profile")
|
||||||
errRemoveConfig = errors.New("failed to remove bootstrap configuration")
|
errListProfiles = errors.New("failed to list profiles")
|
||||||
errRemoveChannel = errors.New("failed to remove channel")
|
errAssignProfile = errors.New("failed to assign profile to enrollment")
|
||||||
errCreateClient = errors.New("failed to create client")
|
errBindResources = errors.New("failed to bind resources")
|
||||||
errConnectClient = errors.New("failed to connect client")
|
errListBindings = errors.New("failed to list bindings")
|
||||||
errDisconnectClient = errors.New("failed to disconnect client")
|
errRefreshBinding = errors.New("failed to refresh bindings")
|
||||||
errCheckChannels = errors.New("failed to check if channels exists")
|
errRenderBootstrap = errors.New("failed to render bootstrap configuration")
|
||||||
errConnectionChannels = errors.New("failed to check channels connections")
|
|
||||||
errClientNotFound = errors.New("failed to find client")
|
|
||||||
errUpdateCert = errors.New("failed to update cert")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Service = (*bootstrapService)(nil)
|
var _ Service = (*bootstrapService)(nil)
|
||||||
@@ -72,10 +66,7 @@ type Service interface {
|
|||||||
|
|
||||||
// UpdateCert updates an existing Config certificate and token.
|
// UpdateCert updates an existing Config certificate and token.
|
||||||
// A non-nil error is returned to indicate operation failure.
|
// A non-nil error is returned to indicate operation failure.
|
||||||
UpdateCert(ctx context.Context, session smqauthn.Session, clientID, clientCert, clientKey, caCert string) (Config, error)
|
UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (Config, error)
|
||||||
|
|
||||||
// UpdateConnections updates list of Channels related to given Config.
|
|
||||||
UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error
|
|
||||||
|
|
||||||
// List returns subset of Configs with given search params that belong to the
|
// List returns subset of Configs with given search params that belong to the
|
||||||
// user identified by the given token.
|
// user identified by the given token.
|
||||||
@@ -87,26 +78,41 @@ type Service interface {
|
|||||||
// Bootstrap returns Config to the Client with provided external ID using external key.
|
// Bootstrap returns Config to the Client with provided external ID using external key.
|
||||||
Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (Config, error)
|
Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (Config, error)
|
||||||
|
|
||||||
// ChangeState changes state of the Client with given client ID and domain ID.
|
// EnableConfig enables the Config so its device can successfully bootstrap.
|
||||||
ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state State) error
|
EnableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error)
|
||||||
|
|
||||||
// Methods RemoveConfig, UpdateChannel, and RemoveChannel are used as
|
// DisableConfig disables the Config, preventing its device from bootstrapping.
|
||||||
// handlers for events. That's why these methods surpass ownership check.
|
DisableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error)
|
||||||
|
|
||||||
// UpdateChannelHandler updates Channel with data received from an event.
|
// CreateProfile persists a new device Profile.
|
||||||
UpdateChannelHandler(ctx context.Context, channel Channel) error
|
CreateProfile(ctx context.Context, session smqauthn.Session, p Profile) (Profile, error)
|
||||||
|
|
||||||
// RemoveConfigHandler removes Configuration with id received from an event.
|
// ViewProfile returns the Profile with the given ID.
|
||||||
RemoveConfigHandler(ctx context.Context, id string) error
|
ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (Profile, error)
|
||||||
|
|
||||||
// RemoveChannelHandler removes Channel with id received from an event.
|
// UpdateProfile updates editable fields of the given Profile.
|
||||||
RemoveChannelHandler(ctx context.Context, id string) error
|
UpdateProfile(ctx context.Context, session smqauthn.Session, p Profile) error
|
||||||
|
|
||||||
// ConnectClientHandler changes state of the Config to active when connect event occurs.
|
// ListProfiles returns a page of Profiles belonging to the domain.
|
||||||
ConnectClientHandler(ctx context.Context, channelID, clientID string) error
|
ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64) (ProfilesPage, error)
|
||||||
|
|
||||||
// DisconnectClientHandler changes state of the Config to inactive when disconnect event occurs.
|
// DeleteProfile removes the Profile with the given ID.
|
||||||
DisconnectClientHandler(ctx context.Context, channelID, clientID string) error
|
DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error
|
||||||
|
|
||||||
|
// AssignProfile sets the ProfileID on an existing enrollment (Config).
|
||||||
|
AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error
|
||||||
|
|
||||||
|
// BindResources resolves the requested bindings through their owning services,
|
||||||
|
// stores snapshots, and marks the enrollment renderable when all required slots
|
||||||
|
// are satisfied.
|
||||||
|
BindResources(ctx context.Context, session smqauthn.Session, token, configID string, bindings []BindingRequest) error
|
||||||
|
|
||||||
|
// ListBindings returns all stored binding snapshots for an enrollment.
|
||||||
|
ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]BindingSnapshot, error)
|
||||||
|
|
||||||
|
// RefreshBindings re-resolves all existing bindings for an enrollment and
|
||||||
|
// updates the stored snapshots.
|
||||||
|
RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigReader is used to parse Config into format which will be encoded
|
// ConfigReader is used to parse Config into format which will be encoded
|
||||||
@@ -118,70 +124,67 @@ type ConfigReader interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bootstrapService struct {
|
type bootstrapService struct {
|
||||||
policies policies.Service
|
|
||||||
configs ConfigRepository
|
configs ConfigRepository
|
||||||
|
profiles ProfileRepository
|
||||||
|
bindings BindingStore
|
||||||
|
resolver BindingResolver
|
||||||
|
renderer Renderer
|
||||||
|
hasher Hasher
|
||||||
sdk mgsdk.SDK
|
sdk mgsdk.SDK
|
||||||
encKey []byte
|
encKey []byte
|
||||||
idProvider magistrala.IDProvider
|
idProvider magistrala.IDProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns new Bootstrap service.
|
// New returns new Bootstrap service.
|
||||||
func New(policyService policies.Service, configs ConfigRepository, sdk mgsdk.SDK, encKey []byte, idp magistrala.IDProvider) Service {
|
func New(
|
||||||
|
configs ConfigRepository,
|
||||||
|
profiles ProfileRepository,
|
||||||
|
bindings BindingStore,
|
||||||
|
resolver BindingResolver,
|
||||||
|
renderer Renderer,
|
||||||
|
sdk mgsdk.SDK,
|
||||||
|
hasher Hasher,
|
||||||
|
encKey []byte,
|
||||||
|
idp magistrala.IDProvider,
|
||||||
|
) Service {
|
||||||
return &bootstrapService{
|
return &bootstrapService{
|
||||||
configs: configs,
|
configs: configs,
|
||||||
|
profiles: profiles,
|
||||||
|
bindings: bindings,
|
||||||
|
resolver: resolver,
|
||||||
|
renderer: renderer,
|
||||||
|
hasher: hasher,
|
||||||
sdk: sdk,
|
sdk: sdk,
|
||||||
policies: policyService,
|
|
||||||
encKey: encKey,
|
encKey: encKey,
|
||||||
idProvider: idp,
|
idProvider: idp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs bootstrapService) Add(ctx context.Context, session smqauthn.Session, token string, cfg Config) (Config, error) {
|
func (bs bootstrapService) Add(ctx context.Context, session smqauthn.Session, token string, cfg Config) (Config, error) {
|
||||||
toConnect := bs.toIDList(cfg.Channels)
|
id, err := bs.idProvider.ID()
|
||||||
|
|
||||||
// Check if channels exist. This is the way to prevent fetching channels that already exist.
|
|
||||||
existing, err := bs.configs.ListExisting(ctx, session.DomainID, toConnect)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Config{}, errors.Wrap(errCheckChannels, err)
|
return Config{}, errors.Wrap(ErrAddBootstrap, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Channels, err = bs.connectionChannels(ctx, toConnect, bs.toIDList(existing), session.DomainID, token)
|
hashedKey, err := bs.hasher.Hash(cfg.ExternalKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Config{}, errors.Wrap(errConnectionChannels, err)
|
return Config{}, errors.Wrap(ErrAddBootstrap, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
id := cfg.ClientID
|
cfg.ID = id
|
||||||
mgClient, err := bs.client(ctx, session.DomainID, id, token)
|
|
||||||
if err != nil {
|
|
||||||
return Config{}, errors.Wrap(errClientNotFound, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, channel := range cfg.Channels {
|
|
||||||
if channel.DomainID != mgClient.DomainID {
|
|
||||||
return Config{}, errors.Wrap(svcerr.ErrMalformedEntity, errNotInSameDomain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.ClientID = mgClient.ID
|
|
||||||
cfg.DomainID = session.DomainID
|
cfg.DomainID = session.DomainID
|
||||||
cfg.State = Inactive
|
cfg.Status = Active
|
||||||
cfg.ClientSecret = mgClient.Credentials.Secret
|
cfg.ExternalKey = hashedKey
|
||||||
|
|
||||||
saved, err := bs.configs.Save(ctx, cfg, toConnect)
|
saved, err := bs.configs.Save(ctx, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If id is empty, then a new client has been created function - bs.client(id, token)
|
if errors.Contains(err, repoerr.ErrConflict) {
|
||||||
// So, on bootstrap config save error , delete the newly created client.
|
return Config{}, errors.Wrap(svcerr.ErrConflict, err)
|
||||||
if id == "" {
|
|
||||||
if errT := bs.sdk.DeleteClient(ctx, cfg.ClientID, cfg.DomainID, token); errT != nil {
|
|
||||||
err = errors.Wrap(err, errT)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Config{}, errors.Wrap(ErrAddBootstrap, err)
|
return Config{}, errors.Wrap(ErrAddBootstrap, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.ClientID = saved
|
cfg.ID = saved
|
||||||
cfg.Channels = append(cfg.Channels, existing...)
|
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,105 +199,21 @@ func (bs bootstrapService) View(ctx context.Context, session smqauthn.Session, i
|
|||||||
func (bs bootstrapService) Update(ctx context.Context, session smqauthn.Session, cfg Config) error {
|
func (bs bootstrapService) Update(ctx context.Context, session smqauthn.Session, cfg Config) error {
|
||||||
cfg.DomainID = session.DomainID
|
cfg.DomainID = session.DomainID
|
||||||
if err := bs.configs.Update(ctx, cfg); err != nil {
|
if err := bs.configs.Update(ctx, cfg); err != nil {
|
||||||
return errors.Wrap(errUpdateConnections, err)
|
return errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs bootstrapService) UpdateCert(ctx context.Context, session smqauthn.Session, clientID, clientCert, clientKey, caCert string) (Config, error) {
|
func (bs bootstrapService) UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (Config, error) {
|
||||||
cfg, err := bs.configs.UpdateCert(ctx, session.DomainID, clientID, clientCert, clientKey, caCert)
|
cfg, err := bs.configs.UpdateCert(ctx, session.DomainID, id, clientCert, clientKey, caCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Config{}, errors.Wrap(errUpdateCert, err)
|
return Config{}, errors.Wrap(errUpdateCert, err)
|
||||||
}
|
}
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs bootstrapService) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error {
|
|
||||||
cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(errUpdateConnections, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
add, remove := bs.updateList(cfg, connections)
|
|
||||||
|
|
||||||
// Check if channels exist. This is the way to prevent fetching channels that already exist.
|
|
||||||
existing, err := bs.configs.ListExisting(ctx, session.DomainID, connections)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(errUpdateConnections, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
channels, err := bs.connectionChannels(ctx, connections, bs.toIDList(existing), session.DomainID, token)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(errUpdateConnections, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.Channels = channels
|
|
||||||
var connect, disconnect []string
|
|
||||||
|
|
||||||
if cfg.State == Active {
|
|
||||||
connect = add
|
|
||||||
disconnect = remove
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range disconnect {
|
|
||||||
if err := bs.sdk.DisconnectClients(ctx, c, []string{id}, []string{"Publish", "Subscribe"}, session.DomainID, token); err != nil {
|
|
||||||
if errors.Contains(err, repoerr.ErrNotFound) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return ErrClients
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range connect {
|
|
||||||
conIDs := mgsdk.Connection{
|
|
||||||
ChannelIDs: []string{c},
|
|
||||||
ClientIDs: []string{id},
|
|
||||||
Types: []string{"Publish", "Subscribe"},
|
|
||||||
}
|
|
||||||
if err := bs.sdk.Connect(ctx, conIDs, session.DomainID, token); err != nil {
|
|
||||||
return ErrClients
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := bs.configs.UpdateConnections(ctx, session.DomainID, id, channels, connections); err != nil {
|
|
||||||
return errors.Wrap(errUpdateConnections, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs bootstrapService) listClientIDs(ctx context.Context, userID string) ([]string, error) {
|
|
||||||
tids, err := bs.policies.ListAllObjects(ctx, policies.Policy{
|
|
||||||
SubjectType: policies.UserType,
|
|
||||||
Subject: userID,
|
|
||||||
Permission: policies.ViewPermission,
|
|
||||||
ObjectType: policies.ClientType,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(svcerr.ErrNotFound, err)
|
|
||||||
}
|
|
||||||
return tids.Policies, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs bootstrapService) List(ctx context.Context, session smqauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error) {
|
func (bs bootstrapService) List(ctx context.Context, session smqauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error) {
|
||||||
if session.SuperAdmin {
|
return bs.configs.RetrieveAll(ctx, session.DomainID, filter, offset, limit), nil
|
||||||
return bs.configs.RetrieveAll(ctx, session.DomainID, []string{}, filter, offset, limit), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle non-admin users
|
|
||||||
clientIDs, err := bs.listClientIDs(ctx, session.DomainUserID)
|
|
||||||
if err != nil {
|
|
||||||
return ConfigsPage{}, errors.Wrap(svcerr.ErrNotFound, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(clientIDs) == 0 {
|
|
||||||
return ConfigsPage{
|
|
||||||
Total: 0,
|
|
||||||
Offset: offset,
|
|
||||||
Limit: limit,
|
|
||||||
Configs: []Config{},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return bs.configs.RetrieveAll(ctx, session.DomainID, clientIDs, filter, offset, limit), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs bootstrapService) Remove(ctx context.Context, session smqauthn.Session, id string) error {
|
func (bs bootstrapService) Remove(ctx context.Context, session smqauthn.Session, id string) error {
|
||||||
@@ -316,171 +235,283 @@ func (bs bootstrapService) Bootstrap(ctx context.Context, externalKey, externalI
|
|||||||
}
|
}
|
||||||
externalKey = dec
|
externalKey = dec
|
||||||
}
|
}
|
||||||
if cfg.ExternalKey != externalKey {
|
|
||||||
|
if err := bs.hasher.Compare(externalKey, cfg.ExternalKey); err != nil {
|
||||||
return Config{}, ErrExternalKey
|
return Config{}, ErrExternalKey
|
||||||
}
|
}
|
||||||
|
if cfg.Status == DisabledStatus {
|
||||||
|
return Config{}, ErrBootstrap
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err = bs.renderBootstrapConfig(ctx, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, errors.Wrap(ErrBootstrap, err)
|
||||||
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs bootstrapService) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state State) error {
|
func (bs bootstrapService) renderBootstrapConfig(ctx context.Context, cfg Config) (Config, error) {
|
||||||
cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, id)
|
if cfg.ProfileID == "" {
|
||||||
if err != nil {
|
return cfg, nil
|
||||||
return errors.Wrap(errChangeState, err)
|
}
|
||||||
|
if bs.profiles == nil || bs.bindings == nil || bs.renderer == nil {
|
||||||
|
return Config{}, errors.Wrap(errRenderBootstrap, errors.New("profile rendering support not configured"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.State == state {
|
profile, err := bs.profiles.RetrieveByID(ctx, cfg.DomainID, cfg.ProfileID)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindings, err := bs.bindings.Retrieve(ctx, cfg.ID)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
||||||
|
}
|
||||||
|
if err := validateRequiredBindings(profile, bindings); err != nil {
|
||||||
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
||||||
|
}
|
||||||
|
bindings, err = bs.decryptSecretSnapshots(bindings)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rendered, err := bs.renderer.Render(profile, cfg, bindings)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, errors.Wrap(errRenderBootstrap, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Content = string(rendered)
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) EnableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error) {
|
||||||
|
cfg, err := bs.changeConfigStatus(ctx, session.DomainID, id, EnabledStatus)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, errors.Wrap(errEnableConfig, err)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) DisableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error) {
|
||||||
|
cfg, err := bs.changeConfigStatus(ctx, session.DomainID, id, DisabledStatus)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, errors.Wrap(errDisableConfig, err)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) changeConfigStatus(ctx context.Context, domainID, id string, status Status) (Config, error) {
|
||||||
|
cfg, err := bs.configs.RetrieveByID(ctx, domainID, id)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||||
|
}
|
||||||
|
if cfg.Status == status {
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
if err := bs.configs.ChangeStatus(ctx, domainID, id, status); err != nil {
|
||||||
|
return Config{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||||
|
}
|
||||||
|
cfg.Status = status
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Profile management ---
|
||||||
|
|
||||||
|
func (bs bootstrapService) CreateProfile(ctx context.Context, session smqauthn.Session, p Profile) (Profile, error) {
|
||||||
|
if bs.profiles == nil {
|
||||||
|
return Profile{}, errors.Wrap(errCreateProfile, errors.New("profile repository not configured"))
|
||||||
|
}
|
||||||
|
id, err := bs.idProvider.ID()
|
||||||
|
if err != nil {
|
||||||
|
return Profile{}, errors.Wrap(errCreateProfile, err)
|
||||||
|
}
|
||||||
|
p.ID = id
|
||||||
|
p.DomainID = session.DomainID
|
||||||
|
if p.TemplateFormat == "" {
|
||||||
|
p.TemplateFormat = TemplateFormatGoTemplate
|
||||||
|
}
|
||||||
|
p.Version = 1
|
||||||
|
if err := validateProfileBindingSlots(p); err != nil {
|
||||||
|
return Profile{}, errors.Wrap(errCreateProfile, err)
|
||||||
|
}
|
||||||
|
if err := validateProfileTemplate(p); err != nil {
|
||||||
|
return Profile{}, errors.Wrap(errCreateProfile, err)
|
||||||
|
}
|
||||||
|
saved, err := bs.profiles.Save(ctx, p)
|
||||||
|
if err != nil {
|
||||||
|
return Profile{}, errors.Wrap(errCreateProfile, err)
|
||||||
|
}
|
||||||
|
return saved, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (Profile, error) {
|
||||||
|
if bs.profiles == nil {
|
||||||
|
return Profile{}, errors.Wrap(errViewProfile, errors.New("profile repository not configured"))
|
||||||
|
}
|
||||||
|
p, err := bs.profiles.RetrieveByID(ctx, session.DomainID, profileID)
|
||||||
|
if err != nil {
|
||||||
|
return Profile{}, errors.Wrap(errViewProfile, err)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) UpdateProfile(ctx context.Context, session smqauthn.Session, p Profile) error {
|
||||||
|
if bs.profiles == nil {
|
||||||
|
return errors.Wrap(errUpdateProfile, errors.New("profile repository not configured"))
|
||||||
|
}
|
||||||
|
p.DomainID = session.DomainID
|
||||||
|
if p.TemplateFormat == "" {
|
||||||
|
p.TemplateFormat = TemplateFormatGoTemplate
|
||||||
|
}
|
||||||
|
if err := validateProfileBindingSlots(p); err != nil {
|
||||||
|
return errors.Wrap(errUpdateProfile, err)
|
||||||
|
}
|
||||||
|
if err := validateProfileTemplate(p); err != nil {
|
||||||
|
return errors.Wrap(errUpdateProfile, err)
|
||||||
|
}
|
||||||
|
if err := bs.profiles.Update(ctx, p); err != nil {
|
||||||
|
return errors.Wrap(errUpdateProfile, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64) (ProfilesPage, error) {
|
||||||
|
if bs.profiles == nil {
|
||||||
|
return ProfilesPage{}, errors.Wrap(errListProfiles, errors.New("profile repository not configured"))
|
||||||
|
}
|
||||||
|
page, err := bs.profiles.RetrieveAll(ctx, session.DomainID, offset, limit)
|
||||||
|
if err != nil {
|
||||||
|
return ProfilesPage{}, errors.Wrap(errListProfiles, err)
|
||||||
|
}
|
||||||
|
return page, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error {
|
||||||
|
if bs.profiles == nil {
|
||||||
|
return errors.Wrap(errDeleteProfile, errors.New("profile repository not configured"))
|
||||||
|
}
|
||||||
|
if err := bs.profiles.Delete(ctx, session.DomainID, profileID); err != nil {
|
||||||
|
return errors.Wrap(errDeleteProfile, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Enrollment-profile assignment ---
|
||||||
|
|
||||||
|
func (bs bootstrapService) AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error {
|
||||||
|
if bs.profiles == nil {
|
||||||
|
return errors.Wrap(errAssignProfile, errors.New("profile repository not configured"))
|
||||||
|
}
|
||||||
|
// Validate profile exists in domain.
|
||||||
|
if _, err := bs.profiles.RetrieveByID(ctx, session.DomainID, profileID); err != nil {
|
||||||
|
return errors.Wrap(errAssignProfile, err)
|
||||||
|
}
|
||||||
|
if err := bs.configs.AssignProfile(ctx, session.DomainID, configID, profileID); err != nil {
|
||||||
|
return errors.Wrap(errAssignProfile, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Binding management ---
|
||||||
|
|
||||||
|
func (bs bootstrapService) BindResources(ctx context.Context, session smqauthn.Session, token, configID string, requested []BindingRequest) error {
|
||||||
|
if bs.profiles == nil || bs.bindings == nil || bs.resolver == nil {
|
||||||
|
return errors.Wrap(errBindResources, errors.New("binding support not configured"))
|
||||||
|
}
|
||||||
|
cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, configID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errBindResources, err)
|
||||||
|
}
|
||||||
|
profile, err := bs.profiles.RetrieveByID(ctx, session.DomainID, cfg.ProfileID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errBindResources, err)
|
||||||
|
}
|
||||||
|
if err := validateRequestedBindings(profile, requested); err != nil {
|
||||||
|
return errors.Wrap(errBindResources, err)
|
||||||
|
}
|
||||||
|
snapshots, err := bs.resolver.Resolve(ctx, ResolveRequest{
|
||||||
|
Enrollment: cfg,
|
||||||
|
Token: token,
|
||||||
|
Requested: requested,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errBindResources, err)
|
||||||
|
}
|
||||||
|
existing, err := bs.bindings.Retrieve(ctx, configID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errBindResources, err)
|
||||||
|
}
|
||||||
|
if err := validateRequiredBindings(profile, mergeBindingSnapshots(existing, snapshots)); err != nil {
|
||||||
|
return errors.Wrap(errBindResources, err)
|
||||||
|
}
|
||||||
|
snapshots, err = bs.encryptSecretSnapshots(snapshots)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errBindResources, err)
|
||||||
|
}
|
||||||
|
if err := bs.bindings.Save(ctx, configID, snapshots); err != nil {
|
||||||
|
return errors.Wrap(errBindResources, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]BindingSnapshot, error) {
|
||||||
|
if bs.bindings == nil {
|
||||||
|
return nil, errors.Wrap(errListBindings, errors.New("binding support not configured"))
|
||||||
|
}
|
||||||
|
if _, err := bs.configs.RetrieveByID(ctx, session.DomainID, configID); err != nil {
|
||||||
|
return nil, errors.Wrap(errListBindings, err)
|
||||||
|
}
|
||||||
|
snapshots, err := bs.bindings.Retrieve(ctx, configID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(errListBindings, err)
|
||||||
|
}
|
||||||
|
return hideSecretSnapshots(snapshots), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs bootstrapService) RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error {
|
||||||
|
if bs.profiles == nil || bs.bindings == nil || bs.resolver == nil {
|
||||||
|
return errors.Wrap(errRefreshBinding, errors.New("binding support not configured"))
|
||||||
|
}
|
||||||
|
cfg, err := bs.configs.RetrieveByID(ctx, session.DomainID, configID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errRefreshBinding, err)
|
||||||
|
}
|
||||||
|
profile, err := bs.profiles.RetrieveByID(ctx, session.DomainID, cfg.ProfileID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errRefreshBinding, err)
|
||||||
|
}
|
||||||
|
existing, err := bs.bindings.Retrieve(ctx, configID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errRefreshBinding, err)
|
||||||
|
}
|
||||||
|
if len(existing) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Re-resolve every existing binding to refresh its snapshot.
|
||||||
switch state {
|
requested := make([]BindingRequest, len(existing))
|
||||||
case Active:
|
for i, b := range existing {
|
||||||
for _, c := range cfg.Channels {
|
requested[i] = BindingRequest{Slot: b.Slot, Type: b.Type, ResourceID: b.ResourceID}
|
||||||
if err := bs.sdk.ConnectClients(ctx, c.ID, []string{cfg.ClientID}, []string{"Publish", "Subscribe"}, session.DomainID, token); err != nil {
|
|
||||||
// Ignore conflict errors as they indicate the connection already exists.
|
|
||||||
if errors.Contains(err, svcerr.ErrConflict) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return ErrClients
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Inactive:
|
|
||||||
for _, c := range cfg.Channels {
|
|
||||||
if err := bs.sdk.DisconnectClients(ctx, c.ID, []string{cfg.ClientID}, []string{"Publish", "Subscribe"}, session.DomainID, token); err != nil {
|
|
||||||
if errors.Contains(err, repoerr.ErrNotFound) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return ErrClients
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err := bs.configs.ChangeState(ctx, session.DomainID, id, state); err != nil {
|
if err := validateRequestedBindings(profile, requested); err != nil {
|
||||||
return errors.Wrap(errChangeState, err)
|
return errors.Wrap(errRefreshBinding, err)
|
||||||
}
|
}
|
||||||
return nil
|
refreshed, err := bs.resolver.Resolve(ctx, ResolveRequest{
|
||||||
}
|
Enrollment: cfg,
|
||||||
|
Token: token,
|
||||||
func (bs bootstrapService) UpdateChannelHandler(ctx context.Context, channel Channel) error {
|
Requested: requested,
|
||||||
if err := bs.configs.UpdateChannel(ctx, channel); err != nil {
|
})
|
||||||
return errors.Wrap(errUpdateChannel, err)
|
if err != nil {
|
||||||
|
return errors.Wrap(errRefreshBinding, err)
|
||||||
}
|
}
|
||||||
return nil
|
if err := validateRequiredBindings(profile, refreshed); err != nil {
|
||||||
}
|
return errors.Wrap(errRefreshBinding, err)
|
||||||
|
|
||||||
func (bs bootstrapService) RemoveConfigHandler(ctx context.Context, id string) error {
|
|
||||||
if err := bs.configs.RemoveClient(ctx, id); err != nil {
|
|
||||||
return errors.Wrap(errRemoveConfig, err)
|
|
||||||
}
|
}
|
||||||
return nil
|
refreshed, err = bs.encryptSecretSnapshots(refreshed)
|
||||||
}
|
if err != nil {
|
||||||
|
return errors.Wrap(errRefreshBinding, err)
|
||||||
func (bs bootstrapService) RemoveChannelHandler(ctx context.Context, id string) error {
|
|
||||||
if err := bs.configs.RemoveChannel(ctx, id); err != nil {
|
|
||||||
return errors.Wrap(errRemoveChannel, err)
|
|
||||||
}
|
}
|
||||||
return nil
|
return bs.bindings.Save(ctx, configID, refreshed)
|
||||||
}
|
|
||||||
|
|
||||||
func (bs bootstrapService) ConnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
|
||||||
if err := bs.configs.ConnectClient(ctx, channelID, clientID); err != nil {
|
|
||||||
return errors.Wrap(errConnectClient, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs bootstrapService) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
|
||||||
if err := bs.configs.DisconnectClient(ctx, channelID, clientID); err != nil {
|
|
||||||
return errors.Wrap(errDisconnectClient, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method client retrieves Magistrala Client creating one if an empty ID is passed.
|
|
||||||
func (bs bootstrapService) client(ctx context.Context, domainID, id, token string) (mgsdk.Client, error) {
|
|
||||||
// If Client ID is not provided, then create new client.
|
|
||||||
if id == "" {
|
|
||||||
id, err := bs.idProvider.ID()
|
|
||||||
if err != nil {
|
|
||||||
return mgsdk.Client{}, errors.Wrap(errCreateClient, err)
|
|
||||||
}
|
|
||||||
client, sdkErr := bs.sdk.CreateClient(ctx, mgsdk.Client{ID: id, Name: "Bootstrapped Client " + id}, domainID, token)
|
|
||||||
if sdkErr != nil {
|
|
||||||
return mgsdk.Client{}, errors.Wrap(errCreateClient, sdkErr)
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
// If Client ID is provided, then retrieve client
|
|
||||||
client, sdkErr := bs.sdk.Client(ctx, id, domainID, token)
|
|
||||||
if sdkErr != nil {
|
|
||||||
return mgsdk.Client{}, errors.Wrap(ErrClients, sdkErr)
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs bootstrapService) connectionChannels(ctx context.Context, channels, existing []string, domainID, token string) ([]Channel, error) {
|
|
||||||
add := make(map[string]bool, len(channels))
|
|
||||||
for _, ch := range channels {
|
|
||||||
add[ch] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ch := range existing {
|
|
||||||
if add[ch] {
|
|
||||||
delete(add, ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret []Channel
|
|
||||||
for id := range add {
|
|
||||||
ch, err := bs.sdk.Channel(ctx, id, domainID, token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(errors.ErrMalformedEntity, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, Channel{
|
|
||||||
ID: ch.ID,
|
|
||||||
Name: ch.Name,
|
|
||||||
Metadata: ch.Metadata,
|
|
||||||
DomainID: ch.DomainID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
// 3) IDs of common Channels for these two configs.
|
|
||||||
func (bs bootstrapService) updateList(cfg Config, connections []string) (add, remove []string) {
|
|
||||||
disconnect := make(map[string]bool, len(cfg.Channels))
|
|
||||||
for _, c := range cfg.Channels {
|
|
||||||
disconnect[c.ID] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range connections {
|
|
||||||
if disconnect[c] {
|
|
||||||
// Don't disconnect common elements.
|
|
||||||
delete(disconnect, c)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Connect new elements.
|
|
||||||
add = append(add, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
for v := range disconnect {
|
|
||||||
remove = append(remove, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bs bootstrapService) toIDList(channels []Channel) []string {
|
|
||||||
var ret []string
|
|
||||||
for _, ch := range channels {
|
|
||||||
ret = append(ret, ch.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs bootstrapService) dec(in string) (string, error) {
|
func (bs bootstrapService) dec(in string) (string, error) {
|
||||||
|
|||||||
+922
-421
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
|||||||
// Copyright (c) Abstract Machines
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package bootstrap
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Inactive Client is created, but not able to exchange messages using Magistrala.
|
|
||||||
Inactive State = iota
|
|
||||||
// Active Client is created, configured, and whitelisted.
|
|
||||||
Active
|
|
||||||
)
|
|
||||||
|
|
||||||
// State represents corresponding Magistrala Client state. The possible Config States
|
|
||||||
// as well as description of what that State represents are given in the table:
|
|
||||||
// | State | What it means |
|
|
||||||
// |----------+--------------------------------------------------------------------------------|
|
|
||||||
// | Inactive | Client is created, but isn't able to communicate over Magistrala |
|
|
||||||
// | Active | Client is able to communicate using Magistrala |.
|
|
||||||
type State int
|
|
||||||
|
|
||||||
// String returns string representation of State.
|
|
||||||
func (s State) String() string {
|
|
||||||
return strconv.Itoa(int(s))
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Status represents bootstrap enrollment availability.
|
||||||
|
type Status uint8
|
||||||
|
|
||||||
|
// Possible bootstrap enrollment statuses.
|
||||||
|
const (
|
||||||
|
EnabledStatus Status = iota
|
||||||
|
DisabledStatus
|
||||||
|
// AllStatus is used for querying purposes to list configs irrespective
|
||||||
|
// of their status. It is never stored in the database.
|
||||||
|
AllStatus
|
||||||
|
)
|
||||||
|
|
||||||
|
// String representation of bootstrap status values.
|
||||||
|
const (
|
||||||
|
Disabled = "disabled"
|
||||||
|
Enabled = "enabled"
|
||||||
|
All = "all"
|
||||||
|
Unknown = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Backward-compatible aliases kept while callers move off the old names.
|
||||||
|
const (
|
||||||
|
Inactive = DisabledStatus
|
||||||
|
Active = EnabledStatus
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns string representation of Status.
|
||||||
|
func (s Status) String() string {
|
||||||
|
switch s {
|
||||||
|
case DisabledStatus:
|
||||||
|
return Disabled
|
||||||
|
case EnabledStatus:
|
||||||
|
return Enabled
|
||||||
|
case AllStatus:
|
||||||
|
return All
|
||||||
|
default:
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStatus converts a string or legacy numeric string value to Status.
|
||||||
|
func ToStatus(status string) (Status, error) {
|
||||||
|
switch strings.ToLower(status) {
|
||||||
|
case "", Enabled, "0":
|
||||||
|
return EnabledStatus, nil
|
||||||
|
case Disabled, "1":
|
||||||
|
return DisabledStatus, nil
|
||||||
|
case All:
|
||||||
|
return AllStatus, nil
|
||||||
|
}
|
||||||
|
return Status(0), svcerr.ErrInvalidStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON renders bootstrap status as a string literal.
|
||||||
|
func (s Status) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(s.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON accepts both string and legacy numeric bootstrap statuses.
|
||||||
|
func (s *Status) UnmarshalJSON(data []byte) error {
|
||||||
|
if len(data) == 0 || string(data) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[0] != '"' {
|
||||||
|
var n int
|
||||||
|
if err := json.Unmarshal(data, &n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parsed, err := ToStatus(strconv.Itoa(n))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*s = parsed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var status string
|
||||||
|
if err := json.Unmarshal(data, &status); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parsed, err := ToStatus(status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*s = parsed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -27,12 +27,12 @@ func New(svc bootstrap.Service, tracer trace.Tracer) bootstrap.Service {
|
|||||||
// Add traces the "Add" operation of the wrapped bootstrap.Service.
|
// Add traces the "Add" operation of the wrapped bootstrap.Service.
|
||||||
func (tm *tracingMiddleware) Add(ctx context.Context, session smqauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) {
|
func (tm *tracingMiddleware) Add(ctx context.Context, session smqauthn.Session, token string, cfg bootstrap.Config) (bootstrap.Config, error) {
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_register_user", trace.WithAttributes(
|
ctx, span := tm.tracer.Start(ctx, "svc_register_user", trace.WithAttributes(
|
||||||
attribute.String("client_id", cfg.ClientID),
|
attribute.String("config_id", cfg.ID),
|
||||||
attribute.String("domain_id ", cfg.DomainID),
|
attribute.String("domain_id ", cfg.DomainID),
|
||||||
attribute.String("name", cfg.Name),
|
attribute.String("name", cfg.Name),
|
||||||
attribute.String("external_id", cfg.ExternalID),
|
attribute.String("external_id", cfg.ExternalID),
|
||||||
attribute.String("content", cfg.Content),
|
attribute.String("content", cfg.Content),
|
||||||
attribute.String("state", cfg.State.String()),
|
attribute.String("status", cfg.Status.String()),
|
||||||
))
|
))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ func (tm *tracingMiddleware) Update(ctx context.Context, session smqauthn.Sessio
|
|||||||
ctx, span := tm.tracer.Start(ctx, "svc_update_user", trace.WithAttributes(
|
ctx, span := tm.tracer.Start(ctx, "svc_update_user", trace.WithAttributes(
|
||||||
attribute.String("name", cfg.Name),
|
attribute.String("name", cfg.Name),
|
||||||
attribute.String("content", cfg.Content),
|
attribute.String("content", cfg.Content),
|
||||||
attribute.String("client_id", cfg.ClientID),
|
attribute.String("config_id", cfg.ID),
|
||||||
attribute.String("domain_id ", cfg.DomainID),
|
attribute.String("domain_id ", cfg.DomainID),
|
||||||
))
|
))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
@@ -63,24 +63,13 @@ func (tm *tracingMiddleware) Update(ctx context.Context, session smqauthn.Sessio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCert traces the "UpdateCert" operation of the wrapped bootstrap.Service.
|
// UpdateCert traces the "UpdateCert" operation of the wrapped bootstrap.Service.
|
||||||
func (tm *tracingMiddleware) UpdateCert(ctx context.Context, session smqauthn.Session, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
func (tm *tracingMiddleware) UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (bootstrap.Config, error) {
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_update_cert", trace.WithAttributes(
|
ctx, span := tm.tracer.Start(ctx, "svc_update_cert", trace.WithAttributes(
|
||||||
attribute.String("client_id", clientID),
|
attribute.String("config_id", id),
|
||||||
))
|
))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
return tm.svc.UpdateCert(ctx, session, clientID, clientCert, clientKey, caCert)
|
return tm.svc.UpdateCert(ctx, session, id, clientCert, clientKey, caCert)
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateConnections traces the "UpdateConnections" operation of the wrapped bootstrap.Service.
|
|
||||||
func (tm *tracingMiddleware) UpdateConnections(ctx context.Context, session smqauthn.Session, token, id string, connections []string) error {
|
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_update_connections", trace.WithAttributes(
|
|
||||||
attribute.String("id", id),
|
|
||||||
attribute.StringSlice("connections", connections),
|
|
||||||
))
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
return tm.svc.UpdateConnections(ctx, session, token, id, connections)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List traces the "List" operation of the wrapped bootstrap.Service.
|
// List traces the "List" operation of the wrapped bootstrap.Service.
|
||||||
@@ -107,7 +96,6 @@ func (tm *tracingMiddleware) Remove(ctx context.Context, session smqauthn.Sessio
|
|||||||
// Bootstrap traces the "Bootstrap" operation of the wrapped bootstrap.Service.
|
// Bootstrap traces the "Bootstrap" operation of the wrapped bootstrap.Service.
|
||||||
func (tm *tracingMiddleware) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (bootstrap.Config, error) {
|
func (tm *tracingMiddleware) Bootstrap(ctx context.Context, externalKey, externalID string, secure bool) (bootstrap.Config, error) {
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_bootstrap_user", trace.WithAttributes(
|
ctx, span := tm.tracer.Start(ctx, "svc_bootstrap_user", trace.WithAttributes(
|
||||||
attribute.String("external_key", externalKey),
|
|
||||||
attribute.String("external_id", externalID),
|
attribute.String("external_id", externalID),
|
||||||
attribute.Bool("secure", secure),
|
attribute.Bool("secure", secure),
|
||||||
))
|
))
|
||||||
@@ -116,67 +104,95 @@ func (tm *tracingMiddleware) Bootstrap(ctx context.Context, externalKey, externa
|
|||||||
return tm.svc.Bootstrap(ctx, externalKey, externalID, secure)
|
return tm.svc.Bootstrap(ctx, externalKey, externalID, secure)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeState traces the "ChangeState" operation of the wrapped bootstrap.Service.
|
func (tm *tracingMiddleware) EnableConfig(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
func (tm *tracingMiddleware) ChangeState(ctx context.Context, session smqauthn.Session, token, id string, state bootstrap.State) error {
|
ctx, span := tm.tracer.Start(ctx, "svc_enable_config", trace.WithAttributes(
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_change_state", trace.WithAttributes(
|
|
||||||
attribute.String("id", id),
|
|
||||||
attribute.String("state", state.String()),
|
|
||||||
))
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
return tm.svc.ChangeState(ctx, session, token, id, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateChannelHandler traces the "UpdateChannelHandler" operation of the wrapped bootstrap.Service.
|
|
||||||
func (tm *tracingMiddleware) UpdateChannelHandler(ctx context.Context, channel bootstrap.Channel) error {
|
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_update_channel_handler", trace.WithAttributes(
|
|
||||||
attribute.String("id", channel.ID),
|
|
||||||
attribute.String("name", channel.Name),
|
|
||||||
attribute.String("description", channel.Description),
|
|
||||||
))
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
return tm.svc.UpdateChannelHandler(ctx, channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveConfigHandler traces the "RemoveConfigHandler" operation of the wrapped bootstrap.Service.
|
|
||||||
func (tm *tracingMiddleware) RemoveConfigHandler(ctx context.Context, id string) error {
|
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_remove_config_handler", trace.WithAttributes(
|
|
||||||
attribute.String("id", id),
|
attribute.String("id", id),
|
||||||
))
|
))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
return tm.svc.RemoveConfigHandler(ctx, id)
|
return tm.svc.EnableConfig(ctx, session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveChannelHandler traces the "RemoveChannelHandler" operation of the wrapped bootstrap.Service.
|
func (tm *tracingMiddleware) DisableConfig(ctx context.Context, session smqauthn.Session, id string) (bootstrap.Config, error) {
|
||||||
func (tm *tracingMiddleware) RemoveChannelHandler(ctx context.Context, id string) error {
|
ctx, span := tm.tracer.Start(ctx, "svc_disable_config", trace.WithAttributes(
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_remove_channel_handler", trace.WithAttributes(
|
|
||||||
attribute.String("id", id),
|
attribute.String("id", id),
|
||||||
))
|
))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
return tm.svc.RemoveChannelHandler(ctx, id)
|
return tm.svc.DisableConfig(ctx, session, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectClientHandler traces the "ConnectClientHandler" operation of the wrapped bootstrap.Service.
|
func (tm *tracingMiddleware) CreateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) (bootstrap.Profile, error) {
|
||||||
func (tm *tracingMiddleware) ConnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
ctx, span := tm.tracer.Start(ctx, "svc_create_profile", trace.WithAttributes(
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_connect_client_handler", trace.WithAttributes(
|
attribute.String("name", p.Name),
|
||||||
attribute.String("channel_id", channelID),
|
attribute.String("domain_id", p.DomainID),
|
||||||
attribute.String("client_id", clientID),
|
|
||||||
))
|
))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
return tm.svc.CreateProfile(ctx, session, p)
|
||||||
return tm.svc.ConnectClientHandler(ctx, channelID, clientID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisconnectClientHandler traces the "DisconnectClientHandler" operation of the wrapped bootstrap.Service.
|
func (tm *tracingMiddleware) ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (bootstrap.Profile, error) {
|
||||||
func (tm *tracingMiddleware) DisconnectClientHandler(ctx context.Context, channelID, clientID string) error {
|
ctx, span := tm.tracer.Start(ctx, "svc_view_profile", trace.WithAttributes(
|
||||||
ctx, span := tm.tracer.Start(ctx, "svc_disconnect_client_handler", trace.WithAttributes(
|
attribute.String("profile_id", profileID),
|
||||||
attribute.String("channel_id", channelID),
|
|
||||||
attribute.String("client_id", clientID),
|
|
||||||
))
|
))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
return tm.svc.ViewProfile(ctx, session, profileID)
|
||||||
return tm.svc.DisconnectClientHandler(ctx, channelID, clientID)
|
}
|
||||||
|
|
||||||
|
func (tm *tracingMiddleware) UpdateProfile(ctx context.Context, session smqauthn.Session, p bootstrap.Profile) error {
|
||||||
|
ctx, span := tm.tracer.Start(ctx, "svc_update_profile", trace.WithAttributes(
|
||||||
|
attribute.String("profile_id", p.ID),
|
||||||
|
))
|
||||||
|
defer span.End()
|
||||||
|
return tm.svc.UpdateProfile(ctx, session, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *tracingMiddleware) ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64) (bootstrap.ProfilesPage, error) {
|
||||||
|
ctx, span := tm.tracer.Start(ctx, "svc_list_profiles", trace.WithAttributes(
|
||||||
|
attribute.Int64("offset", int64(offset)),
|
||||||
|
attribute.Int64("limit", int64(limit)),
|
||||||
|
))
|
||||||
|
defer span.End()
|
||||||
|
return tm.svc.ListProfiles(ctx, session, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *tracingMiddleware) DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error {
|
||||||
|
ctx, span := tm.tracer.Start(ctx, "svc_delete_profile", trace.WithAttributes(
|
||||||
|
attribute.String("profile_id", profileID),
|
||||||
|
))
|
||||||
|
defer span.End()
|
||||||
|
return tm.svc.DeleteProfile(ctx, session, profileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *tracingMiddleware) AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error {
|
||||||
|
ctx, span := tm.tracer.Start(ctx, "svc_assign_profile", trace.WithAttributes(
|
||||||
|
attribute.String("config_id", configID),
|
||||||
|
attribute.String("profile_id", profileID),
|
||||||
|
))
|
||||||
|
defer span.End()
|
||||||
|
return tm.svc.AssignProfile(ctx, session, configID, profileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *tracingMiddleware) BindResources(ctx context.Context, session smqauthn.Session, token, configID string, bindings []bootstrap.BindingRequest) error {
|
||||||
|
ctx, span := tm.tracer.Start(ctx, "svc_bind_resources", trace.WithAttributes(
|
||||||
|
attribute.String("config_id", configID),
|
||||||
|
))
|
||||||
|
defer span.End()
|
||||||
|
return tm.svc.BindResources(ctx, session, token, configID, bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *tracingMiddleware) ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]bootstrap.BindingSnapshot, error) {
|
||||||
|
ctx, span := tm.tracer.Start(ctx, "svc_list_bindings", trace.WithAttributes(
|
||||||
|
attribute.String("config_id", configID),
|
||||||
|
))
|
||||||
|
defer span.End()
|
||||||
|
return tm.svc.ListBindings(ctx, session, configID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *tracingMiddleware) RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error {
|
||||||
|
ctx, span := tm.tracer.Start(ctx, "svc_refresh_bindings", trace.WithAttributes(
|
||||||
|
attribute.String("config_id", configID),
|
||||||
|
))
|
||||||
|
defer span.End()
|
||||||
|
return tm.svc.RefreshBindings(ctx, session, token, configID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.
|
|||||||
UserID: session.UserID,
|
UserID: session.UserID,
|
||||||
PatID: session.PatID,
|
PatID: session.PatID,
|
||||||
EntityID: entityID,
|
EntityID: entityID,
|
||||||
EntityType: auth.ChannelsType.String(),
|
EntityType: patEntityType(entityType),
|
||||||
Operation: opName,
|
Operation: opName,
|
||||||
Domain: session.DomainID,
|
Domain: session.DomainID,
|
||||||
}
|
}
|
||||||
@@ -357,6 +357,15 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, session authn.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func patEntityType(entityType string) string {
|
||||||
|
switch entityType {
|
||||||
|
case policies.ClientType:
|
||||||
|
return auth.ClientsType.String()
|
||||||
|
default:
|
||||||
|
return auth.ChannelsType.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
|
func (am *authorizationMiddleware) checkSuperAdmin(ctx context.Context, session authn.Session) error {
|
||||||
if session.Role != authn.SuperAdminRole {
|
if session.Role != authn.SuperAdminRole {
|
||||||
return svcerr.ErrSuperAdminAction
|
return svcerr.ErrSuperAdminAction
|
||||||
|
|||||||
+186
-14
@@ -14,7 +14,7 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
{
|
{
|
||||||
Use: "create <JSON_config> <domain_id> <user_auth_token>",
|
Use: "create <JSON_config> <domain_id> <user_auth_token>",
|
||||||
Short: "Create config",
|
Short: "Create config",
|
||||||
Long: `Create new Client Bootstrap Config to the user identified by the provided key`,
|
Long: `Create a new bootstrap enrollment in the given domain`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) != 3 {
|
if len(args) != 3 {
|
||||||
logUsageCmd(*cmd, cmd.Use)
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
@@ -37,11 +37,11 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Use: "get [all | <client_id>] <domain_id> <user_auth_token>",
|
Use: "get [all | <config_id>] <domain_id> <user_auth_token>",
|
||||||
Short: "Get config",
|
Short: "Get config",
|
||||||
Long: `Get Client Config with given ID belonging to the user identified by the given key.
|
Long: `Get bootstrap enrollment with given ID belonging to the user identified by the given key.
|
||||||
all - lists all config
|
all - lists all config
|
||||||
<client_id> - view config of <client_id>`,
|
<config_id> - view config of <config_id>`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) != 3 {
|
if len(args) != 3 {
|
||||||
logUsageCmd(*cmd, cmd.Use)
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
@@ -50,7 +50,7 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
pageMetadata := mgsdk.PageMetadata{
|
pageMetadata := mgsdk.PageMetadata{
|
||||||
Offset: Offset,
|
Offset: Offset,
|
||||||
Limit: Limit,
|
Limit: Limit,
|
||||||
State: State,
|
Status: Status,
|
||||||
Name: Name,
|
Name: Name,
|
||||||
}
|
}
|
||||||
if args[0] == all {
|
if args[0] == all {
|
||||||
@@ -77,8 +77,7 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
Short: "Update config",
|
Short: "Update config",
|
||||||
Long: `Updates editable fields of the provided Config.
|
Long: `Updates editable fields of the provided Config.
|
||||||
config <JSON_config> - Updates editable fields of the provided Config.
|
config <JSON_config> - Updates editable fields of the provided Config.
|
||||||
connection <id> <channel_ids> - Updates connections performs update of the channel list corresponding Client is connected to.
|
connection <id> <channel_ids> - Unsupported legacy operation kept for compatibility.
|
||||||
channel_ids - '["channel_id1", ...]'
|
|
||||||
certs <id> <client_cert> <client_key> <ca> - Update bootstrap config certificates.`,
|
certs <id> <client_cert> <client_key> <ca> - Update bootstrap config certificates.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) < 4 {
|
if len(args) < 4 {
|
||||||
@@ -92,7 +91,7 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sdk.UpdateBootstrap(cmd.Context(), cfg, args[1], args[2]); err != nil {
|
if err := sdk.UpdateBootstrap(cmd.Context(), cfg, args[2], args[3]); err != nil {
|
||||||
logErrorCmd(*cmd, err)
|
logErrorCmd(*cmd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -115,7 +114,7 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if args[0] == "certs" {
|
if args[0] == "certs" {
|
||||||
cfg, err := sdk.UpdateBootstrapCerts(cmd.Context(), args[0], args[1], args[2], args[3], args[4], args[5])
|
cfg, err := sdk.UpdateBootstrapCerts(cmd.Context(), args[1], args[2], args[3], args[4], args[5], args[6])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logErrorCmd(*cmd, err)
|
logErrorCmd(*cmd, err)
|
||||||
return
|
return
|
||||||
@@ -128,7 +127,7 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Use: "remove <client_id> <domain_id> <user_auth_token>",
|
Use: "remove <config_id> <domain_id> <user_auth_token>",
|
||||||
Short: "Remove config",
|
Short: "Remove config",
|
||||||
Long: `Removes Config with specified key that belongs to the user identified by the given key`,
|
Long: `Removes Config with specified key that belongs to the user identified by the given key`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
@@ -177,7 +176,7 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
{
|
{
|
||||||
Use: "whitelist <JSON_config> <domain_id> <user_auth_token>",
|
Use: "whitelist <JSON_config> <domain_id> <user_auth_token>",
|
||||||
Short: "Whitelist config",
|
Short: "Whitelist config",
|
||||||
Long: `Whitelist updates client state config with given id from the authenticated user`,
|
Long: `Whitelist updates bootstrap status for the given enrollment`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) != 3 {
|
if len(args) != 3 {
|
||||||
logUsageCmd(*cmd, cmd.Use)
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
@@ -190,7 +189,7 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sdk.Whitelist(cmd.Context(), cfg.ClientID, cfg.State, args[1], args[2]); err != nil {
|
if err := sdk.Whitelist(cmd.Context(), cfg.ID, cfg.Status, args[1], args[2]); err != nil {
|
||||||
logErrorCmd(*cmd, err)
|
logErrorCmd(*cmd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -198,14 +197,187 @@ var cmdBootstrap = []cobra.Command{
|
|||||||
logOKCmd(*cmd)
|
logOKCmd(*cmd)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Use: "profiles [create <JSON_profile> <domain_id> <user_auth_token> | get [all | <profile_id>] <domain_id> <user_auth_token> | update <JSON_profile> <domain_id> <user_auth_token> | remove <profile_id> <domain_id> <user_auth_token>]",
|
||||||
|
Short: "Manage bootstrap profiles",
|
||||||
|
Long: `Manage bootstrap profiles.
|
||||||
|
create <JSON_profile> - Create a bootstrap profile.
|
||||||
|
get all - List bootstrap profiles.
|
||||||
|
get <profile_id> - View bootstrap profile.
|
||||||
|
update <JSON_profile> - Update bootstrap profile.
|
||||||
|
remove <profile_id> - Remove bootstrap profile.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case create:
|
||||||
|
if len(args) != 4 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile mgsdk.BootstrapProfile
|
||||||
|
if err := json.Unmarshal([]byte(args[1]), &profile); err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profile, err := sdk.CreateBootstrapProfile(cmd.Context(), profile, args[2], args[3])
|
||||||
|
if err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logJSONCmd(*cmd, profile)
|
||||||
|
case get:
|
||||||
|
if len(args) != 4 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if args[1] == all {
|
||||||
|
pageMetadata := mgsdk.PageMetadata{
|
||||||
|
Offset: Offset,
|
||||||
|
Limit: Limit,
|
||||||
|
}
|
||||||
|
profiles, err := sdk.BootstrapProfiles(cmd.Context(), pageMetadata, args[2], args[3])
|
||||||
|
if err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logJSONCmd(*cmd, profiles)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profile, err := sdk.ViewBootstrapProfile(cmd.Context(), args[1], args[2], args[3])
|
||||||
|
if err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logJSONCmd(*cmd, profile)
|
||||||
|
case update:
|
||||||
|
if len(args) != 4 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile mgsdk.BootstrapProfile
|
||||||
|
if err := json.Unmarshal([]byte(args[1]), &profile); err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sdk.UpdateBootstrapProfile(cmd.Context(), profile, args[2], args[3]); err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logOKCmd(*cmd)
|
||||||
|
case "remove":
|
||||||
|
if len(args) != 4 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sdk.RemoveBootstrapProfile(cmd.Context(), args[1], args[2], args[3]); err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logOKCmd(*cmd)
|
||||||
|
default:
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Use: "enrollments [assign-profile <config_id> <profile_id> <domain_id> <user_auth_token> | bind <config_id> <JSON_bindings> <domain_id> <user_auth_token> | get-bindings <config_id> <domain_id> <user_auth_token> | refresh-bindings <config_id> <domain_id> <user_auth_token>]",
|
||||||
|
Short: "Manage bootstrap enrollment bindings",
|
||||||
|
Long: `Manage bootstrap enrollment profile assignments and bindings.
|
||||||
|
assign-profile <config_id> <profile_id> - Assign a profile to an enrollment.
|
||||||
|
bind <config_id> <JSON_bindings> - Bind concrete resources to an enrollment.
|
||||||
|
get-bindings <config_id> - List stored binding snapshots for an enrollment.
|
||||||
|
refresh-bindings <config_id> - Refresh stored binding snapshots for an enrollment.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case "assign-profile":
|
||||||
|
if len(args) != 5 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sdk.AssignBootstrapProfile(cmd.Context(), args[1], args[2], args[3], args[4]); err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logOKCmd(*cmd)
|
||||||
|
case "bind":
|
||||||
|
if len(args) != 5 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindings []mgsdk.BootstrapBindingRequest
|
||||||
|
if err := json.Unmarshal([]byte(args[2]), &bindings); err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sdk.BindBootstrapResources(cmd.Context(), args[1], bindings, args[3], args[4]); err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logOKCmd(*cmd)
|
||||||
|
case "get-bindings":
|
||||||
|
if len(args) != 4 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bindings, err := sdk.BootstrapBindings(cmd.Context(), args[1], args[2], args[3])
|
||||||
|
if err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logJSONCmd(*cmd, bindings)
|
||||||
|
case "refresh-bindings":
|
||||||
|
if len(args) != 4 {
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sdk.RefreshBootstrapBindings(cmd.Context(), args[1], args[2], args[3]); err != nil {
|
||||||
|
logErrorCmd(*cmd, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logOKCmd(*cmd)
|
||||||
|
default:
|
||||||
|
logUsageCmd(*cmd, cmd.Use)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBootstrapCmd returns bootstrap command.
|
// NewBootstrapCmd returns bootstrap command.
|
||||||
func NewBootstrapCmd() *cobra.Command {
|
func NewBootstrapCmd() *cobra.Command {
|
||||||
cmd := cobra.Command{
|
cmd := cobra.Command{
|
||||||
Use: "bootstrap [create | get | update | remove | bootstrap | whitelist]",
|
Use: "bootstrap [create | get | update | remove | bootstrap | whitelist | profiles | enrollments]",
|
||||||
Short: "Bootstrap management",
|
Short: "Bootstrap management",
|
||||||
Long: `Bootstrap management: create, get, update, delete or whitelist Bootstrap config`,
|
Long: `Bootstrap management: create, get, update, delete, whitelist, profiles, and enrollment bindings`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range cmdBootstrap {
|
for i := range cmdBootstrap {
|
||||||
|
|||||||
+244
-6
@@ -24,13 +24,21 @@ var (
|
|||||||
clientID = testsutil.GenerateUUID(&testing.T{})
|
clientID = testsutil.GenerateUUID(&testing.T{})
|
||||||
channelID = testsutil.GenerateUUID(&testing.T{})
|
channelID = testsutil.GenerateUUID(&testing.T{})
|
||||||
domainID = testsutil.GenerateUUID(&testing.T{})
|
domainID = testsutil.GenerateUUID(&testing.T{})
|
||||||
|
profileID = testsutil.GenerateUUID(&testing.T{})
|
||||||
bootConfig = mgsdk.BootstrapConfig{
|
bootConfig = mgsdk.BootstrapConfig{
|
||||||
ClientID: clientID,
|
ID: clientID,
|
||||||
Channels: []string{channelID},
|
|
||||||
Name: "Test Bootstrap",
|
Name: "Test Bootstrap",
|
||||||
ExternalID: "09:6:0:sb:sa",
|
ExternalID: "09:6:0:sb:sa",
|
||||||
ExternalKey: "key",
|
ExternalKey: "key",
|
||||||
}
|
}
|
||||||
|
bootProfile = mgsdk.BootstrapProfile{
|
||||||
|
ID: profileID,
|
||||||
|
Name: "Test Profile",
|
||||||
|
Description: "Test profile",
|
||||||
|
TemplateFormat: "go-template",
|
||||||
|
ContentTemplate: "{\"device_id\":\"{{ .Device.ID }}\"}",
|
||||||
|
Version: 1,
|
||||||
|
}
|
||||||
validToken = "validToken"
|
validToken = "validToken"
|
||||||
invalidToken = "invalidToken"
|
invalidToken = "invalidToken"
|
||||||
extraArg = "extra-arg"
|
extraArg = "extra-arg"
|
||||||
@@ -44,8 +52,8 @@ func TestCreateBootstrapConfigCmd(t *testing.T) {
|
|||||||
bootCmd := cli.NewBootstrapCmd()
|
bootCmd := cli.NewBootstrapCmd()
|
||||||
rootCmd := setFlags(bootCmd)
|
rootCmd := setFlags(bootCmd)
|
||||||
|
|
||||||
jsonConfig := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"client_id\": \"%s\", \"external_key\":\"key\", \"name\": \"%s\", \"channels\":[\"%s\"]}", clientID, "Test Bootstrap", channelID)
|
jsonConfig := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"external_key\":\"key\", \"name\": \"%s\"}", "Test Bootstrap")
|
||||||
invalidJson := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"client_id\": \"%s\", \"external_key\":\"key\", \"name\": \"%s\", \"channels\":[\"%s\"]", clientID, "Test Bootstrap", channelID)
|
invalidJson := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"external_key\":\"key\", \"name\": \"%s\"", "Test Bootstrap")
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
args []string
|
args []string
|
||||||
@@ -473,7 +481,7 @@ func TestWhitelistConfigCmd(t *testing.T) {
|
|||||||
bootCmd := cli.NewBootstrapCmd()
|
bootCmd := cli.NewBootstrapCmd()
|
||||||
rootCmd := setFlags(bootCmd)
|
rootCmd := setFlags(bootCmd)
|
||||||
|
|
||||||
jsonConfig := fmt.Sprintf("{\"client_id\": \"%s\", \"state\":%d}", clientID, 1)
|
jsonConfig := fmt.Sprintf("{\"client_id\": \"%s\", \"status\":%d}", clientID, 1)
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
desc string
|
desc string
|
||||||
@@ -504,7 +512,7 @@ func TestWhitelistConfigCmd(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "whitelist config with invalid json",
|
desc: "whitelist config with invalid json",
|
||||||
args: []string{
|
args: []string{
|
||||||
fmt.Sprintf("{\"client_id\": \"%s\", \"state\":%d", clientID, 1),
|
fmt.Sprintf("{\"client_id\": \"%s\", \"status\":%d", clientID, 1),
|
||||||
domainID,
|
domainID,
|
||||||
validToken,
|
validToken,
|
||||||
},
|
},
|
||||||
@@ -631,3 +639,233 @@ func TestBootstrapConfigCmd(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBootstrapProfilesCmd(t *testing.T) {
|
||||||
|
sdkMock := new(sdkmocks.SDK)
|
||||||
|
cli.SetSDK(sdkMock)
|
||||||
|
bootCmd := cli.NewBootstrapCmd()
|
||||||
|
rootCmd := setFlags(bootCmd)
|
||||||
|
|
||||||
|
profilePayload, err := json.Marshal(bootProfile)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
jsonProfile := string(profilePayload)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
args []string
|
||||||
|
profile mgsdk.BootstrapProfile
|
||||||
|
page mgsdk.BootstrapProfilesPage
|
||||||
|
sdkErr errors.SDKError
|
||||||
|
logType outputLog
|
||||||
|
errLogMessage string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "create bootstrap profile successfully",
|
||||||
|
args: []string{
|
||||||
|
"create",
|
||||||
|
jsonProfile,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
profile: bootProfile,
|
||||||
|
logType: entityLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "get all bootstrap profiles successfully",
|
||||||
|
args: []string{
|
||||||
|
"get",
|
||||||
|
all,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
page: mgsdk.BootstrapProfilesPage{
|
||||||
|
PageRes: mgsdk.PageRes{
|
||||||
|
Total: 1,
|
||||||
|
Offset: 0,
|
||||||
|
Limit: 10,
|
||||||
|
},
|
||||||
|
Profiles: []mgsdk.BootstrapProfile{bootProfile},
|
||||||
|
},
|
||||||
|
logType: entityLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "view bootstrap profile successfully",
|
||||||
|
args: []string{
|
||||||
|
"get",
|
||||||
|
profileID,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
profile: bootProfile,
|
||||||
|
logType: entityLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "update bootstrap profile successfully",
|
||||||
|
args: []string{
|
||||||
|
"update",
|
||||||
|
jsonProfile,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
logType: okLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "remove bootstrap profile successfully",
|
||||||
|
args: []string{
|
||||||
|
"remove",
|
||||||
|
profileID,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
logType: okLog,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
var gotProfile mgsdk.BootstrapProfile
|
||||||
|
var gotPage mgsdk.BootstrapProfilesPage
|
||||||
|
|
||||||
|
createCall := sdkMock.On("CreateBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.profile, tc.sdkErr)
|
||||||
|
listCall := sdkMock.On("BootstrapProfiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr)
|
||||||
|
viewCall := sdkMock.On("ViewBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.profile, tc.sdkErr)
|
||||||
|
updateCall := sdkMock.On("UpdateBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||||
|
removeCall := sdkMock.On("RemoveBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||||
|
|
||||||
|
out := executeCommand(t, rootCmd, append([]string{"profiles"}, tc.args...)...)
|
||||||
|
|
||||||
|
switch tc.logType {
|
||||||
|
case entityLog:
|
||||||
|
if tc.args[0] == "get" && tc.args[1] == all {
|
||||||
|
err := json.Unmarshal([]byte(out), &gotPage)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, tc.page, gotPage, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.page, gotPage))
|
||||||
|
} else {
|
||||||
|
err := json.Unmarshal([]byte(out), &gotProfile)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, tc.profile, gotProfile, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.profile, gotProfile))
|
||||||
|
}
|
||||||
|
case okLog:
|
||||||
|
assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
|
||||||
|
case errLog:
|
||||||
|
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||||
|
case usageLog:
|
||||||
|
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||||
|
}
|
||||||
|
|
||||||
|
createCall.Unset()
|
||||||
|
listCall.Unset()
|
||||||
|
viewCall.Unset()
|
||||||
|
updateCall.Unset()
|
||||||
|
removeCall.Unset()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBootstrapEnrollmentsCmd(t *testing.T) {
|
||||||
|
sdkMock := new(sdkmocks.SDK)
|
||||||
|
cli.SetSDK(sdkMock)
|
||||||
|
bootCmd := cli.NewBootstrapCmd()
|
||||||
|
rootCmd := setFlags(bootCmd)
|
||||||
|
|
||||||
|
bindings := []mgsdk.BootstrapBindingRequest{
|
||||||
|
{
|
||||||
|
Slot: "mqtt_client",
|
||||||
|
Type: "client",
|
||||||
|
ResourceID: clientID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
snapshots := []mgsdk.BootstrapBindingSnapshot{
|
||||||
|
{
|
||||||
|
ConfigID: clientID,
|
||||||
|
Slot: "mqtt_client",
|
||||||
|
Type: "client",
|
||||||
|
ResourceID: clientID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
jsonBindings := fmt.Sprintf("[{\"slot\":\"%s\",\"type\":\"%s\",\"resource_id\":\"%s\"}]", bindings[0].Slot, bindings[0].Type, bindings[0].ResourceID)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
args []string
|
||||||
|
snapshots []mgsdk.BootstrapBindingSnapshot
|
||||||
|
sdkErr errors.SDKError
|
||||||
|
logType outputLog
|
||||||
|
errLogMessage string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "assign bootstrap profile successfully",
|
||||||
|
args: []string{
|
||||||
|
"assign-profile",
|
||||||
|
clientID,
|
||||||
|
profileID,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
logType: okLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "bind bootstrap resources successfully",
|
||||||
|
args: []string{
|
||||||
|
"bind",
|
||||||
|
clientID,
|
||||||
|
jsonBindings,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
logType: okLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "get bootstrap bindings successfully",
|
||||||
|
args: []string{
|
||||||
|
"get-bindings",
|
||||||
|
clientID,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
snapshots: snapshots,
|
||||||
|
logType: entityLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "refresh bootstrap bindings successfully",
|
||||||
|
args: []string{
|
||||||
|
"refresh-bindings",
|
||||||
|
clientID,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
},
|
||||||
|
logType: okLog,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
var gotSnapshots []mgsdk.BootstrapBindingSnapshot
|
||||||
|
|
||||||
|
assignCall := sdkMock.On("AssignBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||||
|
bindCall := sdkMock.On("BindBootstrapResources", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||||
|
listCall := sdkMock.On("BootstrapBindings", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.snapshots, tc.sdkErr)
|
||||||
|
refreshCall := sdkMock.On("RefreshBootstrapBindings", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||||
|
|
||||||
|
out := executeCommand(t, rootCmd, append([]string{"enrollments"}, tc.args...)...)
|
||||||
|
|
||||||
|
switch tc.logType {
|
||||||
|
case entityLog:
|
||||||
|
err := json.Unmarshal([]byte(out), &gotSnapshots)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, tc.snapshots, gotSnapshots, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.snapshots, gotSnapshots))
|
||||||
|
case okLog:
|
||||||
|
assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
|
||||||
|
case errLog:
|
||||||
|
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||||
|
case usageLog:
|
||||||
|
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||||
|
}
|
||||||
|
|
||||||
|
assignCall.Unset()
|
||||||
|
bindCall.Unset()
|
||||||
|
listCall.Unset()
|
||||||
|
refreshCall.Unset()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ var (
|
|||||||
Status string = ""
|
Status string = ""
|
||||||
// ConfigPath config path parameter.
|
// ConfigPath config path parameter.
|
||||||
ConfigPath string = ""
|
ConfigPath string = ""
|
||||||
// State query parameter.
|
|
||||||
State string = ""
|
|
||||||
// Topic query parameter.
|
// Topic query parameter.
|
||||||
Topic string = ""
|
Topic string = ""
|
||||||
// Contact query parameter.
|
// Contact query parameter.
|
||||||
|
|||||||
+23
-62
@@ -16,8 +16,8 @@ import (
|
|||||||
"github.com/absmach/magistrala"
|
"github.com/absmach/magistrala"
|
||||||
"github.com/absmach/magistrala/bootstrap"
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
httpapi "github.com/absmach/magistrala/bootstrap/api"
|
httpapi "github.com/absmach/magistrala/bootstrap/api"
|
||||||
"github.com/absmach/magistrala/bootstrap/events/consumer"
|
|
||||||
"github.com/absmach/magistrala/bootstrap/events/producer"
|
"github.com/absmach/magistrala/bootstrap/events/producer"
|
||||||
|
bootstraphasher "github.com/absmach/magistrala/bootstrap/hasher"
|
||||||
"github.com/absmach/magistrala/bootstrap/middleware"
|
"github.com/absmach/magistrala/bootstrap/middleware"
|
||||||
bootstrappg "github.com/absmach/magistrala/bootstrap/postgres"
|
bootstrappg "github.com/absmach/magistrala/bootstrap/postgres"
|
||||||
"github.com/absmach/magistrala/bootstrap/tracing"
|
"github.com/absmach/magistrala/bootstrap/tracing"
|
||||||
@@ -27,26 +27,18 @@ import (
|
|||||||
smqauthz "github.com/absmach/magistrala/pkg/authz"
|
smqauthz "github.com/absmach/magistrala/pkg/authz"
|
||||||
authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc"
|
authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc"
|
||||||
domainsAuthz "github.com/absmach/magistrala/pkg/domains/grpcclient"
|
domainsAuthz "github.com/absmach/magistrala/pkg/domains/grpcclient"
|
||||||
"github.com/absmach/magistrala/pkg/events"
|
|
||||||
"github.com/absmach/magistrala/pkg/events/store"
|
"github.com/absmach/magistrala/pkg/events/store"
|
||||||
"github.com/absmach/magistrala/pkg/grpcclient"
|
"github.com/absmach/magistrala/pkg/grpcclient"
|
||||||
"github.com/absmach/magistrala/pkg/jaeger"
|
"github.com/absmach/magistrala/pkg/jaeger"
|
||||||
"github.com/absmach/magistrala/pkg/policies"
|
|
||||||
"github.com/absmach/magistrala/pkg/policies/spicedb"
|
|
||||||
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
pgclient "github.com/absmach/magistrala/pkg/postgres"
|
||||||
"github.com/absmach/magistrala/pkg/prometheus"
|
"github.com/absmach/magistrala/pkg/prometheus"
|
||||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||||
"github.com/absmach/magistrala/pkg/server"
|
"github.com/absmach/magistrala/pkg/server"
|
||||||
httpserver "github.com/absmach/magistrala/pkg/server/http"
|
httpserver "github.com/absmach/magistrala/pkg/server/http"
|
||||||
"github.com/absmach/magistrala/pkg/uuid"
|
"github.com/absmach/magistrala/pkg/uuid"
|
||||||
"github.com/authzed/authzed-go/v1"
|
|
||||||
"github.com/authzed/grpcutil"
|
|
||||||
"github.com/caarlos0/env/v11"
|
"github.com/caarlos0/env/v11"
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -57,9 +49,6 @@ const (
|
|||||||
envPrefixDomains = "MG_DOMAINS_GRPC_"
|
envPrefixDomains = "MG_DOMAINS_GRPC_"
|
||||||
defDB = "bootstrap"
|
defDB = "bootstrap"
|
||||||
defSvcHTTPPort = "9013"
|
defSvcHTTPPort = "9013"
|
||||||
|
|
||||||
stream = "events.magistrala.clients"
|
|
||||||
streamID = "magistrala.bootstrap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
@@ -108,7 +97,9 @@ func main() {
|
|||||||
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
|
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
|
||||||
logger.Error(err.Error())
|
logger.Error(err.Error())
|
||||||
}
|
}
|
||||||
db, err := pgclient.Setup(dbConfig, *bootstrappg.Migration())
|
migration := bootstrappg.Migration()
|
||||||
|
|
||||||
|
db, err := pgclient.Setup(dbConfig, *migration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err.Error())
|
logger.Error(err.Error())
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
@@ -116,14 +107,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
policySvc, err := newPolicyService(cfg, logger)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err.Error())
|
|
||||||
exitCode = 1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Info("Policy client successfully connected to spicedb gRPC server")
|
|
||||||
|
|
||||||
tp, err := jaeger.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
|
tp, err := jaeger.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err))
|
logger.Error(fmt.Sprintf("failed to init Jaeger: %s", err))
|
||||||
@@ -176,22 +159,16 @@ func main() {
|
|||||||
defer authzClient.Close()
|
defer authzClient.Close()
|
||||||
logger.Info("AuthZ successfully connected to auth gRPC server " + authzClient.Secure())
|
logger.Info("AuthZ successfully connected to auth gRPC server " + authzClient.Secure())
|
||||||
|
|
||||||
|
database := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||||
|
|
||||||
// Create new service
|
// Create new service
|
||||||
svc, err := newService(ctx, authz, policySvc, db, tracer, logger, cfg, dbConfig)
|
svc, err := newService(ctx, authz, database, tracer, logger, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("failed to create %s service: %s", svcName, err))
|
logger.Error(fmt.Sprintf("failed to create %s service: %s", svcName, err))
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = subscribeToClientsES(ctx, svc, cfg, logger); err != nil {
|
|
||||||
logger.Error(fmt.Sprintf("failed to subscribe to clients event store: %s", err))
|
|
||||||
exitCode = 1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Subscribed to Event Store")
|
|
||||||
|
|
||||||
httpServerConfig := server.Config{Port: defSvcHTTPPort}
|
httpServerConfig := server.Config{Port: defSvcHTTPPort}
|
||||||
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
|
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
|
||||||
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
|
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
|
||||||
@@ -218,10 +195,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newService(ctx context.Context, authz smqauthz.Authorization, policySvc policies.Service, db *sqlx.DB, tracer trace.Tracer, logger *slog.Logger, cfg config, dbConfig pgclient.Config) (bootstrap.Service, error) {
|
func newService(ctx context.Context, authz smqauthz.Authorization, database pgclient.Database, tracer trace.Tracer, logger *slog.Logger, cfg config) (bootstrap.Service, error) {
|
||||||
database := pgclient.NewDatabase(db, dbConfig, tracer)
|
|
||||||
|
|
||||||
repoConfig := bootstrappg.NewConfigRepository(database, logger)
|
repoConfig := bootstrappg.NewConfigRepository(database, logger)
|
||||||
|
repoProfile := bootstrappg.NewProfileRepository(database, logger)
|
||||||
|
repoBindings := bootstrappg.NewBindingRepository(database, logger)
|
||||||
|
|
||||||
config := mgsdk.Config{
|
config := mgsdk.Config{
|
||||||
ClientsURL: cfg.ClientsURL,
|
ClientsURL: cfg.ClientsURL,
|
||||||
@@ -230,8 +207,20 @@ func newService(ctx context.Context, authz smqauthz.Authorization, policySvc pol
|
|||||||
|
|
||||||
sdk := mgsdk.NewSDK(config)
|
sdk := mgsdk.NewSDK(config)
|
||||||
idp := uuid.New()
|
idp := uuid.New()
|
||||||
|
resolver := bootstrap.NewSDKResolver(sdk)
|
||||||
|
renderer := bootstrap.NewRenderer()
|
||||||
|
|
||||||
svc := bootstrap.New(policySvc, repoConfig, sdk, []byte(cfg.EncKey), idp)
|
svc := bootstrap.New(
|
||||||
|
repoConfig,
|
||||||
|
repoProfile,
|
||||||
|
repoBindings,
|
||||||
|
resolver,
|
||||||
|
renderer,
|
||||||
|
sdk,
|
||||||
|
bootstraphasher.New(),
|
||||||
|
[]byte(cfg.EncKey),
|
||||||
|
idp,
|
||||||
|
)
|
||||||
|
|
||||||
publisher, err := store.NewPublisher(ctx, cfg.ESURL, "bootstrap-es-pub")
|
publisher, err := store.NewPublisher(ctx, cfg.ESURL, "bootstrap-es-pub")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -247,31 +236,3 @@ func newService(ctx context.Context, authz smqauthz.Authorization, policySvc pol
|
|||||||
|
|
||||||
return svc, nil
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribeToClientsES(ctx context.Context, svc bootstrap.Service, cfg config, logger *slog.Logger) error {
|
|
||||||
subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, "bootstrap-es-sub", logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
subConfig := events.SubscriberConfig{
|
|
||||||
Stream: stream,
|
|
||||||
Consumer: cfg.ESConsumerName,
|
|
||||||
Handler: consumer.NewEventHandler(svc),
|
|
||||||
}
|
|
||||||
return subscriber.Subscribe(ctx, subConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPolicyService(cfg config, logger *slog.Logger) (policies.Service, error) {
|
|
||||||
client, err := authzed.NewClientWithExperimentalAPIs(
|
|
||||||
fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort),
|
|
||||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
||||||
grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
policySvc := spicedb.NewPolicyService(client, logger)
|
|
||||||
|
|
||||||
return policySvc, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
[bootstrap]
|
[bootstrap]
|
||||||
[bootstrap.content]
|
[bootstrap.content]
|
||||||
[bootstrap.content.agent.edgex]
|
|
||||||
url = "http://localhost:48090/api/v1/"
|
|
||||||
|
|
||||||
[bootstrap.content.agent.log]
|
[bootstrap.content.agent.log]
|
||||||
level = "info"
|
level = "info"
|
||||||
|
|
||||||
@@ -17,7 +14,7 @@
|
|||||||
url = "localhost:1883"
|
url = "localhost:1883"
|
||||||
|
|
||||||
[bootstrap.content.agent.server]
|
[bootstrap.content.agent.server]
|
||||||
nats_url = "localhost:4222"
|
fluxmq_url = "amqp://guest:guest@localhost:5682/"
|
||||||
port = "9000"
|
port = "9000"
|
||||||
|
|
||||||
[bootstrap.content.agent.heartbeat]
|
[bootstrap.content.agent.heartbeat]
|
||||||
@@ -29,7 +26,7 @@
|
|||||||
|
|
||||||
[bootstrap.content.export.exp]
|
[bootstrap.content.export.exp]
|
||||||
log_level = "debug"
|
log_level = "debug"
|
||||||
nats = "nats://localhost:4222"
|
fluxmq = "amqp://guest:guest@localhost:5682/"
|
||||||
port = "8172"
|
port = "8172"
|
||||||
cache_url = "localhost:6379"
|
cache_url = "localhost:6379"
|
||||||
cache_pass = ""
|
cache_pass = ""
|
||||||
@@ -37,12 +34,12 @@
|
|||||||
|
|
||||||
[bootstrap.content.export.mqtt]
|
[bootstrap.content.export.mqtt]
|
||||||
ca_path = "ca.crt"
|
ca_path = "ca.crt"
|
||||||
cert_path = "thing.crt"
|
cert_path = "client.crt"
|
||||||
channel = ""
|
channel = ""
|
||||||
host = "tcp://localhost:1883"
|
host = "tcp://localhost:1883"
|
||||||
mtls = false
|
mtls = false
|
||||||
password = ""
|
password = ""
|
||||||
priv_key_path = "thing.key"
|
priv_key_path = "client.key"
|
||||||
qos = 0
|
qos = 0
|
||||||
retain = false
|
retain = false
|
||||||
skip_tls_ver = false
|
skip_tls_ver = false
|
||||||
@@ -50,7 +47,7 @@
|
|||||||
|
|
||||||
[[bootstrap.content.export.routes]]
|
[[bootstrap.content.export.routes]]
|
||||||
mqtt_topic = ""
|
mqtt_topic = ""
|
||||||
nats_topic = ">"
|
fluxmq_topic = "#"
|
||||||
subtopic = ""
|
subtopic = ""
|
||||||
type = "plain"
|
type = "plain"
|
||||||
workers = 10
|
workers = 10
|
||||||
|
|||||||
@@ -27,6 +27,33 @@ EOF
|
|||||||
|
|
||||||
export BAO_ADDR=http://127.0.0.1:8200
|
export BAO_ADDR=http://127.0.0.1:8200
|
||||||
|
|
||||||
|
wait_for_bao() {
|
||||||
|
local retries=30
|
||||||
|
while [ "$retries" -gt 0 ]; do
|
||||||
|
local code=0
|
||||||
|
bao status -format=json >/dev/null 2>&1 || code=$?
|
||||||
|
# exit 0 = unsealed, exit 2 = sealed — both mean the server is up and responding
|
||||||
|
if [ "$code" -eq 0 ] || [ "$code" -eq 2 ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
retries=$((retries - 1))
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "ERROR: OpenBao server did not become ready in time" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
unseal_bao() {
|
||||||
|
local key1="$1" key2="$2" key3="$3"
|
||||||
|
if [ -z "$key1" ] || [ -z "$key2" ] || [ -z "$key3" ]; then
|
||||||
|
echo "ERROR: One or more unseal keys are empty — cannot unseal" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
bao operator unseal "$key1"
|
||||||
|
bao operator unseal "$key2"
|
||||||
|
bao operator unseal "$key3"
|
||||||
|
}
|
||||||
|
|
||||||
create_pki_policy() {
|
create_pki_policy() {
|
||||||
cat > /opt/openbao/config/pki-policy.hcl << EOF
|
cat > /opt/openbao/config/pki-policy.hcl << EOF
|
||||||
path "pki_int/issue/${MG_CERTS_OPENBAO_PKI_ROLE}" {
|
path "pki_int/issue/${MG_CERTS_OPENBAO_PKI_ROLE}" {
|
||||||
@@ -92,12 +119,10 @@ if [ -n "$MG_CERTS_OPENBAO_UNSEAL_KEY_1" ] && [ -n "$MG_CERTS_OPENBAO_UNSEAL_KEY
|
|||||||
echo "Using pre-configured unseal keys and root token..."
|
echo "Using pre-configured unseal keys and root token..."
|
||||||
bao server -config=/opt/openbao/config/config.hcl > /opt/openbao/logs/server.log 2>&1 &
|
bao server -config=/opt/openbao/config/config.hcl > /opt/openbao/logs/server.log 2>&1 &
|
||||||
BAO_PID=$!
|
BAO_PID=$!
|
||||||
sleep 5
|
wait_for_bao
|
||||||
|
|
||||||
bao operator unseal "$MG_CERTS_OPENBAO_UNSEAL_KEY_1"
|
unseal_bao "$MG_CERTS_OPENBAO_UNSEAL_KEY_1" "$MG_CERTS_OPENBAO_UNSEAL_KEY_2" "$MG_CERTS_OPENBAO_UNSEAL_KEY_3"
|
||||||
bao operator unseal "$MG_CERTS_OPENBAO_UNSEAL_KEY_2"
|
|
||||||
bao operator unseal "$MG_CERTS_OPENBAO_UNSEAL_KEY_3"
|
|
||||||
|
|
||||||
export BAO_TOKEN=$MG_CERTS_OPENBAO_ROOT_TOKEN
|
export BAO_TOKEN=$MG_CERTS_OPENBAO_ROOT_TOKEN
|
||||||
else
|
else
|
||||||
# Initialize OpenBao if not already done
|
# Initialize OpenBao if not already done
|
||||||
@@ -105,21 +130,18 @@ else
|
|||||||
echo "Initializing OpenBao for the first time..."
|
echo "Initializing OpenBao for the first time..."
|
||||||
bao server -config=/opt/openbao/config/config.hcl > /opt/openbao/logs/server.log 2>&1 &
|
bao server -config=/opt/openbao/config/config.hcl > /opt/openbao/logs/server.log 2>&1 &
|
||||||
BAO_PID=$!
|
BAO_PID=$!
|
||||||
sleep 5
|
wait_for_bao
|
||||||
|
|
||||||
# Initialize with 5 key shares and threshold of 3
|
# Initialize with 5 key shares and threshold of 3
|
||||||
bao operator init -key-shares=5 -key-threshold=3 -format=json > /opt/openbao/data/init.json
|
bao operator init -key-shares=5 -key-threshold=3 -format=json > /opt/openbao/data/init.json
|
||||||
|
|
||||||
# Extract unseal keys and root token
|
# Extract unseal keys and root token
|
||||||
UNSEAL_KEY_1=$(cat /opt/openbao/data/init.json | jq -r '.unseal_keys_b64[0]')
|
UNSEAL_KEY_1=$(jq -r '.unseal_keys_b64[0]' /opt/openbao/data/init.json)
|
||||||
UNSEAL_KEY_2=$(cat /opt/openbao/data/init.json | jq -r '.unseal_keys_b64[1]')
|
UNSEAL_KEY_2=$(jq -r '.unseal_keys_b64[1]' /opt/openbao/data/init.json)
|
||||||
UNSEAL_KEY_3=$(cat /opt/openbao/data/init.json | jq -r '.unseal_keys_b64[2]')
|
UNSEAL_KEY_3=$(jq -r '.unseal_keys_b64[2]' /opt/openbao/data/init.json)
|
||||||
ROOT_TOKEN=$(cat /opt/openbao/data/init.json | jq -r '.root_token')
|
ROOT_TOKEN=$(jq -r '.root_token' /opt/openbao/data/init.json)
|
||||||
|
|
||||||
# Unseal OpenBao
|
unseal_bao "$UNSEAL_KEY_1" "$UNSEAL_KEY_2" "$UNSEAL_KEY_3"
|
||||||
bao operator unseal "$UNSEAL_KEY_1"
|
|
||||||
bao operator unseal "$UNSEAL_KEY_2"
|
|
||||||
bao operator unseal "$UNSEAL_KEY_3"
|
|
||||||
|
|
||||||
export BAO_TOKEN=$ROOT_TOKEN
|
export BAO_TOKEN=$ROOT_TOKEN
|
||||||
echo "OpenBao initialized successfully!"
|
echo "OpenBao initialized successfully!"
|
||||||
@@ -127,24 +149,22 @@ else
|
|||||||
echo "OpenBao already initialized, starting server..."
|
echo "OpenBao already initialized, starting server..."
|
||||||
bao server -config=/opt/openbao/config/config.hcl > /opt/openbao/logs/server.log 2>&1 &
|
bao server -config=/opt/openbao/config/config.hcl > /opt/openbao/logs/server.log 2>&1 &
|
||||||
BAO_PID=$!
|
BAO_PID=$!
|
||||||
sleep 5
|
wait_for_bao
|
||||||
|
|
||||||
# Check if OpenBao is sealed and unseal if necessary
|
# Check if OpenBao is sealed and unseal if necessary
|
||||||
if bao status -format=json | jq -e '.sealed == true' >/dev/null; then
|
if bao status -format=json | jq -e '.sealed == true' >/dev/null 2>&1; then
|
||||||
echo "OpenBao is sealed, unsealing..."
|
echo "OpenBao is sealed, unsealing..."
|
||||||
UNSEAL_KEY_1=$(cat /opt/openbao/data/init.json | jq -r '.unseal_keys_b64[0]')
|
UNSEAL_KEY_1=$(jq -r '.unseal_keys_b64[0]' /opt/openbao/data/init.json)
|
||||||
UNSEAL_KEY_2=$(cat /opt/openbao/data/init.json | jq -r '.unseal_keys_b64[1]')
|
UNSEAL_KEY_2=$(jq -r '.unseal_keys_b64[1]' /opt/openbao/data/init.json)
|
||||||
UNSEAL_KEY_3=$(cat /opt/openbao/data/init.json | jq -r '.unseal_keys_b64[2]')
|
UNSEAL_KEY_3=$(jq -r '.unseal_keys_b64[2]' /opt/openbao/data/init.json)
|
||||||
|
|
||||||
bao operator unseal "$UNSEAL_KEY_1"
|
unseal_bao "$UNSEAL_KEY_1" "$UNSEAL_KEY_2" "$UNSEAL_KEY_3"
|
||||||
bao operator unseal "$UNSEAL_KEY_2"
|
|
||||||
bao operator unseal "$UNSEAL_KEY_3"
|
|
||||||
echo "OpenBao unsealed successfully!"
|
echo "OpenBao unsealed successfully!"
|
||||||
else
|
else
|
||||||
echo "OpenBao is already unsealed!"
|
echo "OpenBao is already unsealed!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ROOT_TOKEN=$(cat /opt/openbao/data/init.json | jq -r '.root_token')
|
ROOT_TOKEN=$(jq -r '.root_token' /opt/openbao/data/init.json)
|
||||||
export BAO_TOKEN=$ROOT_TOKEN
|
export BAO_TOKEN=$ROOT_TOKEN
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ require (
|
|||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/opencontainers/runc v1.2.8 // indirect
|
github.com/opencontainers/runc v1.2.8 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.3.0
|
||||||
github.com/pion/dtls/v3 v3.1.2 // indirect
|
github.com/pion/dtls/v3 v3.1.2 // indirect
|
||||||
github.com/pion/logging v0.2.4 // indirect
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package consumer contains events consumer for client and channel events
|
||||||
|
// consumed by the Bootstrap service.
|
||||||
|
package consumer
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package consumer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/absmach/magistrala/bootstrap"
|
||||||
|
"github.com/absmach/magistrala/pkg/events"
|
||||||
|
"github.com/absmach/magistrala/pkg/events/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
const stream = "events.magistrala.*.*"
|
||||||
|
|
||||||
|
type eventHandler struct {
|
||||||
|
svc bootstrap.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapEventsSubscribe subscribes bootstrap config-state handlers to the event store.
|
||||||
|
func BootstrapEventsSubscribe(ctx context.Context, svc bootstrap.Service, esURL, esConsumerName string, logger *slog.Logger) error {
|
||||||
|
subscriber, err := store.NewSubscriber(ctx, esURL, "bootstrap-es-sub", logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
subConfig := events.SubscriberConfig{
|
||||||
|
Stream: stream,
|
||||||
|
Consumer: esConsumerName,
|
||||||
|
Handler: NewEventHandler(svc),
|
||||||
|
Ordered: true,
|
||||||
|
}
|
||||||
|
return subscriber.Subscribe(ctx, subConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventHandler returns bootstrap events handler.
|
||||||
|
func NewEventHandler(svc bootstrap.Service) events.EventHandler {
|
||||||
|
return &eventHandler{
|
||||||
|
svc: svc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *eventHandler) Handle(_ context.Context, _ events.Event) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright (c) Abstract Machines
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package events provides the events sourcing of bootstrap
|
||||||
|
// provide replication in other service and definitions needed to support it
|
||||||
|
package events
|
||||||
+290
-82
@@ -14,86 +14,138 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
apiutil "github.com/absmach/magistrala/api/http/util"
|
apiutil "github.com/absmach/magistrala/api/http/util"
|
||||||
"github.com/absmach/magistrala/pkg/errors"
|
"github.com/absmach/magistrala/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configsEndpoint = "clients/configs"
|
configsEndpoint = "clients/configs"
|
||||||
bootstrapEndpoint = "clients/bootstrap"
|
bootstrapEndpoint = "clients/bootstrap"
|
||||||
whitelistEndpoint = "clients/state"
|
bootstrapCertsEndpoint = "clients/configs/certs"
|
||||||
bootstrapCertsEndpoint = "clients/configs/certs"
|
bootstrapProfilesPath = "clients/bootstrap/profiles"
|
||||||
bootstrapConnEndpoint = "clients/configs/connections"
|
bootstrapEnrollmentsPath = "clients/bootstrap/enrollments"
|
||||||
secureEndpoint = "secure"
|
secureEndpoint = "secure"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BootstrapConfig represents Configuration entity. It wraps information about external entity
|
var (
|
||||||
// as well as info about corresponding Magistrala entities.
|
errInvalidBootstrapStatus = errors.New("invalid bootstrap status")
|
||||||
// MGClient represents corresponding Magistrala Client ID.
|
errBootstrapConnectionsDisabled = errors.New("bootstrap connection updates are no longer supported")
|
||||||
// MGKey is key of corresponding Magistrala Client.
|
)
|
||||||
// MGChannels is a list of Magistrala Channels corresponding Magistrala Client connects to.
|
|
||||||
type BootstrapConfig struct {
|
type BootstrapStatus string
|
||||||
Channels any `json:"channels,omitempty"`
|
|
||||||
ExternalID string `json:"external_id,omitempty"`
|
const (
|
||||||
ExternalKey string `json:"external_key,omitempty"`
|
BootstrapDisabledStatus BootstrapStatus = DisabledStatus
|
||||||
ClientID string `json:"client_id,omitempty"`
|
BootstrapEnabledStatus BootstrapStatus = EnabledStatus
|
||||||
ClientSecret string `json:"client_secret,omitempty"`
|
)
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
ClientCert string `json:"client_cert,omitempty"`
|
func (s BootstrapStatus) String() string {
|
||||||
ClientKey string `json:"client_key,omitempty"`
|
return string(s)
|
||||||
CACert string `json:"ca_cert,omitempty"`
|
|
||||||
Content string `json:"content,omitempty"`
|
|
||||||
State int `json:"state,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *BootstrapConfig) UnmarshalJSON(data []byte) error {
|
func (s BootstrapStatus) MarshalJSON() ([]byte, error) {
|
||||||
var rawData map[string]json.RawMessage
|
return json.Marshal(string(s))
|
||||||
if err := json.Unmarshal(data, &rawData); err != nil {
|
}
|
||||||
return err
|
|
||||||
|
func (s *BootstrapStatus) UnmarshalJSON(data []byte) error {
|
||||||
|
if len(data) == 0 || string(data) == "null" {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if channelData, ok := rawData["channels"]; ok {
|
if data[0] != '"' {
|
||||||
var stringData []string
|
var n int
|
||||||
if err := json.Unmarshal(channelData, &stringData); err == nil {
|
if err := json.Unmarshal(data, &n); err != nil {
|
||||||
ts.Channels = stringData
|
return err
|
||||||
} else {
|
}
|
||||||
var channels []Channel
|
switch n {
|
||||||
if err := json.Unmarshal(channelData, &channels); err == nil {
|
case 0:
|
||||||
ts.Channels = channels
|
*s = BootstrapDisabledStatus
|
||||||
} else {
|
return nil
|
||||||
return fmt.Errorf("unsupported channel data type")
|
case 1:
|
||||||
}
|
*s = BootstrapEnabledStatus
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errInvalidBootstrapStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &struct {
|
var status string
|
||||||
ExternalID *string `json:"external_id,omitempty"`
|
if err := json.Unmarshal(data, &status); err != nil {
|
||||||
ExternalKey *string `json:"external_key,omitempty"`
|
|
||||||
ClientID *string `json:"client_id,omitempty"`
|
|
||||||
ClientSecret *string `json:"client_secret,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
ClientCert *string `json:"client_cert,omitempty"`
|
|
||||||
ClientKey *string `json:"client_key,omitempty"`
|
|
||||||
CACert *string `json:"ca_cert,omitempty"`
|
|
||||||
Content *string `json:"content,omitempty"`
|
|
||||||
State *int `json:"state,omitempty"`
|
|
||||||
}{
|
|
||||||
ExternalID: &ts.ExternalID,
|
|
||||||
ExternalKey: &ts.ExternalKey,
|
|
||||||
ClientID: &ts.ClientID,
|
|
||||||
ClientSecret: &ts.ClientSecret,
|
|
||||||
Name: &ts.Name,
|
|
||||||
ClientCert: &ts.ClientCert,
|
|
||||||
ClientKey: &ts.ClientKey,
|
|
||||||
CACert: &ts.CACert,
|
|
||||||
Content: &ts.Content,
|
|
||||||
State: &ts.State,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
switch strings.ToLower(status) {
|
||||||
|
case DisabledStatus:
|
||||||
|
*s = BootstrapDisabledStatus
|
||||||
|
return nil
|
||||||
|
case EnabledStatus:
|
||||||
|
*s = BootstrapEnabledStatus
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errInvalidBootstrapStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapConfig represents a bootstrap enrollment.
|
||||||
|
type BootstrapConfig struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
ExternalID string `json:"external_id,omitempty"`
|
||||||
|
ExternalKey string `json:"external_key,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
ClientCert string `json:"client_cert,omitempty"`
|
||||||
|
ClientKey string `json:"client_key,omitempty"`
|
||||||
|
CACert string `json:"ca_cert,omitempty"`
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
Status BootstrapStatus `json:"status,omitempty"`
|
||||||
|
ProfileID string `json:"profile_id,omitempty"`
|
||||||
|
RenderContext map[string]any `json:"render_context,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapProfile represents a bootstrap profile template.
|
||||||
|
type BootstrapProfile struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
DomainID string `json:"domain_id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
TemplateFormat string `json:"template_format,omitempty"`
|
||||||
|
ContentTemplate string `json:"content_template,omitempty"`
|
||||||
|
Defaults map[string]any `json:"defaults,omitempty"`
|
||||||
|
BindingSlots []BindingSlot `json:"binding_slots,omitempty"`
|
||||||
|
Version int `json:"version,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingSlot declares a named resource placeholder for a bootstrap profile.
|
||||||
|
type BindingSlot struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
Fields []string `json:"fields,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapBindingRequest binds a profile slot to a concrete resource.
|
||||||
|
type BootstrapBindingRequest struct {
|
||||||
|
Slot string `json:"slot"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
ResourceID string `json:"resource_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapBindingSnapshot contains a stored enrollment binding snapshot.
|
||||||
|
type BootstrapBindingSnapshot struct {
|
||||||
|
ConfigID string `json:"config_id"`
|
||||||
|
Slot string `json:"slot"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
ResourceID string `json:"resource_id"`
|
||||||
|
Snapshot map[string]any `json:"snapshot,omitempty"`
|
||||||
|
SecretSnapshot map[string]any `json:"secret_snapshot,omitempty"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type bootstrapBindingsRes struct {
|
||||||
|
Bindings []BootstrapBindingSnapshot `json:"bindings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sdk mgSDK) AddBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) (string, errors.SDKError) {
|
func (sdk mgSDK) AddBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) (string, errors.SDKError) {
|
||||||
@@ -114,6 +166,26 @@ func (sdk mgSDK) AddBootstrap(ctx context.Context, cfg BootstrapConfig, domainID
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) CreateBootstrapProfile(ctx context.Context, profile BootstrapProfile, domainID, token string) (BootstrapProfile, errors.SDKError) {
|
||||||
|
data, err := json.Marshal(profile)
|
||||||
|
if err != nil {
|
||||||
|
return BootstrapProfile{}, errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, domainID, bootstrapProfilesPath)
|
||||||
|
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, data, nil, http.StatusOK, http.StatusCreated)
|
||||||
|
if sdkerr != nil {
|
||||||
|
return BootstrapProfile{}, sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
|
var saved BootstrapProfile
|
||||||
|
if err := json.Unmarshal(body, &saved); err != nil {
|
||||||
|
return BootstrapProfile{}, errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return saved, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (sdk mgSDK) Bootstraps(ctx context.Context, pm PageMetadata, domainID, token string) (BootstrapPage, errors.SDKError) {
|
func (sdk mgSDK) Bootstraps(ctx context.Context, pm PageMetadata, domainID, token string) (BootstrapPage, errors.SDKError) {
|
||||||
endpoint := fmt.Sprintf("%s/%s", domainID, configsEndpoint)
|
endpoint := fmt.Sprintf("%s/%s", domainID, configsEndpoint)
|
||||||
url, err := sdk.withQueryParams(sdk.bootstrapURL, endpoint, pm)
|
url, err := sdk.withQueryParams(sdk.bootstrapURL, endpoint, pm)
|
||||||
@@ -134,19 +206,44 @@ func (sdk mgSDK) Bootstraps(ctx context.Context, pm PageMetadata, domainID, toke
|
|||||||
return bb, nil
|
return bb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sdk mgSDK) Whitelist(ctx context.Context, clientID string, state int, domainID, token string) errors.SDKError {
|
func (sdk mgSDK) BootstrapProfiles(ctx context.Context, pm PageMetadata, domainID, token string) (BootstrapProfilesPage, errors.SDKError) {
|
||||||
if clientID == "" {
|
endpoint := fmt.Sprintf("%s/%s", domainID, bootstrapProfilesPath)
|
||||||
|
url, err := sdk.withQueryParams(sdk.bootstrapURL, endpoint, pm)
|
||||||
|
if err != nil {
|
||||||
|
return BootstrapProfilesPage{}, errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||||
|
if sdkerr != nil {
|
||||||
|
return BootstrapProfilesPage{}, sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
|
var page BootstrapProfilesPage
|
||||||
|
if err := json.Unmarshal(body, &page); err != nil {
|
||||||
|
return BootstrapProfilesPage{}, errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return page, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) Whitelist(ctx context.Context, id string, status BootstrapStatus, domainID, token string) errors.SDKError {
|
||||||
|
if id == "" {
|
||||||
return errors.NewSDKError(apiutil.ErrMissingID)
|
return errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(BootstrapConfig{State: state})
|
var action string
|
||||||
if err != nil {
|
switch status {
|
||||||
return errors.NewSDKError(err)
|
case BootstrapEnabledStatus:
|
||||||
|
action = enableEndpoint
|
||||||
|
case BootstrapDisabledStatus:
|
||||||
|
action = disableEndpoint
|
||||||
|
default:
|
||||||
|
return errors.NewSDKErrorWithStatus(errInvalidBootstrapStatus, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, whitelistEndpoint, clientID)
|
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint, id, action)
|
||||||
|
|
||||||
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusCreated, http.StatusOK)
|
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
|
||||||
|
|
||||||
return sdkerr
|
return sdkerr
|
||||||
}
|
}
|
||||||
@@ -170,22 +267,56 @@ func (sdk mgSDK) ViewBootstrap(ctx context.Context, id, domainID, token string)
|
|||||||
return bc, nil
|
return bc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) ViewBootstrapProfile(ctx context.Context, id, domainID, token string) (BootstrapProfile, errors.SDKError) {
|
||||||
|
if id == "" {
|
||||||
|
return BootstrapProfile{}, errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, bootstrapProfilesPath, id)
|
||||||
|
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||||
|
if sdkerr != nil {
|
||||||
|
return BootstrapProfile{}, sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile BootstrapProfile
|
||||||
|
if err := json.Unmarshal(body, &profile); err != nil {
|
||||||
|
return BootstrapProfile{}, errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (sdk mgSDK) UpdateBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) errors.SDKError {
|
func (sdk mgSDK) UpdateBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) errors.SDKError {
|
||||||
if cfg.ClientID == "" {
|
if cfg.ID == "" {
|
||||||
return errors.NewSDKError(apiutil.ErrMissingID)
|
return errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint, cfg.ClientID)
|
url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, configsEndpoint, cfg.ID)
|
||||||
|
|
||||||
data, err := json.Marshal(cfg)
|
data, err := json.Marshal(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.NewSDKError(err)
|
return errors.NewSDKError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusOK)
|
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
|
||||||
|
|
||||||
return sdkerr
|
return sdkerr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) UpdateBootstrapProfile(ctx context.Context, profile BootstrapProfile, domainID, token string) errors.SDKError {
|
||||||
|
if profile.ID == "" {
|
||||||
|
return errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, bootstrapProfilesPath, profile.ID)
|
||||||
|
data, err := json.Marshal(profile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
|
||||||
|
return sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
func (sdk mgSDK) UpdateBootstrapCerts(ctx context.Context, id, clientCert, clientKey, ca, domainID, token string) (BootstrapConfig, errors.SDKError) {
|
func (sdk mgSDK) UpdateBootstrapCerts(ctx context.Context, id, clientCert, clientKey, ca, domainID, token string) (BootstrapConfig, errors.SDKError) {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return BootstrapConfig{}, errors.NewSDKError(apiutil.ErrMissingID)
|
return BootstrapConfig{}, errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
@@ -219,17 +350,12 @@ func (sdk mgSDK) UpdateBootstrapConnection(ctx context.Context, id string, chann
|
|||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.NewSDKError(apiutil.ErrMissingID)
|
return errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, bootstrapConnEndpoint, id)
|
_ = ctx
|
||||||
request := map[string][]string{
|
_ = channels
|
||||||
"channels": channels,
|
_ = domainID
|
||||||
}
|
_ = token
|
||||||
data, err := json.Marshal(request)
|
|
||||||
if err != nil {
|
|
||||||
return errors.NewSDKError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusOK)
|
return errors.NewSDKError(errBootstrapConnectionsDisabled)
|
||||||
return sdkerr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sdk mgSDK) RemoveBootstrap(ctx context.Context, id, domainID, token string) errors.SDKError {
|
func (sdk mgSDK) RemoveBootstrap(ctx context.Context, id, domainID, token string) errors.SDKError {
|
||||||
@@ -242,6 +368,88 @@ func (sdk mgSDK) RemoveBootstrap(ctx context.Context, id, domainID, token string
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) RemoveBootstrapProfile(ctx context.Context, id, domainID, token string) errors.SDKError {
|
||||||
|
if id == "" {
|
||||||
|
return errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s/%s/%s", sdk.bootstrapURL, domainID, bootstrapProfilesPath, id)
|
||||||
|
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent)
|
||||||
|
return sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) AssignBootstrapProfile(ctx context.Context, configID, profileID, domainID, token string) errors.SDKError {
|
||||||
|
if configID == "" || profileID == "" {
|
||||||
|
return errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s/%s/%s/profile", sdk.bootstrapURL, domainID, bootstrapEnrollmentsPath, configID)
|
||||||
|
request := struct {
|
||||||
|
ProfileID string `json:"profile_id"`
|
||||||
|
}{
|
||||||
|
ProfileID: profileID,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusNoContent)
|
||||||
|
return sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) BindBootstrapResources(ctx context.Context, configID string, bindings []BootstrapBindingRequest, domainID, token string) errors.SDKError {
|
||||||
|
if configID == "" {
|
||||||
|
return errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s/%s/%s/bindings", sdk.bootstrapURL, domainID, bootstrapEnrollmentsPath, configID)
|
||||||
|
request := struct {
|
||||||
|
Bindings []BootstrapBindingRequest `json:"bindings"`
|
||||||
|
}{
|
||||||
|
Bindings: bindings,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusNoContent)
|
||||||
|
return sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) BootstrapBindings(ctx context.Context, configID, domainID, token string) ([]BootstrapBindingSnapshot, errors.SDKError) {
|
||||||
|
if configID == "" {
|
||||||
|
return nil, errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s/%s/%s/bindings", sdk.bootstrapURL, domainID, bootstrapEnrollmentsPath, configID)
|
||||||
|
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
|
||||||
|
if sdkerr != nil {
|
||||||
|
return nil, sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
|
var res bootstrapBindingsRes
|
||||||
|
if err := json.Unmarshal(body, &res); err != nil {
|
||||||
|
return nil, errors.NewSDKError(err)
|
||||||
|
}
|
||||||
|
if res.Bindings == nil {
|
||||||
|
return []BootstrapBindingSnapshot{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Bindings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sdk mgSDK) RefreshBootstrapBindings(ctx context.Context, configID, domainID, token string) errors.SDKError {
|
||||||
|
if configID == "" {
|
||||||
|
return errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s/%s/%s/bindings/refresh", sdk.bootstrapURL, domainID, bootstrapEnrollmentsPath, configID)
|
||||||
|
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusNoContent)
|
||||||
|
return sdkerr
|
||||||
|
}
|
||||||
|
|
||||||
func (sdk mgSDK) Bootstrap(ctx context.Context, externalID, externalKey string) (BootstrapConfig, errors.SDKError) {
|
func (sdk mgSDK) Bootstrap(ctx context.Context, externalID, externalKey string) (BootstrapConfig, errors.SDKError) {
|
||||||
if externalID == "" {
|
if externalID == "" {
|
||||||
return BootstrapConfig{}, errors.NewSDKError(apiutil.ErrMissingID)
|
return BootstrapConfig{}, errors.NewSDKError(apiutil.ErrMissingID)
|
||||||
|
|||||||
+584
-1010
File diff suppressed because it is too large
Load Diff
+700
-11
@@ -975,6 +975,83 @@ func (_c *SDK_AddRule_Call) RunAndReturn(run func(ctx context.Context, r sdk.Rul
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssignBootstrapProfile provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) AssignBootstrapProfile(ctx context.Context, configID string, profileID string, domainID string, token string) errors.SDKError {
|
||||||
|
ret := _mock.Called(ctx, configID, profileID, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for AssignBootstrapProfile")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string) errors.SDKError); ok {
|
||||||
|
r0 = returnFunc(ctx, configID, profileID, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_AssignBootstrapProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AssignBootstrapProfile'
|
||||||
|
type SDK_AssignBootstrapProfile_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssignBootstrapProfile is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - configID string
|
||||||
|
// - profileID string
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) AssignBootstrapProfile(ctx interface{}, configID interface{}, profileID interface{}, domainID interface{}, token interface{}) *SDK_AssignBootstrapProfile_Call {
|
||||||
|
return &SDK_AssignBootstrapProfile_Call{Call: _e.mock.On("AssignBootstrapProfile", ctx, configID, profileID, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_AssignBootstrapProfile_Call) Run(run func(ctx context.Context, configID string, profileID string, domainID string, token string)) *SDK_AssignBootstrapProfile_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
var arg4 string
|
||||||
|
if args[4] != nil {
|
||||||
|
arg4 = args[4].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
arg4,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_AssignBootstrapProfile_Call) Return(sDKError errors.SDKError) *SDK_AssignBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_AssignBootstrapProfile_Call) RunAndReturn(run func(ctx context.Context, configID string, profileID string, domainID string, token string) errors.SDKError) *SDK_AssignBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// AvailableClientRoleActions provides a mock function for the type SDK
|
// AvailableClientRoleActions provides a mock function for the type SDK
|
||||||
func (_mock *SDK) AvailableClientRoleActions(ctx context.Context, domainID string, token string) ([]string, errors.SDKError) {
|
func (_mock *SDK) AvailableClientRoleActions(ctx context.Context, domainID string, token string) ([]string, errors.SDKError) {
|
||||||
ret := _mock.Called(ctx, domainID, token)
|
ret := _mock.Called(ctx, domainID, token)
|
||||||
@@ -1197,6 +1274,83 @@ func (_c *SDK_AvailableGroupRoleActions_Call) RunAndReturn(run func(ctx context.
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindBootstrapResources provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) BindBootstrapResources(ctx context.Context, configID string, bindings []sdk.BootstrapBindingRequest, domainID string, token string) errors.SDKError {
|
||||||
|
ret := _mock.Called(ctx, configID, bindings, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for BindBootstrapResources")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, []sdk.BootstrapBindingRequest, string, string) errors.SDKError); ok {
|
||||||
|
r0 = returnFunc(ctx, configID, bindings, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_BindBootstrapResources_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BindBootstrapResources'
|
||||||
|
type SDK_BindBootstrapResources_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindBootstrapResources is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - configID string
|
||||||
|
// - bindings []sdk.BootstrapBindingRequest
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) BindBootstrapResources(ctx interface{}, configID interface{}, bindings interface{}, domainID interface{}, token interface{}) *SDK_BindBootstrapResources_Call {
|
||||||
|
return &SDK_BindBootstrapResources_Call{Call: _e.mock.On("BindBootstrapResources", ctx, configID, bindings, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BindBootstrapResources_Call) Run(run func(ctx context.Context, configID string, bindings []sdk.BootstrapBindingRequest, domainID string, token string)) *SDK_BindBootstrapResources_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 []sdk.BootstrapBindingRequest
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].([]sdk.BootstrapBindingRequest)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
var arg4 string
|
||||||
|
if args[4] != nil {
|
||||||
|
arg4 = args[4].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
arg4,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BindBootstrapResources_Call) Return(sDKError errors.SDKError) *SDK_BindBootstrapResources_Call {
|
||||||
|
_c.Call.Return(sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BindBootstrapResources_Call) RunAndReturn(run func(ctx context.Context, configID string, bindings []sdk.BootstrapBindingRequest, domainID string, token string) errors.SDKError) *SDK_BindBootstrapResources_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// Bootstrap provides a mock function for the type SDK
|
// Bootstrap provides a mock function for the type SDK
|
||||||
func (_mock *SDK) Bootstrap(ctx context.Context, externalID string, externalKey string) (sdk.BootstrapConfig, errors.SDKError) {
|
func (_mock *SDK) Bootstrap(ctx context.Context, externalID string, externalKey string) (sdk.BootstrapConfig, errors.SDKError) {
|
||||||
ret := _mock.Called(ctx, externalID, externalKey)
|
ret := _mock.Called(ctx, externalID, externalKey)
|
||||||
@@ -1271,6 +1425,168 @@ func (_c *SDK_Bootstrap_Call) RunAndReturn(run func(ctx context.Context, externa
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BootstrapBindings provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) BootstrapBindings(ctx context.Context, configID string, domainID string, token string) ([]sdk.BootstrapBindingSnapshot, errors.SDKError) {
|
||||||
|
ret := _mock.Called(ctx, configID, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for BootstrapBindings")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 []sdk.BootstrapBindingSnapshot
|
||||||
|
var r1 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) ([]sdk.BootstrapBindingSnapshot, errors.SDKError)); ok {
|
||||||
|
return returnFunc(ctx, configID, domainID, token)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) []sdk.BootstrapBindingSnapshot); ok {
|
||||||
|
r0 = returnFunc(ctx, configID, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]sdk.BootstrapBindingSnapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string) errors.SDKError); ok {
|
||||||
|
r1 = returnFunc(ctx, configID, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(1) != nil {
|
||||||
|
r1 = ret.Get(1).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_BootstrapBindings_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BootstrapBindings'
|
||||||
|
type SDK_BootstrapBindings_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapBindings is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - configID string
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) BootstrapBindings(ctx interface{}, configID interface{}, domainID interface{}, token interface{}) *SDK_BootstrapBindings_Call {
|
||||||
|
return &SDK_BootstrapBindings_Call{Call: _e.mock.On("BootstrapBindings", ctx, configID, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BootstrapBindings_Call) Run(run func(ctx context.Context, configID string, domainID string, token string)) *SDK_BootstrapBindings_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BootstrapBindings_Call) Return(bootstrapBindingSnapshots []sdk.BootstrapBindingSnapshot, sDKError errors.SDKError) *SDK_BootstrapBindings_Call {
|
||||||
|
_c.Call.Return(bootstrapBindingSnapshots, sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BootstrapBindings_Call) RunAndReturn(run func(ctx context.Context, configID string, domainID string, token string) ([]sdk.BootstrapBindingSnapshot, errors.SDKError)) *SDK_BootstrapBindings_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapProfiles provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) BootstrapProfiles(ctx context.Context, pm sdk.PageMetadata, domainID string, token string) (sdk.BootstrapProfilesPage, errors.SDKError) {
|
||||||
|
ret := _mock.Called(ctx, pm, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for BootstrapProfiles")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 sdk.BootstrapProfilesPage
|
||||||
|
var r1 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, sdk.PageMetadata, string, string) (sdk.BootstrapProfilesPage, errors.SDKError)); ok {
|
||||||
|
return returnFunc(ctx, pm, domainID, token)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, sdk.PageMetadata, string, string) sdk.BootstrapProfilesPage); ok {
|
||||||
|
r0 = returnFunc(ctx, pm, domainID, token)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(sdk.BootstrapProfilesPage)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, sdk.PageMetadata, string, string) errors.SDKError); ok {
|
||||||
|
r1 = returnFunc(ctx, pm, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(1) != nil {
|
||||||
|
r1 = ret.Get(1).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_BootstrapProfiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BootstrapProfiles'
|
||||||
|
type SDK_BootstrapProfiles_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapProfiles is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - pm sdk.PageMetadata
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) BootstrapProfiles(ctx interface{}, pm interface{}, domainID interface{}, token interface{}) *SDK_BootstrapProfiles_Call {
|
||||||
|
return &SDK_BootstrapProfiles_Call{Call: _e.mock.On("BootstrapProfiles", ctx, pm, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BootstrapProfiles_Call) Run(run func(ctx context.Context, pm sdk.PageMetadata, domainID string, token string)) *SDK_BootstrapProfiles_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 sdk.PageMetadata
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(sdk.PageMetadata)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BootstrapProfiles_Call) Return(bootstrapProfilesPage sdk.BootstrapProfilesPage, sDKError errors.SDKError) *SDK_BootstrapProfiles_Call {
|
||||||
|
_c.Call.Return(bootstrapProfilesPage, sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_BootstrapProfiles_Call) RunAndReturn(run func(ctx context.Context, pm sdk.PageMetadata, domainID string, token string) (sdk.BootstrapProfilesPage, errors.SDKError)) *SDK_BootstrapProfiles_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// BootstrapSecure provides a mock function for the type SDK
|
// BootstrapSecure provides a mock function for the type SDK
|
||||||
func (_mock *SDK) BootstrapSecure(ctx context.Context, externalID string, externalKey string, cryptoKey string) (sdk.BootstrapConfig, errors.SDKError) {
|
func (_mock *SDK) BootstrapSecure(ctx context.Context, externalID string, externalKey string, cryptoKey string) (sdk.BootstrapConfig, errors.SDKError) {
|
||||||
ret := _mock.Called(ctx, externalID, externalKey, cryptoKey)
|
ret := _mock.Called(ctx, externalID, externalKey, cryptoKey)
|
||||||
@@ -2343,6 +2659,86 @@ func (_c *SDK_ConnectClients_Call) RunAndReturn(run func(ctx context.Context, ch
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateBootstrapProfile provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) CreateBootstrapProfile(ctx context.Context, profile sdk.BootstrapProfile, domainID string, token string) (sdk.BootstrapProfile, errors.SDKError) {
|
||||||
|
ret := _mock.Called(ctx, profile, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for CreateBootstrapProfile")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 sdk.BootstrapProfile
|
||||||
|
var r1 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, sdk.BootstrapProfile, string, string) (sdk.BootstrapProfile, errors.SDKError)); ok {
|
||||||
|
return returnFunc(ctx, profile, domainID, token)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, sdk.BootstrapProfile, string, string) sdk.BootstrapProfile); ok {
|
||||||
|
r0 = returnFunc(ctx, profile, domainID, token)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(sdk.BootstrapProfile)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, sdk.BootstrapProfile, string, string) errors.SDKError); ok {
|
||||||
|
r1 = returnFunc(ctx, profile, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(1) != nil {
|
||||||
|
r1 = ret.Get(1).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_CreateBootstrapProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateBootstrapProfile'
|
||||||
|
type SDK_CreateBootstrapProfile_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBootstrapProfile is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - profile sdk.BootstrapProfile
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) CreateBootstrapProfile(ctx interface{}, profile interface{}, domainID interface{}, token interface{}) *SDK_CreateBootstrapProfile_Call {
|
||||||
|
return &SDK_CreateBootstrapProfile_Call{Call: _e.mock.On("CreateBootstrapProfile", ctx, profile, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_CreateBootstrapProfile_Call) Run(run func(ctx context.Context, profile sdk.BootstrapProfile, domainID string, token string)) *SDK_CreateBootstrapProfile_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 sdk.BootstrapProfile
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(sdk.BootstrapProfile)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_CreateBootstrapProfile_Call) Return(bootstrapProfile sdk.BootstrapProfile, sDKError errors.SDKError) *SDK_CreateBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(bootstrapProfile, sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_CreateBootstrapProfile_Call) RunAndReturn(run func(ctx context.Context, profile sdk.BootstrapProfile, domainID string, token string) (sdk.BootstrapProfile, errors.SDKError)) *SDK_CreateBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// CreateCSR provides a mock function for the type SDK
|
// CreateCSR provides a mock function for the type SDK
|
||||||
func (_mock *SDK) CreateCSR(ctx context.Context, metadata sdk.CSRMetadata, privKey any) (sdk.CSR, errors.SDKError) {
|
func (_mock *SDK) CreateCSR(ctx context.Context, metadata sdk.CSRMetadata, privKey any) (sdk.CSR, errors.SDKError) {
|
||||||
ret := _mock.Called(ctx, metadata, privKey)
|
ret := _mock.Called(ctx, metadata, privKey)
|
||||||
@@ -8360,6 +8756,77 @@ func (_c *SDK_ReadMessages_Call) RunAndReturn(run func(ctx context.Context, pm s
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefreshBootstrapBindings provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) RefreshBootstrapBindings(ctx context.Context, configID string, domainID string, token string) errors.SDKError {
|
||||||
|
ret := _mock.Called(ctx, configID, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for RefreshBootstrapBindings")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) errors.SDKError); ok {
|
||||||
|
r0 = returnFunc(ctx, configID, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_RefreshBootstrapBindings_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RefreshBootstrapBindings'
|
||||||
|
type SDK_RefreshBootstrapBindings_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshBootstrapBindings is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - configID string
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) RefreshBootstrapBindings(ctx interface{}, configID interface{}, domainID interface{}, token interface{}) *SDK_RefreshBootstrapBindings_Call {
|
||||||
|
return &SDK_RefreshBootstrapBindings_Call{Call: _e.mock.On("RefreshBootstrapBindings", ctx, configID, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_RefreshBootstrapBindings_Call) Run(run func(ctx context.Context, configID string, domainID string, token string)) *SDK_RefreshBootstrapBindings_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_RefreshBootstrapBindings_Call) Return(sDKError errors.SDKError) *SDK_RefreshBootstrapBindings_Call {
|
||||||
|
_c.Call.Return(sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_RefreshBootstrapBindings_Call) RunAndReturn(run func(ctx context.Context, configID string, domainID string, token string) errors.SDKError) *SDK_RefreshBootstrapBindings_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// RefreshToken provides a mock function for the type SDK
|
// RefreshToken provides a mock function for the type SDK
|
||||||
func (_mock *SDK) RefreshToken(ctx context.Context, token string) (sdk.Token, errors.SDKError) {
|
func (_mock *SDK) RefreshToken(ctx context.Context, token string) (sdk.Token, errors.SDKError) {
|
||||||
ret := _mock.Called(ctx, token)
|
ret := _mock.Called(ctx, token)
|
||||||
@@ -9083,6 +9550,77 @@ func (_c *SDK_RemoveBootstrap_Call) RunAndReturn(run func(ctx context.Context, i
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveBootstrapProfile provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) RemoveBootstrapProfile(ctx context.Context, id string, domainID string, token string) errors.SDKError {
|
||||||
|
ret := _mock.Called(ctx, id, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for RemoveBootstrapProfile")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) errors.SDKError); ok {
|
||||||
|
r0 = returnFunc(ctx, id, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_RemoveBootstrapProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveBootstrapProfile'
|
||||||
|
type SDK_RemoveBootstrapProfile_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveBootstrapProfile is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) RemoveBootstrapProfile(ctx interface{}, id interface{}, domainID interface{}, token interface{}) *SDK_RemoveBootstrapProfile_Call {
|
||||||
|
return &SDK_RemoveBootstrapProfile_Call{Call: _e.mock.On("RemoveBootstrapProfile", ctx, id, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_RemoveBootstrapProfile_Call) Run(run func(ctx context.Context, id string, domainID string, token string)) *SDK_RemoveBootstrapProfile_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_RemoveBootstrapProfile_Call) Return(sDKError errors.SDKError) *SDK_RemoveBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_RemoveBootstrapProfile_Call) RunAndReturn(run func(ctx context.Context, id string, domainID string, token string) errors.SDKError) *SDK_RemoveBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveChannelParent provides a mock function for the type SDK
|
// RemoveChannelParent provides a mock function for the type SDK
|
||||||
func (_mock *SDK) RemoveChannelParent(ctx context.Context, id string, domainID string, groupID string, token string) errors.SDKError {
|
func (_mock *SDK) RemoveChannelParent(ctx context.Context, id string, domainID string, groupID string, token string) errors.SDKError {
|
||||||
ret := _mock.Called(ctx, id, domainID, groupID, token)
|
ret := _mock.Called(ctx, id, domainID, groupID, token)
|
||||||
@@ -11254,6 +11792,77 @@ func (_c *SDK_UpdateBootstrapConnection_Call) RunAndReturn(run func(ctx context.
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateBootstrapProfile provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) UpdateBootstrapProfile(ctx context.Context, profile sdk.BootstrapProfile, domainID string, token string) errors.SDKError {
|
||||||
|
ret := _mock.Called(ctx, profile, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for UpdateBootstrapProfile")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, sdk.BootstrapProfile, string, string) errors.SDKError); ok {
|
||||||
|
r0 = returnFunc(ctx, profile, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_UpdateBootstrapProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateBootstrapProfile'
|
||||||
|
type SDK_UpdateBootstrapProfile_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBootstrapProfile is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - profile sdk.BootstrapProfile
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) UpdateBootstrapProfile(ctx interface{}, profile interface{}, domainID interface{}, token interface{}) *SDK_UpdateBootstrapProfile_Call {
|
||||||
|
return &SDK_UpdateBootstrapProfile_Call{Call: _e.mock.On("UpdateBootstrapProfile", ctx, profile, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_UpdateBootstrapProfile_Call) Run(run func(ctx context.Context, profile sdk.BootstrapProfile, domainID string, token string)) *SDK_UpdateBootstrapProfile_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 sdk.BootstrapProfile
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(sdk.BootstrapProfile)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_UpdateBootstrapProfile_Call) Return(sDKError errors.SDKError) *SDK_UpdateBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_UpdateBootstrapProfile_Call) RunAndReturn(run func(ctx context.Context, profile sdk.BootstrapProfile, domainID string, token string) errors.SDKError) *SDK_UpdateBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateChannel provides a mock function for the type SDK
|
// UpdateChannel provides a mock function for the type SDK
|
||||||
func (_mock *SDK) UpdateChannel(ctx context.Context, channel sdk.Channel, domainID string, token string) (sdk.Channel, errors.SDKError) {
|
func (_mock *SDK) UpdateChannel(ctx context.Context, channel sdk.Channel, domainID string, token string) (sdk.Channel, errors.SDKError) {
|
||||||
ret := _mock.Called(ctx, channel, domainID, token)
|
ret := _mock.Called(ctx, channel, domainID, token)
|
||||||
@@ -13594,6 +14203,86 @@ func (_c *SDK_ViewBootstrap_Call) RunAndReturn(run func(ctx context.Context, id
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ViewBootstrapProfile provides a mock function for the type SDK
|
||||||
|
func (_mock *SDK) ViewBootstrapProfile(ctx context.Context, id string, domainID string, token string) (sdk.BootstrapProfile, errors.SDKError) {
|
||||||
|
ret := _mock.Called(ctx, id, domainID, token)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for ViewBootstrapProfile")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 sdk.BootstrapProfile
|
||||||
|
var r1 errors.SDKError
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) (sdk.BootstrapProfile, errors.SDKError)); ok {
|
||||||
|
return returnFunc(ctx, id, domainID, token)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) sdk.BootstrapProfile); ok {
|
||||||
|
r0 = returnFunc(ctx, id, domainID, token)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(sdk.BootstrapProfile)
|
||||||
|
}
|
||||||
|
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string) errors.SDKError); ok {
|
||||||
|
r1 = returnFunc(ctx, id, domainID, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(1) != nil {
|
||||||
|
r1 = ret.Get(1).(errors.SDKError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDK_ViewBootstrapProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewBootstrapProfile'
|
||||||
|
type SDK_ViewBootstrapProfile_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewBootstrapProfile is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
// - domainID string
|
||||||
|
// - token string
|
||||||
|
func (_e *SDK_Expecter) ViewBootstrapProfile(ctx interface{}, id interface{}, domainID interface{}, token interface{}) *SDK_ViewBootstrapProfile_Call {
|
||||||
|
return &SDK_ViewBootstrapProfile_Call{Call: _e.mock.On("ViewBootstrapProfile", ctx, id, domainID, token)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_ViewBootstrapProfile_Call) Run(run func(ctx context.Context, id string, domainID string, token string)) *SDK_ViewBootstrapProfile_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
var arg0 context.Context
|
||||||
|
if args[0] != nil {
|
||||||
|
arg0 = args[0].(context.Context)
|
||||||
|
}
|
||||||
|
var arg1 string
|
||||||
|
if args[1] != nil {
|
||||||
|
arg1 = args[1].(string)
|
||||||
|
}
|
||||||
|
var arg2 string
|
||||||
|
if args[2] != nil {
|
||||||
|
arg2 = args[2].(string)
|
||||||
|
}
|
||||||
|
var arg3 string
|
||||||
|
if args[3] != nil {
|
||||||
|
arg3 = args[3].(string)
|
||||||
|
}
|
||||||
|
run(
|
||||||
|
arg0,
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
arg3,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_ViewBootstrapProfile_Call) Return(bootstrapProfile sdk.BootstrapProfile, sDKError errors.SDKError) *SDK_ViewBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(bootstrapProfile, sDKError)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *SDK_ViewBootstrapProfile_Call) RunAndReturn(run func(ctx context.Context, id string, domainID string, token string) (sdk.BootstrapProfile, errors.SDKError)) *SDK_ViewBootstrapProfile_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// ViewCA provides a mock function for the type SDK
|
// ViewCA provides a mock function for the type SDK
|
||||||
func (_mock *SDK) ViewCA(ctx context.Context) (sdk.Certificate, errors.SDKError) {
|
func (_mock *SDK) ViewCA(ctx context.Context) (sdk.Certificate, errors.SDKError) {
|
||||||
ret := _mock.Called(ctx)
|
ret := _mock.Called(ctx)
|
||||||
@@ -14053,16 +14742,16 @@ func (_c *SDK_ViewSubscription_Call) RunAndReturn(run func(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Whitelist provides a mock function for the type SDK
|
// Whitelist provides a mock function for the type SDK
|
||||||
func (_mock *SDK) Whitelist(ctx context.Context, clientID string, state int, domainID string, token string) errors.SDKError {
|
func (_mock *SDK) Whitelist(ctx context.Context, clientID string, status sdk.BootstrapStatus, domainID string, token string) errors.SDKError {
|
||||||
ret := _mock.Called(ctx, clientID, state, domainID, token)
|
ret := _mock.Called(ctx, clientID, status, domainID, token)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for Whitelist")
|
panic("no return value specified for Whitelist")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 errors.SDKError
|
var r0 errors.SDKError
|
||||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, int, string, string) errors.SDKError); ok {
|
if returnFunc, ok := ret.Get(0).(func(context.Context, string, sdk.BootstrapStatus, string, string) errors.SDKError); ok {
|
||||||
r0 = returnFunc(ctx, clientID, state, domainID, token)
|
r0 = returnFunc(ctx, clientID, status, domainID, token)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(errors.SDKError)
|
r0 = ret.Get(0).(errors.SDKError)
|
||||||
@@ -14079,14 +14768,14 @@ type SDK_Whitelist_Call struct {
|
|||||||
// Whitelist is a helper method to define mock.On call
|
// Whitelist is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - clientID string
|
// - clientID string
|
||||||
// - state int
|
// - status sdk.BootstrapStatus
|
||||||
// - domainID string
|
// - domainID string
|
||||||
// - token string
|
// - token string
|
||||||
func (_e *SDK_Expecter) Whitelist(ctx interface{}, clientID interface{}, state interface{}, domainID interface{}, token interface{}) *SDK_Whitelist_Call {
|
func (_e *SDK_Expecter) Whitelist(ctx interface{}, clientID interface{}, status interface{}, domainID interface{}, token interface{}) *SDK_Whitelist_Call {
|
||||||
return &SDK_Whitelist_Call{Call: _e.mock.On("Whitelist", ctx, clientID, state, domainID, token)}
|
return &SDK_Whitelist_Call{Call: _e.mock.On("Whitelist", ctx, clientID, status, domainID, token)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *SDK_Whitelist_Call) Run(run func(ctx context.Context, clientID string, state int, domainID string, token string)) *SDK_Whitelist_Call {
|
func (_c *SDK_Whitelist_Call) Run(run func(ctx context.Context, clientID string, status sdk.BootstrapStatus, domainID string, token string)) *SDK_Whitelist_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
var arg0 context.Context
|
var arg0 context.Context
|
||||||
if args[0] != nil {
|
if args[0] != nil {
|
||||||
@@ -14096,9 +14785,9 @@ func (_c *SDK_Whitelist_Call) Run(run func(ctx context.Context, clientID string,
|
|||||||
if args[1] != nil {
|
if args[1] != nil {
|
||||||
arg1 = args[1].(string)
|
arg1 = args[1].(string)
|
||||||
}
|
}
|
||||||
var arg2 int
|
var arg2 sdk.BootstrapStatus
|
||||||
if args[2] != nil {
|
if args[2] != nil {
|
||||||
arg2 = args[2].(int)
|
arg2 = args[2].(sdk.BootstrapStatus)
|
||||||
}
|
}
|
||||||
var arg3 string
|
var arg3 string
|
||||||
if args[3] != nil {
|
if args[3] != nil {
|
||||||
@@ -14124,7 +14813,7 @@ func (_c *SDK_Whitelist_Call) Return(sDKError errors.SDKError) *SDK_Whitelist_Ca
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *SDK_Whitelist_Call) RunAndReturn(run func(ctx context.Context, clientID string, state int, domainID string, token string) errors.SDKError) *SDK_Whitelist_Call {
|
func (_c *SDK_Whitelist_Call) RunAndReturn(run func(ctx context.Context, clientID string, status sdk.BootstrapStatus, domainID string, token string) errors.SDKError) *SDK_Whitelist_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ type BootstrapPage struct {
|
|||||||
PageRes
|
PageRes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BootstrapProfilesPage contains list of bootstrap profiles in a page with proper metadata.
|
||||||
|
type BootstrapProfilesPage struct {
|
||||||
|
Profiles []BootstrapProfile `json:"profiles"`
|
||||||
|
PageRes
|
||||||
|
}
|
||||||
|
|
||||||
// SubscriptionPage contains list of subscriptions in a page with proper metadata.
|
// SubscriptionPage contains list of subscriptions in a page with proper metadata.
|
||||||
type SubscriptionPage struct {
|
type SubscriptionPage struct {
|
||||||
Subscriptions []Subscription `json:"subscriptions"`
|
Subscriptions []Subscription `json:"subscriptions"`
|
||||||
|
|||||||
+29
-2
@@ -1619,12 +1619,21 @@ type SDK interface {
|
|||||||
// AddBootstrap add bootstrap configuration
|
// AddBootstrap add bootstrap configuration
|
||||||
AddBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) (string, smqerrors.SDKError)
|
AddBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) (string, smqerrors.SDKError)
|
||||||
|
|
||||||
|
// CreateBootstrapProfile creates a bootstrap profile template.
|
||||||
|
CreateBootstrapProfile(ctx context.Context, profile BootstrapProfile, domainID, token string) (BootstrapProfile, smqerrors.SDKError)
|
||||||
|
|
||||||
// ViewBootstrap returns Client Config with given ID belonging to the user identified by the given token.
|
// ViewBootstrap returns Client Config with given ID belonging to the user identified by the given token.
|
||||||
ViewBootstrap(ctx context.Context, id, domainID, token string) (BootstrapConfig, smqerrors.SDKError)
|
ViewBootstrap(ctx context.Context, id, domainID, token string) (BootstrapConfig, smqerrors.SDKError)
|
||||||
|
|
||||||
|
// ViewBootstrapProfile returns bootstrap profile with the given ID.
|
||||||
|
ViewBootstrapProfile(ctx context.Context, id, domainID, token string) (BootstrapProfile, smqerrors.SDKError)
|
||||||
|
|
||||||
// UpdateBootstrap updates editable fields of the provided Config.
|
// UpdateBootstrap updates editable fields of the provided Config.
|
||||||
UpdateBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) smqerrors.SDKError
|
UpdateBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) smqerrors.SDKError
|
||||||
|
|
||||||
|
// UpdateBootstrapProfile updates editable fields of the provided bootstrap profile.
|
||||||
|
UpdateBootstrapProfile(ctx context.Context, profile BootstrapProfile, domainID, token string) smqerrors.SDKError
|
||||||
|
|
||||||
// UpdateBootstrapCerts updates bootstrap config certificates.
|
// UpdateBootstrapCerts updates bootstrap config certificates.
|
||||||
UpdateBootstrapCerts(ctx context.Context, id string, clientCert, clientKey, ca string, domainID, token string) (BootstrapConfig, smqerrors.SDKError)
|
UpdateBootstrapCerts(ctx context.Context, id string, clientCert, clientKey, ca string, domainID, token string) (BootstrapConfig, smqerrors.SDKError)
|
||||||
|
|
||||||
@@ -1634,6 +1643,9 @@ type SDK interface {
|
|||||||
// RemoveBootstrap removes Config with specified token that belongs to the user identified by the given token.
|
// RemoveBootstrap removes Config with specified token that belongs to the user identified by the given token.
|
||||||
RemoveBootstrap(ctx context.Context, id, domainID, token string) smqerrors.SDKError
|
RemoveBootstrap(ctx context.Context, id, domainID, token string) smqerrors.SDKError
|
||||||
|
|
||||||
|
// RemoveBootstrapProfile removes a bootstrap profile with the given ID.
|
||||||
|
RemoveBootstrapProfile(ctx context.Context, id, domainID, token string) smqerrors.SDKError
|
||||||
|
|
||||||
// Bootstrap returns Config to the Client with provided external ID using external key.
|
// Bootstrap returns Config to the Client with provided external ID using external key.
|
||||||
Bootstrap(ctx context.Context, externalID, externalKey string) (BootstrapConfig, smqerrors.SDKError)
|
Bootstrap(ctx context.Context, externalID, externalKey string) (BootstrapConfig, smqerrors.SDKError)
|
||||||
|
|
||||||
@@ -1643,8 +1655,23 @@ type SDK interface {
|
|||||||
// Bootstraps retrieves a list of managed configs.
|
// Bootstraps retrieves a list of managed configs.
|
||||||
Bootstraps(ctx context.Context, pm PageMetadata, domainID, token string) (BootstrapPage, smqerrors.SDKError)
|
Bootstraps(ctx context.Context, pm PageMetadata, domainID, token string) (BootstrapPage, smqerrors.SDKError)
|
||||||
|
|
||||||
// Whitelist updates Client state Config with given ID belonging to the user identified by the given token.
|
// BootstrapProfiles retrieves a list of bootstrap profiles.
|
||||||
Whitelist(ctx context.Context, clientID string, state int, domainID, token string) smqerrors.SDKError
|
BootstrapProfiles(ctx context.Context, pm PageMetadata, domainID, token string) (BootstrapProfilesPage, smqerrors.SDKError)
|
||||||
|
|
||||||
|
// Whitelist updates Client bootstrap status with given ID belonging to the user identified by the given token.
|
||||||
|
Whitelist(ctx context.Context, clientID string, status BootstrapStatus, domainID, token string) smqerrors.SDKError
|
||||||
|
|
||||||
|
// AssignBootstrapProfile assigns a bootstrap profile to the given enrollment.
|
||||||
|
AssignBootstrapProfile(ctx context.Context, configID, profileID, domainID, token string) smqerrors.SDKError
|
||||||
|
|
||||||
|
// BindBootstrapResources stores resolved binding snapshots for the given enrollment.
|
||||||
|
BindBootstrapResources(ctx context.Context, configID string, bindings []BootstrapBindingRequest, domainID, token string) smqerrors.SDKError
|
||||||
|
|
||||||
|
// BootstrapBindings lists stored binding snapshots for the given enrollment.
|
||||||
|
BootstrapBindings(ctx context.Context, configID, domainID, token string) ([]BootstrapBindingSnapshot, smqerrors.SDKError)
|
||||||
|
|
||||||
|
// RefreshBootstrapBindings refreshes stored binding snapshots for the given enrollment.
|
||||||
|
RefreshBootstrapBindings(ctx context.Context, configID, domainID, token string) smqerrors.SDKError
|
||||||
|
|
||||||
// ReadMessages reads messages of specified channel.
|
// ReadMessages reads messages of specified channel.
|
||||||
ReadMessages(ctx context.Context, pm MessagePageMetadata, chanID, domainID, token string) (MessagesPage, smqerrors.SDKError)
|
ReadMessages(ctx context.Context, pm MessagePageMetadata, chanID, domainID, token string) (MessagesPage, smqerrors.SDKError)
|
||||||
|
|||||||
+20
-4
@@ -36,10 +36,23 @@ type ServiceConf struct {
|
|||||||
|
|
||||||
// Bootstrap represetns the Bootstrap config.
|
// Bootstrap represetns the Bootstrap config.
|
||||||
type Bootstrap struct {
|
type Bootstrap struct {
|
||||||
X509Provision bool `toml:"x509_provision" env:"MG_PROVISION_X509_PROVISIONING" envDefault:"false"`
|
X509Provision bool `toml:"x509_provision" env:"MG_PROVISION_X509_PROVISIONING" envDefault:"false"`
|
||||||
Provision bool `toml:"provision" env:"MG_PROVISION_BS_CONFIG_PROVISIONING" envDefault:"true"`
|
Provision bool `toml:"provision" env:"MG_PROVISION_BS_CONFIG_PROVISIONING" envDefault:"true"`
|
||||||
AutoWhiteList bool `toml:"autowhite_list" env:"MG_PROVISION_BS_AUTO_WHITELIST" envDefault:"true"`
|
AutoWhiteList bool `toml:"autowhite_list" env:"MG_PROVISION_BS_AUTO_WHITELIST" envDefault:"true"`
|
||||||
Content map[string]any `toml:"content"`
|
ProfileID string `toml:"profile_id" env:"MG_PROVISION_BS_PROFILE_ID" envDefault:""`
|
||||||
|
RenderContext map[string]any `toml:"render_context,omitempty"`
|
||||||
|
Bindings []BootstrapBinding `toml:"bindings,omitempty"`
|
||||||
|
Content map[string]any `toml:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapBinding maps a bootstrap profile slot to one of the resources
|
||||||
|
// created by provision.
|
||||||
|
type BootstrapBinding struct {
|
||||||
|
Slot string `toml:"slot" json:"slot"`
|
||||||
|
Type string `toml:"type" json:"type"`
|
||||||
|
Name string `toml:"name" json:"name,omitempty"`
|
||||||
|
MetadataKey string `toml:"metadata_key" json:"metadata_key,omitempty"`
|
||||||
|
MetadataValue string `toml:"metadata_value" json:"metadata_value,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gateway represetns the Gateway config.
|
// Gateway represetns the Gateway config.
|
||||||
@@ -99,6 +112,9 @@ func Read(file string) (Config, error) {
|
|||||||
if err := toml.Unmarshal(data, &c); err != nil {
|
if err := toml.Unmarshal(data, &c); err != nil {
|
||||||
return Config{}, fmt.Errorf("Error unmarshaling toml: %w", err)
|
return Config{}, fmt.Errorf("Error unmarshaling toml: %w", err)
|
||||||
}
|
}
|
||||||
|
if len(c.Bootstrap.RenderContext) == 0 {
|
||||||
|
c.Bootstrap.RenderContext = nil
|
||||||
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+151
-59
@@ -17,7 +17,6 @@ import (
|
|||||||
const (
|
const (
|
||||||
externalIDKey = "external_id"
|
externalIDKey = "external_id"
|
||||||
gateway = "gateway"
|
gateway = "gateway"
|
||||||
Active = 1
|
|
||||||
|
|
||||||
control = "control"
|
control = "control"
|
||||||
data = "data"
|
data = "data"
|
||||||
@@ -40,6 +39,7 @@ var (
|
|||||||
ErrFailedCertView = errors.NewServiceError("failed to view certificate")
|
ErrFailedCertView = errors.NewServiceError("failed to view certificate")
|
||||||
ErrFailedBootstrap = errors.NewServiceError("failed to create bootstrap config")
|
ErrFailedBootstrap = errors.NewServiceError("failed to create bootstrap config")
|
||||||
ErrFailedBootstrapValidate = errors.NewServiceError("failed to validate bootstrap config creation")
|
ErrFailedBootstrapValidate = errors.NewServiceError("failed to validate bootstrap config creation")
|
||||||
|
ErrFailedBootstrapBinding = errors.NewServiceError("failed to bind bootstrap resources")
|
||||||
ErrGatewayUpdate = errors.NewServiceError("failed to update gateway metadata")
|
ErrGatewayUpdate = errors.NewServiceError("failed to update gateway metadata")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ type Service interface {
|
|||||||
// - create a Client based on external_id (eg. MAC address)
|
// - create a Client based on external_id (eg. MAC address)
|
||||||
// - create multiple Channels
|
// - create multiple Channels
|
||||||
// - create Bootstrap configuration
|
// - create Bootstrap configuration
|
||||||
// - whitelist Client in Bootstrap configuration == connect Client to Channels
|
// - enable created Bootstrap enrollments
|
||||||
Provision(ctx context.Context, domainID, token, name, externalID, externalKey string) (Result, error)
|
Provision(ctx context.Context, domainID, token, name, externalID, externalKey string) (Result, error)
|
||||||
|
|
||||||
// Mapping returns current configuration used for provision
|
// Mapping returns current configuration used for provision
|
||||||
@@ -103,7 +103,8 @@ func (ps *provisionService) Mapping() map[string]any {
|
|||||||
func (ps *provisionService) Provision(ctx context.Context, domainID, token, name, externalID, externalKey string) (res Result, err error) {
|
func (ps *provisionService) Provision(ctx context.Context, domainID, token, name, externalID, externalKey string) (res Result, err error) {
|
||||||
var channels []smqSDK.Channel
|
var channels []smqSDK.Channel
|
||||||
var clients []smqSDK.Client
|
var clients []smqSDK.Client
|
||||||
defer ps.recover(ctx, &err, &clients, &channels, domainID, token)
|
var bootstrapIDs []string
|
||||||
|
defer ps.recover(ctx, &err, &clients, &channels, &bootstrapIDs, domainID, token)
|
||||||
|
|
||||||
token, err = ps.createTokenIfEmpty(ctx, token)
|
token, err = ps.createTokenIfEmpty(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -170,38 +171,44 @@ func (ps *provisionService) Provision(ctx context.Context, domainID, token, name
|
|||||||
ClientKey: map[string]string{},
|
ClientKey: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
var bsConfig sdk.BootstrapConfig
|
content, err := json.Marshal(ps.conf.Bootstrap.Content)
|
||||||
|
if err != nil {
|
||||||
|
return Result{}, errors.Wrap(ErrFailedBootstrap, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrapConfigs := make(map[string]sdk.BootstrapConfig)
|
||||||
|
var gatewayConfig sdk.BootstrapConfig
|
||||||
|
var gatewayClientID string
|
||||||
for _, c := range clients {
|
for _, c := range clients {
|
||||||
var chanIDs []string
|
|
||||||
|
|
||||||
for _, ch := range channels {
|
|
||||||
chanIDs = append(chanIDs, ch.ID)
|
|
||||||
}
|
|
||||||
content, err := json.Marshal(ps.conf.Bootstrap.Content)
|
|
||||||
if err != nil {
|
|
||||||
return Result{}, errors.Wrap(ErrFailedBootstrap, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ps.conf.Bootstrap.Provision && needsBootstrap(c) {
|
if ps.conf.Bootstrap.Provision && needsBootstrap(c) {
|
||||||
bsReq := sdk.BootstrapConfig{
|
bsReq := sdk.BootstrapConfig{
|
||||||
ClientID: c.ID,
|
ExternalID: externalID,
|
||||||
ExternalID: externalID,
|
ExternalKey: externalKey,
|
||||||
ExternalKey: externalKey,
|
Name: name,
|
||||||
Channels: chanIDs,
|
CACert: res.CACert,
|
||||||
CACert: res.CACert,
|
ClientCert: "",
|
||||||
ClientCert: "",
|
ClientKey: "",
|
||||||
ClientKey: "",
|
Content: string(content),
|
||||||
Content: string(content),
|
ProfileID: ps.conf.Bootstrap.ProfileID,
|
||||||
|
RenderContext: ps.bootstrapRenderContext(externalID, name),
|
||||||
}
|
}
|
||||||
bsid, err := ps.sdk.AddBootstrap(ctx, bsReq, domainID, token)
|
bsid, err := ps.sdk.AddBootstrap(ctx, bsReq, domainID, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Result{}, errors.Wrap(ErrFailedBootstrap, err)
|
return Result{}, errors.Wrap(ErrFailedBootstrap, err)
|
||||||
}
|
}
|
||||||
|
bootstrapIDs = append(bootstrapIDs, bsid)
|
||||||
|
|
||||||
bsConfig, err = ps.sdk.ViewBootstrap(ctx, bsid, domainID, token)
|
bsConfig, err := ps.sdk.ViewBootstrap(ctx, bsid, domainID, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Result{}, errors.Wrap(ErrFailedBootstrapValidate, err)
|
return Result{}, errors.Wrap(ErrFailedBootstrapValidate, err)
|
||||||
}
|
}
|
||||||
|
bootstrapConfigs[c.ID] = bsConfig
|
||||||
|
gatewayConfig = bsConfig
|
||||||
|
gatewayClientID = c.ID
|
||||||
|
|
||||||
|
if err := ps.bindBootstrapResources(ctx, bsConfig.ID, c, clients, channels, domainID, token); err != nil {
|
||||||
|
return Result{}, errors.Wrap(ErrFailedBootstrapBinding, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ps.conf.Bootstrap.X509Provision {
|
if ps.conf.Bootstrap.X509Provision {
|
||||||
@@ -221,24 +228,33 @@ func (ps *provisionService) Provision(ctx context.Context, domainID, token, name
|
|||||||
res.ClientKey[c.ID] = cert.Key
|
res.ClientKey[c.ID] = cert.Key
|
||||||
res.CACert = ""
|
res.CACert = ""
|
||||||
|
|
||||||
if needsBootstrap(c) {
|
if bsConfig, ok := bootstrapConfigs[c.ID]; ok {
|
||||||
if _, err = ps.sdk.UpdateBootstrapCerts(ctx, bsConfig.ClientID, cert.Certificate, cert.Key, "", domainID, token); err != nil {
|
updated, err := ps.sdk.UpdateBootstrapCerts(ctx, bsConfig.ID, cert.Certificate, cert.Key, "", domainID, token)
|
||||||
|
if err != nil {
|
||||||
return Result{}, errors.Wrap(ErrFailedCertCreation, err)
|
return Result{}, errors.Wrap(ErrFailedCertCreation, err)
|
||||||
}
|
}
|
||||||
|
bootstrapConfigs[c.ID] = updated
|
||||||
|
if gatewayClientID == c.ID {
|
||||||
|
gatewayConfig = updated
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ps.conf.Bootstrap.AutoWhiteList {
|
if ps.conf.Bootstrap.AutoWhiteList {
|
||||||
if err := ps.sdk.Whitelist(ctx, c.ID, Active, domainID, token); err != nil {
|
if bsConfig, ok := bootstrapConfigs[c.ID]; ok {
|
||||||
res.Error = err.Error()
|
if err := ps.sdk.Whitelist(ctx, bsConfig.ID, smqSDK.BootstrapEnabledStatus, domainID, token); err != nil {
|
||||||
return res, ErrClientUpdate
|
res.Error = err.Error()
|
||||||
|
return res, ErrClientUpdate
|
||||||
|
}
|
||||||
|
res.Whitelisted[bsConfig.ID] = true
|
||||||
}
|
}
|
||||||
res.Whitelisted[c.ID] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ps.updateGateway(ctx, domainID, token, bsConfig, channels); err != nil {
|
if gatewayClientID != "" && gatewayConfig.ID != "" {
|
||||||
return res, err
|
if err = ps.updateGateway(ctx, domainID, token, gatewayClientID, gatewayConfig, externalKey, channels); err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
@@ -292,7 +308,7 @@ func (ps *provisionService) createTokenIfEmpty(ctx context.Context, token string
|
|||||||
return tkn.AccessToken, nil
|
return tkn.AccessToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *provisionService) updateGateway(ctx context.Context, domainID, token string, bs sdk.BootstrapConfig, channels []smqSDK.Channel) error {
|
func (ps *provisionService) updateGateway(ctx context.Context, domainID, token, gatewayClientID string, bs sdk.BootstrapConfig, externalKey string, channels []smqSDK.Channel) error {
|
||||||
var gw Gateway
|
var gw Gateway
|
||||||
for _, ch := range channels {
|
for _, ch := range channels {
|
||||||
switch ch.Metadata["type"] {
|
switch ch.Metadata["type"] {
|
||||||
@@ -305,11 +321,11 @@ func (ps *provisionService) updateGateway(ctx context.Context, domainID, token s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
gw.ExternalID = bs.ExternalID
|
gw.ExternalID = bs.ExternalID
|
||||||
gw.ExternalKey = bs.ExternalKey
|
gw.ExternalKey = externalKey
|
||||||
gw.CfgID = bs.ClientID
|
gw.CfgID = bs.ID
|
||||||
gw.Type = gateway
|
gw.Type = gateway
|
||||||
|
|
||||||
c, sdkerr := ps.sdk.Client(ctx, bs.ClientID, domainID, token)
|
c, sdkerr := ps.sdk.Client(ctx, gatewayClientID, domainID, token)
|
||||||
if sdkerr != nil {
|
if sdkerr != nil {
|
||||||
return errors.Wrap(ErrGatewayUpdate, sdkerr)
|
return errors.Wrap(ErrGatewayUpdate, sdkerr)
|
||||||
}
|
}
|
||||||
@@ -326,6 +342,95 @@ func (ps *provisionService) updateGateway(ctx context.Context, domainID, token s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ps *provisionService) bootstrapRenderContext(externalID, name string) map[string]any {
|
||||||
|
renderContext := make(map[string]any, len(ps.conf.Bootstrap.RenderContext)+2)
|
||||||
|
for k, v := range ps.conf.Bootstrap.RenderContext {
|
||||||
|
renderContext[k] = v
|
||||||
|
}
|
||||||
|
if externalID != "" {
|
||||||
|
renderContext[externalIDKey] = externalID
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
renderContext["name"] = name
|
||||||
|
}
|
||||||
|
if len(renderContext) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return renderContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *provisionService) bindBootstrapResources(ctx context.Context, configID string, bootstrapClient smqSDK.Client, clients []smqSDK.Client, channels []smqSDK.Channel, domainID, token string) error {
|
||||||
|
if len(ps.conf.Bootstrap.Bindings) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
requests := make([]smqSDK.BootstrapBindingRequest, 0, len(ps.conf.Bootstrap.Bindings))
|
||||||
|
for _, binding := range ps.conf.Bootstrap.Bindings {
|
||||||
|
resourceID := ps.bindingResourceID(binding, bootstrapClient, clients, channels)
|
||||||
|
if resourceID == "" {
|
||||||
|
return fmt.Errorf("resource for bootstrap binding slot %q not found", binding.Slot)
|
||||||
|
}
|
||||||
|
requests = append(requests, smqSDK.BootstrapBindingRequest{
|
||||||
|
Slot: binding.Slot,
|
||||||
|
Type: binding.Type,
|
||||||
|
ResourceID: resourceID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps.sdk.BindBootstrapResources(ctx, configID, requests, domainID, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *provisionService) bindingResourceID(binding BootstrapBinding, bootstrapClient smqSDK.Client, clients []smqSDK.Client, channels []smqSDK.Channel) string {
|
||||||
|
switch binding.Type {
|
||||||
|
case "client":
|
||||||
|
if matchesClientBinding(binding, bootstrapClient) {
|
||||||
|
return bootstrapClient.ID
|
||||||
|
}
|
||||||
|
for _, client := range clients {
|
||||||
|
if matchesClientBinding(binding, client) {
|
||||||
|
return client.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "channel":
|
||||||
|
for _, channel := range channels {
|
||||||
|
if matchesChannelBinding(binding, channel) {
|
||||||
|
return channel.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesClientBinding(binding BootstrapBinding, client smqSDK.Client) bool {
|
||||||
|
if binding.Name != "" && client.Name != binding.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if binding.MetadataKey != "" {
|
||||||
|
return metadataValue(client.Metadata, binding.MetadataKey) == binding.MetadataValue
|
||||||
|
}
|
||||||
|
return binding.Name != "" || client.ID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesChannelBinding(binding BootstrapBinding, channel smqSDK.Channel) bool {
|
||||||
|
if binding.Name != "" && channel.Name != binding.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if binding.MetadataKey != "" {
|
||||||
|
return metadataValue(channel.Metadata, binding.MetadataKey) == binding.MetadataValue
|
||||||
|
}
|
||||||
|
return binding.Name != "" || channel.ID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func metadataValue(metadata map[string]any, key string) string {
|
||||||
|
if metadata == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if value, ok := metadata[key]; ok {
|
||||||
|
return fmt.Sprint(value)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (ps *provisionService) errLog(err error) {
|
func (ps *provisionService) errLog(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ps.logger.Error(fmt.Sprintf("Error recovering: %s", err))
|
ps.logger.Error(fmt.Sprintf("Error recovering: %s", err))
|
||||||
@@ -343,11 +448,17 @@ func clean(ctx context.Context, ps *provisionService, clients []smqSDK.Client, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *provisionService) recover(ctx context.Context, e *error, ths *[]smqSDK.Client, chs *[]smqSDK.Channel, domainID, token string) {
|
func (ps *provisionService) removeBootstraps(ctx context.Context, ids []string, domainID, token string) {
|
||||||
|
for _, id := range ids {
|
||||||
|
ps.errLog(ps.sdk.RemoveBootstrap(ctx, id, domainID, token))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *provisionService) recover(ctx context.Context, e *error, ths *[]smqSDK.Client, chs *[]smqSDK.Channel, bootstrapIDs *[]string, domainID, token string) {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clients, channels, err := *ths, *chs, *e
|
clients, channels, bootstraps, err := *ths, *chs, *bootstrapIDs, *e
|
||||||
|
|
||||||
if errors.Contains(err, ErrFailedClientRetrieval) || errors.Contains(err, ErrFailedChannelCreation) {
|
if errors.Contains(err, ErrFailedClientRetrieval) || errors.Contains(err, ErrFailedChannelCreation) {
|
||||||
for _, c := range clients {
|
for _, c := range clients {
|
||||||
@@ -362,27 +473,12 @@ func (ps *provisionService) recover(ctx context.Context, e *error, ths *[]smqSDK
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Contains(err, ErrFailedBootstrapValidate) || errors.Contains(err, ErrFailedCertCreation) {
|
if errors.Contains(err, ErrFailedBootstrapValidate) || errors.Contains(err, ErrFailedCertCreation) || errors.Contains(err, ErrFailedBootstrapBinding) {
|
||||||
clean(ctx, ps, clients, channels, domainID, token)
|
clean(ctx, ps, clients, channels, domainID, token)
|
||||||
for _, c := range clients {
|
ps.removeBootstraps(ctx, bootstraps, domainID, token)
|
||||||
if needsBootstrap(c) {
|
|
||||||
ps.errLog(ps.sdk.RemoveBootstrap(ctx, c.ID, domainID, token))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Contains(err, ErrFailedBootstrapValidate) || errors.Contains(err, ErrFailedCertCreation) {
|
|
||||||
clean(ctx, ps, clients, channels, domainID, token)
|
|
||||||
for _, c := range clients {
|
|
||||||
if needsBootstrap(c) {
|
|
||||||
bs, err := ps.sdk.ViewBootstrap(ctx, c.ID, domainID, token)
|
|
||||||
ps.errLog(errors.Wrap(ErrFailedBootstrapRetrieval, err))
|
|
||||||
ps.errLog(ps.sdk.RemoveBootstrap(ctx, bs.ClientID, domainID, token))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Contains(err, ErrClientUpdate) || errors.Contains(err, ErrGatewayUpdate) {
|
if errors.Contains(err, ErrClientUpdate) || errors.Contains(err, ErrGatewayUpdate) {
|
||||||
clean(ctx, ps, clients, channels, domainID, token)
|
clean(ctx, ps, clients, channels, domainID, token)
|
||||||
for _, c := range clients {
|
for _, c := range clients {
|
||||||
@@ -390,12 +486,8 @@ func (ps *provisionService) recover(ctx context.Context, e *error, ths *[]smqSDK
|
|||||||
err := ps.sdk.RevokeCert(ctx, c.ID, domainID, token)
|
err := ps.sdk.RevokeCert(ctx, c.ID, domainID, token)
|
||||||
ps.errLog(err)
|
ps.errLog(err)
|
||||||
}
|
}
|
||||||
if needsBootstrap(c) {
|
|
||||||
bs, err := ps.sdk.ViewBootstrap(ctx, c.ID, domainID, token)
|
|
||||||
ps.errLog(errors.Wrap(ErrFailedBootstrapRetrieval, err))
|
|
||||||
ps.errLog(ps.sdk.RemoveBootstrap(ctx, bs.ClientID, domainID, token))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ps.removeBootstraps(ctx, bootstraps, domainID, token)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,3 +231,219 @@ func TestCert(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProvisionUsesBootstrapEnrollmentID(t *testing.T) {
|
||||||
|
cfg := validConfig
|
||||||
|
cfg.Bootstrap = provision.Bootstrap{
|
||||||
|
X509Provision: true,
|
||||||
|
Provision: true,
|
||||||
|
AutoWhiteList: true,
|
||||||
|
ProfileID: "gateway-profile",
|
||||||
|
RenderContext: map[string]any{
|
||||||
|
"site": "warehouse-1",
|
||||||
|
},
|
||||||
|
Bindings: []provision.BootstrapBinding{
|
||||||
|
{
|
||||||
|
Slot: "mqtt_client",
|
||||||
|
Type: "client",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Slot: "control",
|
||||||
|
Type: "channel",
|
||||||
|
MetadataKey: "type",
|
||||||
|
MetadataValue: "control",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Content: map[string]any{
|
||||||
|
"broker": "mqtt://localhost:1883",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cfg.Clients[0].Metadata = map[string]any{
|
||||||
|
"external_id": "placeholder",
|
||||||
|
}
|
||||||
|
cfg.Channels[0].Name = "control-channel"
|
||||||
|
cfg.Channels[0].Metadata = map[string]any{
|
||||||
|
"type": "control",
|
||||||
|
}
|
||||||
|
cfg.Cert.TTL = "1h"
|
||||||
|
|
||||||
|
const (
|
||||||
|
name = "gateway-1"
|
||||||
|
externalID = "AA:BB:CC:DD"
|
||||||
|
externalKey = "secret"
|
||||||
|
certPEM = "cert-pem"
|
||||||
|
keyPEM = "key-pem"
|
||||||
|
serial = "serial-1"
|
||||||
|
)
|
||||||
|
|
||||||
|
clientID := testsutil.GenerateUUID(t)
|
||||||
|
channelID := testsutil.GenerateUUID(t)
|
||||||
|
bootstrapID := testsutil.GenerateUUID(t)
|
||||||
|
domainID := testsutil.GenerateUUID(t)
|
||||||
|
|
||||||
|
clientMetadata := map[string]any{
|
||||||
|
"external_id": externalID,
|
||||||
|
}
|
||||||
|
var updatedClient smqSDK.Client
|
||||||
|
|
||||||
|
mgsdk := new(sdkmocks.SDK)
|
||||||
|
svc := provision.New(cfg, mgsdk, mglog.NewMock())
|
||||||
|
|
||||||
|
createClientCall := mgsdk.On(
|
||||||
|
"CreateClient",
|
||||||
|
mock.Anything,
|
||||||
|
mock.Anything,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(smqSDK.Client{ID: clientID}, nil)
|
||||||
|
|
||||||
|
clientCall := mgsdk.On(
|
||||||
|
"Client",
|
||||||
|
mock.Anything,
|
||||||
|
clientID,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(smqSDK.Client{ID: clientID, Name: name, Metadata: clientMetadata}, nil).Twice()
|
||||||
|
|
||||||
|
createChannelCall := mgsdk.On(
|
||||||
|
"CreateChannel",
|
||||||
|
mock.Anything,
|
||||||
|
mock.Anything,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(smqSDK.Channel{ID: channelID}, nil)
|
||||||
|
|
||||||
|
channelCall := mgsdk.On(
|
||||||
|
"Channel",
|
||||||
|
mock.Anything,
|
||||||
|
channelID,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(smqSDK.Channel{ID: channelID, Metadata: smqSDK.Metadata{"type": "control"}}, nil)
|
||||||
|
|
||||||
|
addBootstrapCall := mgsdk.On(
|
||||||
|
"AddBootstrap",
|
||||||
|
mock.Anything,
|
||||||
|
mock.MatchedBy(func(cfg smqSDK.BootstrapConfig) bool {
|
||||||
|
return cfg.ProfileID == "gateway-profile" &&
|
||||||
|
cfg.RenderContext["site"] == "warehouse-1" &&
|
||||||
|
cfg.RenderContext["external_id"] == externalID &&
|
||||||
|
cfg.RenderContext["name"] == name
|
||||||
|
}),
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(bootstrapID, nil)
|
||||||
|
|
||||||
|
viewBootstrapCall := mgsdk.On(
|
||||||
|
"ViewBootstrap",
|
||||||
|
mock.Anything,
|
||||||
|
bootstrapID,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(smqSDK.BootstrapConfig{
|
||||||
|
ID: bootstrapID,
|
||||||
|
ExternalID: externalID,
|
||||||
|
ExternalKey: externalKey,
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
bindBootstrapResourcesCall := mgsdk.On(
|
||||||
|
"BindBootstrapResources",
|
||||||
|
mock.Anything,
|
||||||
|
bootstrapID,
|
||||||
|
[]smqSDK.BootstrapBindingRequest{
|
||||||
|
{
|
||||||
|
Slot: "mqtt_client",
|
||||||
|
Type: "client",
|
||||||
|
ResourceID: clientID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Slot: "control",
|
||||||
|
Type: "channel",
|
||||||
|
ResourceID: channelID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(nil)
|
||||||
|
|
||||||
|
issueCertCall := mgsdk.On(
|
||||||
|
"IssueCert",
|
||||||
|
mock.Anything,
|
||||||
|
clientID,
|
||||||
|
cfg.Cert.TTL,
|
||||||
|
mock.Anything,
|
||||||
|
mock.Anything,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(smqSDK.Certificate{SerialNumber: serial}, nil)
|
||||||
|
|
||||||
|
viewCertCall := mgsdk.On(
|
||||||
|
"ViewCert",
|
||||||
|
mock.Anything,
|
||||||
|
serial,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(smqSDK.Certificate{Certificate: certPEM, Key: keyPEM}, nil)
|
||||||
|
|
||||||
|
updateBootstrapCertsCall := mgsdk.On(
|
||||||
|
"UpdateBootstrapCerts",
|
||||||
|
mock.Anything,
|
||||||
|
bootstrapID,
|
||||||
|
certPEM,
|
||||||
|
keyPEM,
|
||||||
|
"",
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(smqSDK.BootstrapConfig{
|
||||||
|
ID: bootstrapID,
|
||||||
|
ExternalID: externalID,
|
||||||
|
ExternalKey: externalKey,
|
||||||
|
ClientCert: certPEM,
|
||||||
|
ClientKey: keyPEM,
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
whitelistCall := mgsdk.On(
|
||||||
|
"Whitelist",
|
||||||
|
mock.Anything,
|
||||||
|
bootstrapID,
|
||||||
|
smqSDK.BootstrapEnabledStatus,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Return(nil)
|
||||||
|
|
||||||
|
updateClientCall := mgsdk.On(
|
||||||
|
"UpdateClient",
|
||||||
|
mock.Anything,
|
||||||
|
mock.Anything,
|
||||||
|
domainID,
|
||||||
|
validToken,
|
||||||
|
).Run(func(args mock.Arguments) {
|
||||||
|
updatedClient = args.Get(1).(smqSDK.Client)
|
||||||
|
}).Return(smqSDK.Client{ID: clientID}, nil)
|
||||||
|
|
||||||
|
res, err := svc.Provision(context.Background(), domainID, validToken, name, externalID, externalKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, res.Clients, 1)
|
||||||
|
assert.Len(t, res.Channels, 1)
|
||||||
|
assert.True(t, res.Whitelisted[bootstrapID])
|
||||||
|
assert.Equal(t, certPEM, res.ClientCert[clientID])
|
||||||
|
assert.Equal(t, keyPEM, res.ClientKey[clientID])
|
||||||
|
assert.Equal(t, clientID, updatedClient.ID)
|
||||||
|
assert.Equal(t, bootstrapID, updatedClient.Metadata["cfg_id"])
|
||||||
|
assert.Equal(t, externalID, updatedClient.Metadata["external_id"])
|
||||||
|
assert.Equal(t, channelID, updatedClient.Metadata["ctrl_channel_id"])
|
||||||
|
assert.Equal(t, "gateway", updatedClient.Metadata["type"])
|
||||||
|
|
||||||
|
createClientCall.Unset()
|
||||||
|
clientCall.Unset()
|
||||||
|
createChannelCall.Unset()
|
||||||
|
channelCall.Unset()
|
||||||
|
_ = addBootstrapCall
|
||||||
|
viewBootstrapCall.Unset()
|
||||||
|
bindBootstrapResourcesCall.Unset()
|
||||||
|
issueCertCall.Unset()
|
||||||
|
viewCertCall.Unset()
|
||||||
|
updateBootstrapCertsCall.Unset()
|
||||||
|
whitelistCall.Unset()
|
||||||
|
updateClientCall.Unset()
|
||||||
|
}
|
||||||
|
|||||||
+36
-4
@@ -491,9 +491,9 @@ func TestAddRule(t *testing.T) {
|
|||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
repoCall := repo.On("AddRule", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
repoCall := repo.On("AddRule", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||||
policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesErr)
|
policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesErr)
|
||||||
policyCall2 := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePolicies).Maybe()
|
policyCall2 := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePolicies)
|
||||||
repoCall1 := repo.On("AddRoles", context.Background(), mock.Anything).Return([]roles.RoleProvision{}, tc.addRoleErr)
|
repoCall1 := repo.On("AddRoles", context.Background(), mock.Anything).Return([]roles.RoleProvision{}, tc.addRoleErr)
|
||||||
repoCall2 := repo.On("Remove", context.Background(), mock.Anything).Return(tc.deleteErr).Maybe()
|
repoCall2 := repo.On("Remove", context.Background(), mock.Anything).Return(tc.deleteErr)
|
||||||
res, _, err := svc.AddRule(context.Background(), tc.session, tc.rule)
|
res, _, err := svc.AddRule(context.Background(), tc.session, tc.rule)
|
||||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -1175,6 +1175,7 @@ func TestHandle(t *testing.T) {
|
|||||||
page re.Page
|
page re.Page
|
||||||
listErr error
|
listErr error
|
||||||
publishErr error
|
publishErr error
|
||||||
|
emailErr error
|
||||||
expectErr bool
|
expectErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -1420,6 +1421,37 @@ func TestHandle(t *testing.T) {
|
|||||||
},
|
},
|
||||||
listErr: nil,
|
listErr: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "consume message with Lua script and failed Email output",
|
||||||
|
message: &messaging.Message{
|
||||||
|
Channel: inputChannel,
|
||||||
|
Created: now.Unix(),
|
||||||
|
Payload: []byte(`{"temperature": 25.5}`),
|
||||||
|
},
|
||||||
|
page: re.Page{
|
||||||
|
Rules: []re.Rule{
|
||||||
|
{
|
||||||
|
ID: testsutil.GenerateUUID(t),
|
||||||
|
Name: namegen.Generate(),
|
||||||
|
InputChannel: inputChannel,
|
||||||
|
Status: re.EnabledStatus,
|
||||||
|
Logic: re.Script{
|
||||||
|
Type: re.LuaType,
|
||||||
|
Value: `return message.payload`,
|
||||||
|
},
|
||||||
|
Outputs: re.Outputs{
|
||||||
|
&outputs.Email{
|
||||||
|
To: []string{"test@example.com"},
|
||||||
|
Subject: "Temperature Alert",
|
||||||
|
Content: "Temperature: {{.Result}}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Schedule: schedule,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emailErr: errors.New("failed to send email"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "consume message with rules using GoType",
|
desc: "consume message with rules using GoType",
|
||||||
message: &messaging.Message{
|
message: &messaging.Message{
|
||||||
@@ -1817,8 +1849,8 @@ func TestHandle(t *testing.T) {
|
|||||||
err = tc.listErr
|
err = tc.listErr
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
repoCall1 := pubmocks.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(tc.publishErr).Maybe()
|
repoCall1 := pubmocks.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(tc.publishErr)
|
||||||
repoCall2 := emailer.On("SendEmailNotification", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
|
repoCall2 := emailer.On("SendEmailNotification", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.emailErr)
|
||||||
|
|
||||||
err = svc.Handle(tc.message)
|
err = svc.Handle(tc.message)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|||||||
@@ -184,9 +184,9 @@ func TestAddReportConfig(t *testing.T) {
|
|||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
repoCall := repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
repoCall := repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||||
policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesErr)
|
policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesErr)
|
||||||
policyCall2 := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePolicies).Maybe()
|
policyCall2 := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePolicies)
|
||||||
repoCall1 := repo.On("AddRoles", context.Background(), mock.Anything).Return([]roles.RoleProvision{}, tc.addRoleErr)
|
repoCall1 := repo.On("AddRoles", context.Background(), mock.Anything).Return([]roles.RoleProvision{}, tc.addRoleErr)
|
||||||
repoCall2 := repo.On("Remove", context.Background(), mock.Anything).Return(tc.deleteErr).Maybe()
|
repoCall2 := repo.On("Remove", context.Background(), mock.Anything).Return(tc.deleteErr)
|
||||||
res, err := svc.AddReportConfig(context.Background(), tc.session, tc.cfg)
|
res, err := svc.AddReportConfig(context.Background(), tc.session, tc.cfg)
|
||||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -173,6 +173,10 @@ packages:
|
|||||||
ConfigRepository:
|
ConfigRepository:
|
||||||
ConfigReader:
|
ConfigReader:
|
||||||
Service:
|
Service:
|
||||||
|
ProfileRepository:
|
||||||
|
BindingStore:
|
||||||
|
BindingResolver:
|
||||||
|
Renderer:
|
||||||
github.com/absmach/magistrala/provision:
|
github.com/absmach/magistrala/provision:
|
||||||
interfaces:
|
interfaces:
|
||||||
Service:
|
Service:
|
||||||
|
|||||||
+14
-1
@@ -2082,6 +2082,7 @@ func TestOAuthCallback(t *testing.T) {
|
|||||||
retrieveByEmailErr error
|
retrieveByEmailErr error
|
||||||
saveResponse users.User
|
saveResponse users.User
|
||||||
addPoliciesErr error
|
addPoliciesErr error
|
||||||
|
updateVerifiedAtErr error
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -2136,6 +2137,18 @@ func TestOAuthCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "oauth signin callback with failed update verified at",
|
||||||
|
user: users.User{
|
||||||
|
Email: "test@example.com",
|
||||||
|
},
|
||||||
|
retrieveByEmailResponse: users.User{
|
||||||
|
ID: testsutil.GenerateUUID(t),
|
||||||
|
Role: users.UserRole,
|
||||||
|
},
|
||||||
|
updateVerifiedAtErr: svcerr.ErrUpdateEntity,
|
||||||
|
err: svcerr.ErrUpdateEntity,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
@@ -2144,7 +2157,7 @@ func TestOAuthCallback(t *testing.T) {
|
|||||||
repoCall2 := cRepo.On("UpdateVerifiedAt", context.Background(), mock.MatchedBy(func(u users.User) bool {
|
repoCall2 := cRepo.On("UpdateVerifiedAt", context.Background(), mock.MatchedBy(func(u users.User) bool {
|
||||||
assert.NotEmpty(t, u.ID, "UpdateVerifiedAt must be called with non-empty user ID")
|
assert.NotEmpty(t, u.ID, "UpdateVerifiedAt must be called with non-empty user ID")
|
||||||
return u.ID != ""
|
return u.ID != ""
|
||||||
})).Maybe().Return(tc.retrieveByEmailResponse, nil)
|
})).Return(tc.retrieveByEmailResponse, tc.updateVerifiedAtErr)
|
||||||
policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesErr)
|
policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesErr)
|
||||||
_, err := svc.OAuthCallback(context.Background(), tc.user)
|
_, err := svc.OAuthCallback(context.Background(), tc.user)
|
||||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||||
|
|||||||
Reference in New Issue
Block a user