Files
magistrala/bootstrap/postgres/bindings.go
T
Steve Munene 7f03134d8e
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
NOISSUE - Update bootstrap and provision service (#3476)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: JeffMboya <jangina.mboya@gmail.com>
Co-authored-by: JeffMboya <jangina.mboya@gmail.com>
2026-05-08 10:35:00 +02:00

147 lines
4.4 KiB
Go

// 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
}