42 Commits

Author SHA1 Message Date
mr
e03a0d3dd0 OR 2026-03-04 15:57:47 +01:00
mr
340f2a6301 OR missing 2026-03-04 15:39:17 +01:00
mr
a426bdf655 isDraft 2026-03-04 13:54:43 +01:00
mr
2bfcfb5736 New 2026-03-04 13:51:43 +01:00
mr
5d18512f67 models 2026-03-04 13:43:14 +01:00
mr
66ee4156e2 is_draft 2026-03-04 13:37:27 +01:00
mr
f1eaf497aa resource as draft for update 2026-03-04 13:31:05 +01:00
mr
b47b51126a by pass temp 2026-03-04 13:15:01 +01:00
mr
473dc62660 decode 2026-03-04 13:02:53 +01:00
mr
334de8ca2e err = res_mongo.Decode(&data); err != nil 2026-03-04 12:43:47 +01:00
mr
ae7e297622 loadone catch error 2026-03-04 12:40:06 +01:00
mr
3e0f369850 prospect 2026-03-04 12:34:11 +01:00
mr
6217618e6c a.Type 2026-03-04 12:22:54 +01:00
mr
f033182382 Apply 2026-03-04 12:18:13 +01:00
mr
542b0b73ab getmyself 2026-03-02 16:24:14 +01:00
mr
44812309db Update try 2026-03-02 15:46:05 +01:00
mr
cb3771c17a reverse VARs 2026-02-26 10:12:17 +01:00
mr
f4e2d8057d crypto 2026-02-26 09:57:54 +01:00
mr
959fce48ef try -> crypto adjust 2026-02-26 09:48:51 +01:00
mr
ce8ef70516 offical pb for remote config 2026-02-24 14:08:21 +01:00
mr
d18b031a29 WorkflowResource 2026-02-24 13:29:00 +01:00
mr
0f6aa1fe78 ARGO_KUBE_EVENT 2026-02-24 13:00:19 +01:00
mr
a9ebad78f3 kube 2026-02-24 10:36:10 +01:00
mr
54aef164ba kubernetes lib 2026-02-24 10:29:28 +01:00
mr
ff830065ec set up 2026-02-23 17:26:37 +01:00
mr
e039fa56b6 execution_id 2026-02-23 15:56:40 +01:00
mr
e10bb55455 p.SetID(uuid.NewString()) 2026-02-23 15:50:10 +01:00
mr
f28e2c3620 State in WF 2026-02-23 15:41:48 +01:00
mr
b08bbf51dd priced.GetInstanceID() 2026-02-23 15:22:48 +01:00
mr
5d32b4646a Add InstanceID 2026-02-23 15:18:27 +01:00
mr
25e4e67111 missing Instance hit per Purchase +Booking 2026-02-23 15:08:27 +01:00
mr
19b0f10e71 adjust self 2026-02-23 14:07:39 +01:00
mr
12c506e9a7 Native Schedule 2026-02-23 10:23:55 +01:00
mr
2871353635 close planner 2026-02-23 09:30:03 +01:00
mr
59923ac5c1 Missing action 2026-02-23 09:25:38 +01:00
mr
da8b8ec397 Planner 2026-02-23 09:20:00 +01:00
mr
9afbbb5c82 Planner Improve 2026-02-23 08:38:43 +01:00
mr
9662ac6d67 Add New 2026-02-19 09:43:44 +01:00
mr
0b41e2505e Nats Native Behaviors + Peer is Stateless 2026-02-18 14:25:56 +01:00
mr
fa5c3a3c60 Adjust + Test 2026-02-18 12:24:19 +01:00
mr
842e09f22f by pass pricing profile need 2026-02-17 10:02:44 +01:00
mr
403913d8cf new oclib match 2026-02-12 13:39:52 +01:00
62 changed files with 2911 additions and 1480 deletions

190
UNUSED_AND_ISSUES.md Normal file
View File

@@ -0,0 +1,190 @@
# Rapport d'audit Éléments inutilisés et problèmes identifiés
> Généré le 2026-02-18 branche `feature/event`
---
## 1. Bugs critiques corrigés dans cette session
| Fichier | Ligne | Description | Statut |
|---------|-------|-------------|--------|
| `entrypoint.go` | 652, 664, 676, 688 | `fmt.Errorf(res.Err)` format string non-constant (erreur de build) | Corrigé |
| `models/utils/abstracts.go` | 136 | `VerifyAuth` déréférençait `request.Admin` avant de vérifier `request != nil` | Corrigé |
| `models/utils/abstracts.go` | 68-78 | `DeepCopy()` faisait `Unmarshal` dans un pointeur nil retournait toujours `nil` | Corrigé |
| `models/resources/resource.go` | 176 | `instances = append(instances)` argument manquant, l'instance n'était jamais ajoutée | Corrigé |
| `models/resources/priced_resource.go` | 63-69 | Code mort après `return true` dans `IsBooked()` | Corrigé |
| `tools/remote_caller.go` | 118 | `CallDelete` vérifiait `req.Body == nil` (toujours vrai pour DELETE), court-circuitant la lecture de la réponse | Corrigé |
---
## 2. Debug prints à supprimer (fmt.Println en production)
Ces appels `fmt.Println` polluent stdout et peuvent exposer des informations sensibles.
| Fichier | Lignes | Contenu |
|---------|--------|---------|
| `models/bill/bill.go` | ~197 | `fmt.Println(err)` |
| `models/collaborative_area/collaborative_area_mongo_accessor.go` | ~95, 109, 118, 123 | Debug sur `res`, `sharedWorkspace.AllowedPeersGroup`, `canFound`, `peerskey` |
| `models/peer/peer_cache.go` | ~44, 55 | URL et `"Launching peer execution on..."` |
| `models/resources/storage.go` | ~196 | `fmt.Println("GetPriceHT", ...)` |
| `models/workflow/workflow.go` | ~158, 164, 170, 176 | 4× `fmt.Println(err)` |
| `tools/nats_caller.go` | ~110, 117, 122, 126 | 4× `fmt.Println()` divers |
| `tools/remote_caller.go` | 227 | `fmt.Println("Error reading the body...")` (devrait utiliser le logger) |
| `dbs/dbs.go` | 47 | `fmt.Println("Recovered. Error:\n", r, debug.Stack())` |
> **Note :** `priced_resource.go` et `data.go` corrigés dans cette session.
---
## 3. Code commenté significatif
### 3.1 Validation de pricing désactivée (workflow)
**Fichier :** `models/workflow/workflow.go` ~lignes 631-634
```go
// Should be commented once the Pricing selection feature has been implemented
// if priced.SelectPricing() == nil {
// return resources, priceds, errors.New("no pricings are selected... can't proceed")
// }
```
Une vérification de sécurité critique est désactivée. Sans elle, des ressources sans pricing peuvent être traitées silencieusement.
### 3.2 PAY_PER_USE stratégie supprimée mais traces restantes
**Fichier :** `models/common/pricing/pricing_strategy.go` lignes 47, 61-63
```go
// PAY_PER_USE // per request. ( unpredictible )
/*case PAY_PER_USE:
return bs, true*/
```
La constante `PAY_PER_USE` a été supprimée mais les commentaires laissés créent de la confusion.
### 3.3 Vérification d'autorisation peer désactivée
**Fichier :** `models/resources/resource.go` lignes 98-104
```go
/*if ok, _ := utils.IsMySelf(request.PeerID, ...); ok {*/
profile = pricing.GetDefaultPricingProfile()
/*} else {
return nil, errors.New("no pricing profile found")
}*/
```
Le profil par défaut est retourné sans vérifier si le pair est bien `myself`. Sécurité à revoir.
---
## 4. Logique erronée non corrigée (à traiter)
### 4.1 IsTimeStrategy logique inversée
**Fichier :** `models/common/pricing/pricing_strategy.go` ligne 88
```go
func IsTimeStrategy(i int) bool {
return len(TimePricingStrategyList()) < i // BUG: devrait être ">"
}
```
La condition est inversée. Retourne `true` pour des valeurs hors de la liste. Fonction actuellement non utilisée (voir §5).
### 4.2 IsBillingStrategyAllowed case SUBSCRIPTION sans retour
**Fichier :** `models/common/pricing/pricing_strategy.go` lignes 54-65
```go
case SUBSCRIPTION:
/*case PAY_PER_USE:
return bs, true*/
// Aucun return ici → tombe dans le default
```
Le cas `SUBSCRIPTION` ne retourne rien explicitement, ce qui est trompeur.
---
## 5. Éléments inutilisés
### 5.1 Fonction jamais appelée
| Symbole | Fichier | Ligne |
|---------|---------|-------|
| `IsTimeStrategy(i int) bool` | `models/common/pricing/pricing_strategy.go` | 88 |
De plus, cette fonction a une logique erronée (voir §4.1).
### 5.2 Variable singleton inutilisée
| Symbole | Fichier | Ligne |
|---------|---------|-------|
| `HTTPCallerInstance` | `tools/remote_caller.go` | 57 |
Déclarée comme singleton mais jamais utilisée de nouvelles instances sont créées via `NewHTTPCaller()`.
---
## 6. Tests supprimés (couverture perdue)
Les fichiers suivants ont été supprimés sur la branche `feature/event` et la couverture correspondante n'est plus assurée :
| Fichier supprimé | Modèles non couverts |
|------------------|----------------------|
| `models/peer/tests/peer_cache_test.go` | `PeerCache` logique d'exécution distribuée |
| `models/peer/tests/peer_test.go` | `Peer` modèle et accesseur |
| `models/utils/tests/abstracts_test.go` | `AbstractObject` méthodes de base |
| `models/utils/tests/common_test.go` | `GenericStoreOne`, `GenericDeleteOne`, etc. |
| `models/workflow_execution/tests/workflow_test.go` | `WorkflowExecution` modèle et accesseur |
> `models/order/tests/order_test.go` existe mais ne contient **aucune fonction de test**.
---
## 7. Fautes d'orthographe dans les identifiants publics
Ces typos sont dans des noms exportés (API publique) les corriger est un **breaking change**.
### 7.1 `Instanciated` `Instantiated`
Apparaît 50+ fois dans les types exportés centraux :
- `AbstractInstanciatedResource[T]` (resource.go, compute.go, data.go, storage.go, processing.go, workflow.go)
- `AbstractInstanciatedResource.Instances`
- Tests : `resources.AbstractInstanciatedResource[*MockInstance]{...}`
### 7.2 `ressource` `resource` (dans les messages d'erreur)
**Fichier :** `entrypoint.go` messages dans `LoadOneStorage`, `LoadOneComputing`, etc.
```go
"Error while loading storage ressource " + storageId // "ressource" est du français
```
### 7.3 `GARANTED` `GUARANTEED`
**Fichiers :** `models/common/pricing/pricing_profile.go`, `models/resources/storage.go`
```go
GARANTED_ON_DELAY // pricing_profile.go:72
GARANTED // pricing_profile.go:73
GARANTED_ON_DELAY_STORAGE // storage.go:106
GARANTED_STORAGE // storage.go:107
```
### 7.4 `CREATE_EXECTUTION` `CREATE_EXECUTION`
**Fichier :** `tools/nats_caller.go` ligne 34
```go
CREATE_EXECTUTION // faute de frappe dans la constante enum
```
### 7.5 `PROPALGATION` `PROPAGATION`
**Fichier :** `tools/nats_caller.go` lignes 29, 45, 56
```go
"propalgation event" // et PROPALGATION_EVENT
```
---
## 8. Incohérences de nommage mineures
| Fichier | Problème |
|---------|----------|
| `models/resources/interfaces.go:19` | Paramètre `instance_id` en snake_case dans une signature Go (devrait être `instanceID`) |
| `entrypoint.go:505` | Message de panique dans `CopyOne` dit `"Panic recovered in UpdateOne"` |
| `tools/remote_caller.go:110` | Commentaire `// CallPut calls the DELETE method` (copie-colle incorrect) |
---
## 9. Résumé
| Catégorie | Nombre | Priorité |
|-----------|--------|----------|
| Bugs critiques corrigés | 6 | Fait |
| Debug `fmt.Println` restants | 15+ | 🔴 Haute |
| Code commenté important | 3 | 🟠 Moyenne |
| Logique erronée (non corrigée) | 2 | 🟠 Moyenne |
| Éléments inutilisés | 2 | 🟡 Faible |
| Tests supprimés (couverture perdue) | 5 fichiers | 🟠 Moyenne |
| Typos dans API publique | 5 types | 🟡 Faible (breaking change) |
| Incohérences mineures | 3 | 🟢 Très faible |

View File

@@ -35,6 +35,7 @@ var str = [...]string{
"equal", "equal",
"not", "not",
"elemMatch", "elemMatch",
"or",
} }
func (m Operator) String() string { func (m Operator) String() string {
@@ -47,30 +48,30 @@ func (m Operator) ToMongoOperator(k string, value interface{}) bson.M {
fmt.Println("Recovered. Error:\n", r, debug.Stack()) fmt.Println("Recovered. Error:\n", r, debug.Stack())
} }
}() }()
defaultValue := bson.M{k: bson.M{"$regex": m.ToValueOperator(StringToOperator(m.String()), value)}} defaultValue := bson.M{k: bson.M{"$regex": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
switch m { switch m {
case LIKE: case LIKE:
return bson.M{k: bson.M{"$regex": m.ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$regex": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
case EXISTS: case EXISTS:
return bson.M{k: bson.M{"$exists": m.ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$exists": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
case IN: case IN:
return bson.M{k: bson.M{"$in": m.ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$in": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
case GTE: case GTE:
return bson.M{k: bson.M{"$gte": m.ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$gte": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
case GT: case GT:
return bson.M{k: bson.M{"$gt": m.ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$gt": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
case LTE: case LTE:
return bson.M{k: bson.M{"$lte": m.ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$lte": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
case LT: case LT:
return bson.M{k: bson.M{"$lt": m.ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$lt": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
case ELEMMATCH: case ELEMMATCH:
return bson.M{k: bson.M{"$elemMatch": m.ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$elemMatch": m.ToValueOperator(StringToOperator(m.String()), value, false)}}
case EQUAL: case EQUAL:
return bson.M{k: value} return bson.M{k: value}
case NOT: case NOT:
return bson.M{"$not": m.ToValueOperator(StringToOperator(m.String()), value)} return bson.M{"$not": m.ToValueOperator(StringToOperator(m.String()), value, false)}
case OR: case OR:
return bson.M{"$or": m.ToValueOperator(StringToOperator(m.String()), value)} return bson.M{"$or": m.ToValueOperator(StringToOperator(m.String()), value, true)}
default: default:
return defaultValue return defaultValue
} }
@@ -112,10 +113,19 @@ func GetBson(filters *Filters) bson.D {
return f return f
} }
func (m Operator) ToValueOperator(operator Operator, value interface{}) interface{} { func (m Operator) ToValueOperator(operator Operator, value interface{}, or bool) interface{} {
switch value := value.(type) { switch value := value.(type) {
case *Filters: case *Filters:
return GetBson(value) bson := GetBson(value)
if or {
for _, b := range bson {
if b.Key == "$or" {
return b.Value
}
}
} else {
return bson
}
default: default:
if strings.TrimSpace(fmt.Sprintf("%v", value)) == "*" { if strings.TrimSpace(fmt.Sprintf("%v", value)) == "*" {
value = "" value = ""

View File

@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"slices"
"strings" "strings"
"runtime/debug" "runtime/debug"
@@ -34,6 +35,7 @@ import (
"github.com/beego/beego/v2/server/web/filter/cors" "github.com/beego/beego/v2/server/web/filter/cors"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/goraz/onion" "github.com/goraz/onion"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
@@ -63,8 +65,9 @@ const (
NATIVE_TOOL = tools.NATIVE_TOOL NATIVE_TOOL = tools.NATIVE_TOOL
) )
func GetMySelf() (string, error) { func GetMySelf() (*peer.Peer, error) {
return utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{Admin: true})) pp, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{Admin: true}))
return pp.(*peer.Peer), err
} }
func IsMySelf(peerID string) (bool, string) { func IsMySelf(peerID string) (bool, string) {
@@ -92,7 +95,7 @@ func GenerateNodeID() (string, error) {
// will turn into standards api hostnames // will turn into standards api hostnames
func (d LibDataEnum) API() string { func (d LibDataEnum) API() string {
return tools.DefaultAPI[d]() return tools.Str[d]
} }
// will turn into standards name // will turn into standards name
@@ -246,7 +249,7 @@ func GetLogger() zerolog.Logger {
* @return *Config * @return *Config
*/ */
func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string, func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string,
port int, pkpath string, pppath string, port int, pppath string, pkpath string,
internalCatalogAPI, internalSharedAPI, internalWorkflowAPI, internalWorkspaceAPI, internalPeerAPI, internalDatacenterAPI string) *config.Config { internalCatalogAPI, internalSharedAPI, internalWorkflowAPI, internalWorkspaceAPI, internalPeerAPI, internalDatacenterAPI string) *config.Config {
cfg := config.SetConfig(mongoUrl, database, natsUrl, lokiUrl, logLevel, port, pkpath, pppath, internalCatalogAPI, internalSharedAPI, internalWorkflowAPI, internalWorkspaceAPI, internalPeerAPI, internalDatacenterAPI) cfg := config.SetConfig(mongoUrl, database, natsUrl, lokiUrl, logLevel, port, pkpath, pppath, internalCatalogAPI, internalSharedAPI, internalWorkflowAPI, internalWorkspaceAPI, internalPeerAPI, internalDatacenterAPI)
defer func() { defer func() {
@@ -423,7 +426,7 @@ func (r *Request) UpdateOne(set map[string]interface{}, id string) (data LibData
PeerID: r.PeerID, PeerID: r.PeerID,
Groups: r.Groups, Groups: r.Groups,
Admin: r.admin, Admin: r.admin,
}).UpdateOne(model.Deserialize(set, model), id) }).UpdateOne(set, id)
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
return return
@@ -630,18 +633,7 @@ func (l *LibData) ToPurchasedResource() *purchase_resource.PurchaseResource {
return nil return nil
} }
// ============== ADMIRALTY ============== // ------------- Loading resources ----------GetAccessor
// Returns a concatenation of the peerId and namespace in order for
// kubernetes ressources to have a unique name, under 63 characters
// and yet identify which peer they are created for
func GetConcatenatedName(peerId string, namespace string) string {
s := strings.Split(namespace, "-")[:2]
n := s[0] + "-" + s[1]
return peerId + "-" + n
}
// ------------- Loading resources ----------
func LoadOneStorage(storageId string, user string, peerID string, groups []string) (*resources.StorageResource, error) { func LoadOneStorage(storageId string, user string, peerID string, groups []string) (*resources.StorageResource, error) {
@@ -649,7 +641,7 @@ func LoadOneStorage(storageId string, user string, peerID string, groups []strin
if res.Code != 200 { if res.Code != 200 {
l := GetLogger() l := GetLogger()
l.Error().Msg("Error while loading storage ressource " + storageId) l.Error().Msg("Error while loading storage ressource " + storageId)
return nil, fmt.Errorf(res.Err) return nil, errors.New(res.Err)
} }
return res.ToStorageResource(), nil return res.ToStorageResource(), nil
@@ -661,7 +653,7 @@ func LoadOneComputing(computingId string, user string, peerID string, groups []s
if res.Code != 200 { if res.Code != 200 {
l := GetLogger() l := GetLogger()
l.Error().Msg("Error while loading computing ressource " + computingId) l.Error().Msg("Error while loading computing ressource " + computingId)
return nil, fmt.Errorf(res.Err) return nil, errors.New(res.Err)
} }
return res.ToComputeResource(), nil return res.ToComputeResource(), nil
@@ -673,7 +665,7 @@ func LoadOneProcessing(processingId string, user string, peerID string, groups [
if res.Code != 200 { if res.Code != 200 {
l := GetLogger() l := GetLogger()
l.Error().Msg("Error while loading processing ressource " + processingId) l.Error().Msg("Error while loading processing ressource " + processingId)
return nil, fmt.Errorf(res.Err) return nil, errors.New(res.Err)
} }
return res.ToProcessingResource(), nil return res.ToProcessingResource(), nil
@@ -685,8 +677,81 @@ func LoadOneData(dataId string, user string, peerID string, groups []string) (*r
if res.Code != 200 { if res.Code != 200 {
l := GetLogger() l := GetLogger()
l.Error().Msg("Error while loading data ressource " + dataId) l.Error().Msg("Error while loading data ressource " + dataId)
return nil, fmt.Errorf(res.Err) return nil, errors.New(res.Err)
} }
return res.ToDataResource(), nil return res.ToDataResource(), nil
} }
// verify signature...
func InitNATSDecentralizedEmitter(authorizedDT ...tools.DataType) {
tools.NewNATSCaller().ListenNats(map[tools.NATSMethod]func(tools.NATSResponse){
tools.CREATE_RESOURCE: func(resp tools.NATSResponse) {
if resp.FromApp == config.GetAppName() || !slices.Contains(authorizedDT, resp.Datatype) {
return
}
p := map[string]interface{}{}
if err := json.Unmarshal(resp.Payload, &p); err == nil {
if err := verify(resp.Payload); err != nil {
return // don't trust anyone... only friends and foes are privilege
}
access := NewRequestAdmin(LibDataEnum(resp.Datatype), nil)
if data := access.Search(nil, fmt.Sprintf("%v", p[resp.SearchAttr]), false); len(data.Data) > 0 {
delete(p, "id")
access.UpdateOne(p, data.Data[0].GetID())
} else {
access.StoreOne(p)
}
}
},
tools.REMOVE_RESOURCE: func(resp tools.NATSResponse) {
if resp.FromApp == config.GetAppName() || !slices.Contains(authorizedDT, resp.Datatype) {
return
}
if err := verify(resp.Payload); err != nil {
return // don't trust anyone... only friends and foes are privilege
}
p := map[string]interface{}{}
access := NewRequestAdmin(LibDataEnum(resp.Datatype), nil)
err := json.Unmarshal(resp.Payload, &p)
if err == nil {
if data := access.Search(nil, fmt.Sprintf("%v", p[resp.SearchAttr]), false); len(data.Data) > 0 {
access.DeleteOne(fmt.Sprintf("%v", p[resp.SearchAttr]))
}
}
},
})
}
func verify(payload []byte) error {
var obj utils.AbstractObject
if err := json.Unmarshal(payload, &obj); err == nil {
obj.Unsign()
origin := NewRequestAdmin(LibDataEnum(PEER), nil).LoadOne(obj.GetCreatorID())
if origin.Data == nil || origin.Data.(*peer.Peer).Relation != peer.PARTNER {
return errors.New("don't know personnaly this guy") // don't trust anyone... only friends and foes are privilege
}
data, err := base64.StdEncoding.DecodeString(origin.Data.(*peer.Peer).PublicKey)
if err != nil {
return err
}
pk, err := crypto.UnmarshalPublicKey(data)
if err != nil {
return err
}
b, err := json.Marshal(obj)
if err != nil {
return err
}
if ok, err := pk.Verify(b, obj.GetSignature()); err != nil {
return err
} else if !ok {
return errors.New("signature is not corresponding to public key")
} else {
return nil
}
} else {
return err
}
}

50
go.mod
View File

@@ -1,29 +1,58 @@
module cloud.o-forge.io/core/oc-lib module cloud.o-forge.io/core/oc-lib
go 1.24.6 go 1.25.0
require ( require (
github.com/beego/beego/v2 v2.3.1 github.com/beego/beego/v2 v2.3.8
github.com/go-playground/validator/v10 v10.22.0 github.com/go-playground/validator/v10 v10.22.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/goraz/onion v0.1.3 github.com/goraz/onion v0.1.3
github.com/nats-io/nats.go v1.37.0 github.com/nats-io/nats.go v1.37.0
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
k8s.io/apimachinery v0.35.1
) )
require ( require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nkeys v0.4.7 // indirect
github.com/nats-io/nuid v1.0.1 // indirect github.com/nats-io/nuid v1.0.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/time v0.9.0 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
) )
require ( require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
go.mongodb.org/mongo-driver v1.16.0 go.mongodb.org/mongo-driver v1.16.0
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.38.0 // indirect
) )
require ( require (
@@ -37,7 +66,6 @@ require (
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/libp2p/go-libp2p/core v0.43.0-rc2 github.com/libp2p/go-libp2p/core v0.43.0-rc2
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -52,10 +80,12 @@ require (
github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect
golang.org/x/crypto v0.39.0 // indirect golang.org/x/crypto v0.44.0 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.15.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/text v0.26.0 // indirect golang.org/x/text v0.31.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.35.1
k8s.io/client-go v0.35.1
) )

173
go.sum
View File

@@ -1,6 +1,8 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/beego/beego/v2 v2.3.1 h1:7MUKMpJYzOXtCUsTEoXOxsDV/UcHw6CPbaWMlthVNsc= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/beego/beego/v2 v2.3.1/go.mod h1:5cqHsOHJIxkq44tBpRvtDe59GuVRVv/9/tyVDxd5ce4= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/beego/beego/v2 v2.3.8 h1:wplhB1pF4TxR+2SS4PUej8eDoH4xGfxuHfS7wAk9VBc=
github.com/beego/beego/v2 v2.3.8/go.mod h1:8vl9+RrXqvodrl9C8yivX1e6le6deCK6RWeq8R7gTTg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q= github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q=
@@ -10,17 +12,34 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/etcd-io/etcd v3.3.17+incompatible/go.mod h1:cdZ77EstHBwVtD6iTgzgvogwcjo9m4iOqoijouPJ4bs= github.com/etcd-io/etcd v3.3.17+incompatible/go.mod h1:cdZ77EstHBwVtD6iTgzgvogwcjo9m4iOqoijouPJ4bs=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -29,12 +48,18 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -44,36 +69,74 @@ github.com/goraz/onion v0.1.3/go.mod h1:XEmz1XoBz+wxTgWB8NwuvRm4RAu3vKxvrmYtzK+X
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-libp2p/core v0.43.0-rc2 h1:1X1aDJNWhMfodJ/ynbaGLkgnC8f+hfBIqQDrzxFZOqI= github.com/libp2p/go-libp2p/core v0.43.0-rc2 h1:1X1aDJNWhMfodJ/ynbaGLkgnC8f+hfBIqQDrzxFZOqI=
github.com/libp2p/go-libp2p/core v0.43.0-rc2/go.mod h1:NYeJ9lvyBv9nbDk2IuGb8gFKEOkIv/W5YRIy1pAJB2Q= github.com/libp2p/go-libp2p/core v0.43.0-rc2/go.mod h1:NYeJ9lvyBv9nbDk2IuGb8gFKEOkIv/W5YRIy1pAJB2Q=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.16.0 h1:oGWEVKioVQcdIOBlYM8BH1rZDWOGJSqr9/BKl6zQ4qc=
github.com/multiformats/go-multiaddr v0.16.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.1 h1:x/Fuxr7ZuR4jJV4Os5g444F7xC4XmyUaT/FWtE+9Zjo=
github.com/multiformats/go-multicodec v0.9.1/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
@@ -81,6 +144,10 @@ github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDm
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g= github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -93,8 +160,8 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
@@ -105,12 +172,23 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@@ -122,27 +200,33 @@ github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76/go.mod h1:SQliXeA7Dh
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4= go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -153,33 +237,60 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q=
k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM=
k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU=
k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM=
k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -1,63 +1,23 @@
package bill package bill
import ( import (
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type billMongoAccessor struct { type billMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[*Bill] // AbstractAccessor contains the basic fields of an accessor (model, caller)
} }
// New creates a new instance of the billMongoAccessor // New creates a new instance of the billMongoAccessor
func NewAccessor(request *tools.APIRequest) *billMongoAccessor { func NewAccessor(request *tools.APIRequest) *billMongoAccessor {
return &billMongoAccessor{ return &billMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*Bill]{
Logger: logs.CreateLogger(tools.LIVE_DATACENTER.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.BILL.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.LIVE_DATACENTER, Type: tools.BILL,
New: func() *Bill { return &Bill{} },
}, },
} }
} }
/*
* Nothing special here, just the basic CRUD operations
*/
func (a *billMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *billMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
// should verify if a source is existing...
return utils.GenericUpdateOne(set, id, a, &Bill{})
}
func (a *billMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data.(*Bill), a)
}
func (a *billMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data.(*Bill), a)
}
func (a *billMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Bill](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *billMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Bill](a.getExec(), isDraft, a)
}
func (a *billMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Bill](filters, search, (&Bill{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *billMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject {
return d
}
}

View File

@@ -0,0 +1,95 @@
package bill_test
import (
"testing"
"cloud.o-forge.io/core/oc-lib/models/bill"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/order"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ---- Bill model ----
func TestBill_StoreDraftDefault(t *testing.T) {
b := &bill.Bill{}
b.StoreDraftDefault()
assert.True(t, b.IsDraft)
}
func TestBill_CanDelete_Draft(t *testing.T) {
b := &bill.Bill{}
b.IsDraft = true
assert.True(t, b.CanDelete())
}
func TestBill_CanDelete_NonDraft(t *testing.T) {
b := &bill.Bill{}
b.IsDraft = false
assert.False(t, b.CanDelete())
}
func TestBill_CanUpdate_StatusChange_NonDraft(t *testing.T) {
b := &bill.Bill{Status: enum.PENDING}
b.IsDraft = false
set := &bill.Bill{Status: enum.PAID}
ok, returned := b.CanUpdate(set)
assert.True(t, ok)
assert.Equal(t, enum.PAID, returned.(*bill.Bill).Status)
}
func TestBill_CanUpdate_SameStatus_NonDraft(t *testing.T) {
b := &bill.Bill{Status: enum.PENDING}
b.IsDraft = false
set := &bill.Bill{Status: enum.PENDING}
ok, _ := b.CanUpdate(set)
assert.False(t, ok)
}
func TestBill_CanUpdate_Draft(t *testing.T) {
b := &bill.Bill{Status: enum.PENDING}
b.IsDraft = true
set := &bill.Bill{Status: enum.PAID}
ok, _ := b.CanUpdate(set)
assert.True(t, ok)
}
func TestBill_GetAccessor(t *testing.T) {
b := &bill.Bill{}
acc := b.GetAccessor(&tools.APIRequest{})
assert.NotNil(t, acc)
}
func TestBill_GetAccessor_NilRequest(t *testing.T) {
b := &bill.Bill{}
acc := b.GetAccessor(nil)
assert.NotNil(t, acc)
}
// ---- GenerateBill ----
func TestGenerateBill_Basic(t *testing.T) {
o := &order.Order{
AbstractObject: utils.AbstractObject{UUID: "order-uuid-1"},
}
req := &tools.APIRequest{PeerID: "peer-abc"}
b, err := bill.GenerateBill(o, req)
require.NoError(t, err)
assert.NotNil(t, b)
assert.Equal(t, "order-uuid-1", b.OrderID)
assert.Equal(t, enum.PENDING, b.Status)
assert.False(t, b.IsDraft)
assert.Contains(t, b.Name, "peer-abc")
}
// ---- SumUpBill ----
func TestBill_SumUpBill_NoSubOrders(t *testing.T) {
b := &bill.Bill{Total: 0}
result, err := b.SumUpBill(nil)
require.NoError(t, err)
assert.Equal(t, 0.0, result.Total)
}

View File

@@ -34,6 +34,8 @@ type Booking struct {
ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` // ResourceType is the type of the resource ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` // ResourceType is the type of the resource
ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty" validate:"required"` // could be a Compute or a Storage ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty" validate:"required"` // could be a Compute or a Storage
InstanceID string `json:"instance_id,omitempty" bson:"instance_id,omitempty" validate:"required"` // could be a Compute or a Storage
} }
func (b *Booking) CalcDeltaOfExecution() map[string]map[string]models.MetricResume { func (b *Booking) CalcDeltaOfExecution() map[string]map[string]models.MetricResume {

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"time" "time"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/common/enum" "cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
@@ -12,16 +11,17 @@ import (
) )
type BookingMongoAccessor struct { type BookingMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[*Booking] // AbstractAccessor contains the basic fields of an accessor (model, caller)
} }
// New creates a new instance of the BookingMongoAccessor // New creates a new instance of the BookingMongoAccessor
func NewAccessor(request *tools.APIRequest) *BookingMongoAccessor { func NewAccessor(request *tools.APIRequest) *BookingMongoAccessor {
return &BookingMongoAccessor{ return &BookingMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*Booking]{
Logger: logs.CreateLogger(tools.BOOKING.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.BOOKING.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.BOOKING, Type: tools.BOOKING,
New: func() *Booking { return &Booking{} },
}, },
} }
} }
@@ -29,28 +29,18 @@ func NewAccessor(request *tools.APIRequest) *BookingMongoAccessor {
/* /*
* Nothing special here, just the basic CRUD operations * Nothing special here, just the basic CRUD operations
*/ */
func (a *BookingMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (a *BookingMongoAccessor) UpdateOne(set map[string]interface{}, id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a) if set["state"] == nil {
}
func (a *BookingMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
if set.(*Booking).State == 0 {
return nil, 400, errors.New("state is required") return nil, 400, errors.New("state is required")
} }
realSet := &Booking{State: set.(*Booking).State} set = map[string]interface{}{
return utils.GenericUpdateOne(realSet, id, a, &Booking{}) "state": set["state"],
} }
return utils.GenericUpdateOne(set, id, a)
func (a *BookingMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
}
func (a *BookingMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
} }
func (a *BookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *BookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Booking](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne(id, a.New(), func(d utils.DBObject) (utils.DBObject, int, error) {
now := time.Now() now := time.Now()
now = now.Add(time.Second * -60) now = now.Add(time.Second * -60)
if d.(*Booking).State == enum.DRAFT && now.UTC().After(d.(*Booking).ExpectedStartDate) { if d.(*Booking).State == enum.DRAFT && now.UTC().After(d.(*Booking).ExpectedStartDate) {
@@ -67,15 +57,7 @@ func (a *BookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
}, a) }, a)
} }
func (a *BookingMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { func (a *BookingMongoAccessor) GetExec(isDraft bool) func(utils.DBObject) utils.ShallowDBObject {
return utils.GenericLoadAll[*Booking](a.getExec(), isDraft, a)
}
func (a *BookingMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Booking](filters, search, (&Booking{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *BookingMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject { return func(d utils.DBObject) utils.ShallowDBObject {
now := time.Now() now := time.Now()
now = now.Add(time.Second * -60) now = now.Add(time.Second * -60)

View File

@@ -0,0 +1,434 @@
package planner
import (
"encoding/json"
"time"
"cloud.o-forge.io/core/oc-lib/models/booking"
"cloud.o-forge.io/core/oc-lib/models/resources"
"cloud.o-forge.io/core/oc-lib/tools"
)
// InstanceCapacity holds the maximum available resources of a single resource instance.
type InstanceCapacity struct {
CPUCores map[string]float64 `json:"cpu_cores,omitempty"` // model -> total cores
GPUMemGB map[string]float64 `json:"gpu_mem_gb,omitempty"` // model -> total memory GB
RAMGB float64 `json:"ram_gb,omitempty"` // total RAM GB
StorageGB float64 `json:"storage_gb,omitempty"` // total storage GB
}
// ResourceRequest describes the resource amounts needed for a prospective booking.
// A nil map or nil pointer for a dimension means "use the full instance capacity" for that dimension.
type ResourceRequest struct {
CPUCores map[string]float64 // model -> cores needed (nil = max)
GPUMemGB map[string]float64 // model -> memory GB needed (nil = max)
RAMGB *float64 // GB needed (nil = max)
StorageGB *float64 // GB needed (nil = max)
}
// PlannerSlot represents a single booking occupying a resource instance during a time window.
// Usage maps each resource dimension (cpu_<model>, gpu_<model>, ram, storage) to
// its percentage of consumption relative to the instance's maximum capacity (0100).
type PlannerSlot struct {
Start time.Time `json:"start"`
End time.Time `json:"end"`
InstanceID string `json:"instance_id,omitempty"` // instance targeted by this booking
BookingID string `json:"booking_id,omitempty"` // empty in shallow mode
Usage map[string]float64 `json:"usage,omitempty"` // dimension -> % of max (0-100)
}
// Planner is a volatile (non-persisted) object that organises bookings by resource.
// Only ComputeResource and StorageResource bookings appear in the schedule.
type Planner struct {
GeneratedAt time.Time `json:"generated_at"`
Schedule map[string][]*PlannerSlot `json:"schedule"` // resource_id -> slots
Capacities map[string]map[string]*InstanceCapacity `json:"capacities"` // resource_id -> instance_id -> max capacity
}
// Generate builds a full Planner from all active bookings.
// Each slot includes the booking ID, the instance ID, and the usage percentage of every resource dimension.
func Generate(request *tools.APIRequest) (*Planner, error) {
return generate(request, false)
}
// GenerateShallow builds a Planner from all active bookings without booking IDs.
func GenerateShallow(request *tools.APIRequest) (*Planner, error) {
return generate(request, true)
}
func generate(request *tools.APIRequest, shallow bool) (*Planner, error) {
accessor := booking.NewAccessor(request)
bookings, code, err := accessor.Search(nil, "*", false)
if code != 200 || err != nil {
return nil, err
}
p := &Planner{
GeneratedAt: time.Now(),
Schedule: map[string][]*PlannerSlot{},
Capacities: map[string]map[string]*InstanceCapacity{},
}
for _, b := range bookings {
bk := b.(*booking.Booking)
// Only compute and storage resources are eligible
if bk.ResourceType != tools.COMPUTE_RESOURCE && bk.ResourceType != tools.STORAGE_RESOURCE {
continue
}
end := bk.ExpectedEndDate
if end == nil {
e := bk.ExpectedStartDate.Add(time.Hour)
end = &e
}
instanceID, usage, cap := extractSlotData(bk, request)
if cap != nil && instanceID != "" {
if p.Capacities[bk.ResourceID] == nil {
p.Capacities[bk.ResourceID] = map[string]*InstanceCapacity{}
}
p.Capacities[bk.ResourceID][instanceID] = cap
}
slot := &PlannerSlot{
Start: bk.ExpectedStartDate,
End: *end,
InstanceID: instanceID,
Usage: usage,
}
if !shallow {
slot.BookingID = bk.GetID()
}
p.Schedule[bk.ResourceID] = append(p.Schedule[bk.ResourceID], slot)
}
return p, nil
}
// Check reports whether the requested time window has enough remaining capacity
// on the specified instance of the given resource.
//
// req describes the amounts needed; nil fields default to the full instance capacity.
// If req itself is nil, the full capacity of every dimension is assumed.
// If end is nil, a 1-hour window from start is assumed.
//
// A slot that overlaps the requested window is acceptable if, for every requested
// dimension, existing usage + requested usage ≤ 100 %.
// Slots targeting other instances are ignored.
// If no capacity is known for this instance (never booked), it is fully available.
func (p *Planner) Check(resourceID string, instanceID string, req *ResourceRequest, start time.Time, end *time.Time) bool {
if end == nil {
e := start.Add(time.Hour)
end = &e
}
cap := p.instanceCapacity(resourceID, instanceID)
reqPct := toPercentages(req, cap)
slots, ok := p.Schedule[resourceID]
if !ok {
return true
}
for _, slot := range slots {
// Only consider slots on the same instance
if slot.InstanceID != instanceID {
continue
}
// Only consider overlapping slots
if !slot.Start.Before(*end) || !slot.End.After(start) {
continue
}
// Combined usage must not exceed 100 % for any requested dimension
for dim, needed := range reqPct {
if slot.Usage[dim]+needed > 100.0 {
return false
}
}
}
return true
}
// instanceCapacity returns the stored max capacity for a resource/instance pair.
// Returns an empty (but non-nil) capacity when the instance has never been booked.
func (p *Planner) instanceCapacity(resourceID, instanceID string) *InstanceCapacity {
if instances, ok := p.Capacities[resourceID]; ok {
if c, ok := instances[instanceID]; ok {
return c
}
}
return &InstanceCapacity{
CPUCores: map[string]float64{},
GPUMemGB: map[string]float64{},
}
}
// toPercentages converts a ResourceRequest into a map of dimension -> percentage-of-max.
// nil fields in req (or nil req) are treated as requesting the full capacity (100 %).
func toPercentages(req *ResourceRequest, cap *InstanceCapacity) map[string]float64 {
pct := map[string]float64{}
if req == nil {
for model := range cap.CPUCores {
pct["cpu_"+model] = 100.0
}
for model := range cap.GPUMemGB {
pct["gpu_"+model] = 100.0
}
if cap.RAMGB > 0 {
pct["ram"] = 100.0
}
if cap.StorageGB > 0 {
pct["storage"] = 100.0
}
return pct
}
if req.CPUCores == nil {
for model, maxCores := range cap.CPUCores {
if maxCores > 0 {
pct["cpu_"+model] = 100.0
}
}
} else {
for model, needed := range req.CPUCores {
if maxCores, ok := cap.CPUCores[model]; ok && maxCores > 0 {
pct["cpu_"+model] = (needed / maxCores) * 100.0
}
}
}
if req.GPUMemGB == nil {
for model, maxMem := range cap.GPUMemGB {
if maxMem > 0 {
pct["gpu_"+model] = 100.0
}
}
} else {
for model, needed := range req.GPUMemGB {
if maxMem, ok := cap.GPUMemGB[model]; ok && maxMem > 0 {
pct["gpu_"+model] = (needed / maxMem) * 100.0
}
}
}
if req.RAMGB == nil {
if cap.RAMGB > 0 {
pct["ram"] = 100.0
}
} else if cap.RAMGB > 0 {
pct["ram"] = (*req.RAMGB / cap.RAMGB) * 100.0
}
if req.StorageGB == nil {
if cap.StorageGB > 0 {
pct["storage"] = 100.0
}
} else if cap.StorageGB > 0 {
pct["storage"] = (*req.StorageGB / cap.StorageGB) * 100.0
}
return pct
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
// extractSlotData parses the booking's PricedItem, loads the corresponding resource,
// and returns the instance ID, usage percentages, and instance capacity in a single pass.
func extractSlotData(bk *booking.Booking, request *tools.APIRequest) (instanceID string, usage map[string]float64, cap *InstanceCapacity) {
usage = map[string]float64{}
if len(bk.PricedItem) == 0 {
return
}
b, err := json.Marshal(bk.PricedItem)
if err != nil {
return
}
switch bk.ResourceType {
case tools.COMPUTE_RESOURCE:
instanceID, usage, cap = extractComputeSlot(b, bk.ResourceID, request)
case tools.STORAGE_RESOURCE:
instanceID, usage, cap = extractStorageSlot(b, bk.ResourceID, request)
}
return
}
// extractComputeSlot extracts the instance ID, usage percentages, and max capacity for a compute booking.
// Keys in usage: "cpu_<model>", "gpu_<model>", "ram".
func extractComputeSlot(pricedJSON []byte, resourceID string, request *tools.APIRequest) (instanceID string, usage map[string]float64, cap *InstanceCapacity) {
usage = map[string]float64{}
var priced resources.PricedComputeResource
if err := json.Unmarshal(pricedJSON, &priced); err != nil {
return
}
res, _, err := (&resources.ComputeResource{}).GetAccessor(request).LoadOne(resourceID)
if err != nil {
return
}
compute := res.(*resources.ComputeResource)
instance := findComputeInstance(compute, priced.InstancesRefs)
if instance == nil {
return
}
instanceID = instance.GetID()
// Build the instance's maximum capacity
cap = &InstanceCapacity{
CPUCores: map[string]float64{},
GPUMemGB: map[string]float64{},
RAMGB: totalRAM(instance),
}
for model := range instance.CPUs {
cap.CPUCores[model] = totalCPUCores(instance, model)
}
for model := range instance.GPUs {
cap.GPUMemGB[model] = totalGPUMemory(instance, model)
}
// Compute usage as a percentage of the instance's maximum capacity
for model, usedCores := range priced.CPUsLocated {
if maxCores := cap.CPUCores[model]; maxCores > 0 {
usage["cpu_"+model] = (usedCores / maxCores) * 100.0
}
}
for model, usedMem := range priced.GPUsLocated {
if maxMem := cap.GPUMemGB[model]; maxMem > 0 {
usage["gpu_"+model] = (usedMem / maxMem) * 100.0
}
}
if cap.RAMGB > 0 && priced.RAMLocated > 0 {
usage["ram"] = (priced.RAMLocated / cap.RAMGB) * 100.0
}
return
}
// extractStorageSlot extracts the instance ID, usage percentages, and max capacity for a storage booking.
// Key in usage: "storage".
func extractStorageSlot(pricedJSON []byte, resourceID string, request *tools.APIRequest) (instanceID string, usage map[string]float64, cap *InstanceCapacity) {
usage = map[string]float64{}
var priced resources.PricedStorageResource
if err := json.Unmarshal(pricedJSON, &priced); err != nil {
return
}
res, _, err := (&resources.StorageResource{}).GetAccessor(request).LoadOne(resourceID)
if err != nil {
return
}
storage := res.(*resources.StorageResource)
instance := findStorageInstance(storage, priced.InstancesRefs)
if instance == nil {
return
}
instanceID = instance.GetID()
maxStorage := float64(instance.SizeGB)
cap = &InstanceCapacity{
CPUCores: map[string]float64{},
GPUMemGB: map[string]float64{},
StorageGB: maxStorage,
}
if maxStorage > 0 && priced.UsageStorageGB > 0 {
usage["storage"] = (priced.UsageStorageGB / maxStorage) * 100.0
}
return
}
// findComputeInstance returns the instance referenced by the priced item's InstancesRefs,
// falling back to the first available instance.
func findComputeInstance(compute *resources.ComputeResource, refs map[string]string) *resources.ComputeResourceInstance {
for _, inst := range compute.Instances {
if _, ok := refs[inst.GetID()]; ok {
return inst
}
}
if len(compute.Instances) > 0 {
return compute.Instances[0]
}
return nil
}
// findStorageInstance returns the instance referenced by the priced item's InstancesRefs,
// falling back to the first available instance.
func findStorageInstance(storage *resources.StorageResource, refs map[string]string) *resources.StorageResourceInstance {
for _, inst := range storage.Instances {
if _, ok := refs[inst.GetID()]; ok {
return inst
}
}
if len(storage.Instances) > 0 {
return storage.Instances[0]
}
return nil
}
// totalCPUCores returns the total number of cores for a given CPU model across all nodes.
// It multiplies the per-chip core count (from the instance's CPU spec) by the total
// number of chips of that model across all nodes (chip_count × node.Quantity).
// Falls back to the spec's core count if no nodes are defined.
func totalCPUCores(instance *resources.ComputeResourceInstance, model string) float64 {
spec, ok := instance.CPUs[model]
if !ok || spec == nil || spec.Cores == 0 {
return 0
}
if len(instance.Nodes) == 0 {
return float64(spec.Cores)
}
totalChips := int64(0)
for _, node := range instance.Nodes {
if chipCount, ok := node.CPUs[model]; ok {
totalChips += chipCount * max(node.Quantity, 1)
}
}
if totalChips == 0 {
return float64(spec.Cores)
}
return float64(totalChips * int64(spec.Cores))
}
// totalGPUMemory returns the total GPU memory (GB) for a given model across all nodes.
// Falls back to the spec's memory if no nodes are defined.
func totalGPUMemory(instance *resources.ComputeResourceInstance, model string) float64 {
spec, ok := instance.GPUs[model]
if !ok || spec == nil || spec.MemoryGb == 0 {
return 0
}
if len(instance.Nodes) == 0 {
return spec.MemoryGb
}
totalUnits := int64(0)
for _, node := range instance.Nodes {
if unitCount, ok := node.GPUs[model]; ok {
totalUnits += unitCount * max(node.Quantity, 1)
}
}
if totalUnits == 0 {
return spec.MemoryGb
}
return float64(totalUnits) * spec.MemoryGb
}
// totalRAM returns the total RAM (GB) across all nodes of a compute instance.
func totalRAM(instance *resources.ComputeResourceInstance) float64 {
total := float64(0)
for _, node := range instance.Nodes {
if node.RAM != nil && node.RAM.SizeGb > 0 {
total += node.RAM.SizeGb * float64(max(node.Quantity, 1))
}
}
return total
}

View File

@@ -91,10 +91,6 @@ func (d *CollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accesso
return NewAccessor(request) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (d *CollaborativeArea) Trim() *CollaborativeArea {
return d
}
func (d *CollaborativeArea) StoreDraftDefault() { func (d *CollaborativeArea) StoreDraftDefault() {
d.AllowedPeersGroup = map[string][]string{ d.AllowedPeersGroup = map[string][]string{
d.CreatorID: {"*"}, d.CreatorID: {"*"},

View File

@@ -17,7 +17,7 @@ import (
// SharedWorkspace is a struct that represents a collaborative area // SharedWorkspace is a struct that represents a collaborative area
type collaborativeAreaMongoAccessor struct { type collaborativeAreaMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[*CollaborativeArea] // AbstractAccessor contains the basic fields of an accessor (model, caller)
workspaceAccessor utils.Accessor workspaceAccessor utils.Accessor
workflowAccessor utils.Accessor workflowAccessor utils.Accessor
@@ -27,10 +27,11 @@ type collaborativeAreaMongoAccessor struct {
func NewAccessor(request *tools.APIRequest) *collaborativeAreaMongoAccessor { func NewAccessor(request *tools.APIRequest) *collaborativeAreaMongoAccessor {
return &collaborativeAreaMongoAccessor{ return &collaborativeAreaMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*CollaborativeArea]{
Logger: logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.COLLABORATIVE_AREA, Type: tools.COLLABORATIVE_AREA,
New: func() *CollaborativeArea { return &CollaborativeArea{} },
}, },
workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request), workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request),
workflowAccessor: (&w.Workflow{}).GetAccessor(request), workflowAccessor: (&w.Workflow{}).GetAccessor(request),
@@ -52,8 +53,8 @@ func (a *collaborativeAreaMongoAccessor) DeleteOne(id string) (utils.DBObject, i
} }
// UpdateOne updates a collaborative area in the database, given its ID and the new data, it automatically share to peers if the workspace is shared // UpdateOne updates a collaborative area in the database, given its ID and the new data, it automatically share to peers if the workspace is shared
func (a *collaborativeAreaMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (a *collaborativeAreaMongoAccessor) UpdateOne(set map[string]interface{}, id string) (utils.DBObject, int, error) {
res, code, err := utils.GenericUpdateOne(set.(*CollaborativeArea).Trim(), id, a, &CollaborativeArea{}) res, code, err := utils.GenericUpdateOne(set, id, a)
// a.deleteToPeer(res.(*CollaborativeArea)) // delete the collaborative area on the peer // a.deleteToPeer(res.(*CollaborativeArea)) // delete the collaborative area on the peer
a.sharedWorkflow(res.(*CollaborativeArea), id) // replace all shared workflows a.sharedWorkflow(res.(*CollaborativeArea), id) // replace all shared workflows
a.sharedWorkspace(res.(*CollaborativeArea), id) // replace all collaborative areas (not shared worspace obj but workspace one) a.sharedWorkspace(res.(*CollaborativeArea), id) // replace all collaborative areas (not shared worspace obj but workspace one)
@@ -63,19 +64,19 @@ func (a *collaborativeAreaMongoAccessor) UpdateOne(set utils.DBObject, id string
// StoreOne stores a collaborative area in the database, it automatically share to peers if the workspace is shared // StoreOne stores a collaborative area in the database, it automatically share to peers if the workspace is shared
func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
id, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{ pp, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true, Admin: true,
})) // get the local peer })) // get the local peer
if err != nil { if err != nil || pp == nil {
return data, 404, err return data, 404, err
} }
data.(*CollaborativeArea).Clear(id) // set the creator data.(*CollaborativeArea).Clear(pp.GetID()) // set the creator
// retrieve or proper peer // retrieve or proper peer
if data.(*CollaborativeArea).CollaborativeAreaRule != nil { if data.(*CollaborativeArea).CollaborativeAreaRule != nil {
data.(*CollaborativeArea).CollaborativeAreaRule = &CollaborativeAreaRule{} data.(*CollaborativeArea).CollaborativeAreaRule = &CollaborativeAreaRule{}
} }
data.(*CollaborativeArea).CollaborativeAreaRule.Creator = id data.(*CollaborativeArea).CollaborativeAreaRule.Creator = pp.GetID()
d, code, err := utils.GenericStoreOne(data.(*CollaborativeArea).Trim(), a) d, code, err := utils.GenericStoreOne(data, a)
if code == 200 { if code == 200 {
a.sharedWorkflow(d.(*CollaborativeArea), d.GetID()) // create all shared workflows a.sharedWorkflow(d.(*CollaborativeArea), d.GetID()) // create all shared workflows
a.sharedWorkspace(d.(*CollaborativeArea), d.GetID()) // create all collaborative areas a.sharedWorkspace(d.(*CollaborativeArea), d.GetID()) // create all collaborative areas
@@ -84,11 +85,6 @@ func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DB
return data, code, err return data, code, err
} }
// CopyOne copies a CollaborativeArea in the database
func (a *collaborativeAreaMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return a.StoreOne(data)
}
func filterEnrich[T utils.ShallowDBObject](arr []string, isDrafted bool, a utils.Accessor) []T { func filterEnrich[T utils.ShallowDBObject](arr []string, isDrafted bool, a utils.Accessor) []T {
var new []T var new []T
res, code, _ := a.Search(&dbs.Filters{ res, code, _ := a.Search(&dbs.Filters{
@@ -130,23 +126,10 @@ func (a *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeAr
return sharedWorkspace return sharedWorkspace
} }
func (a *collaborativeAreaMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *collaborativeAreaMongoAccessor) GetExec(isDraft bool) func(utils.DBObject) utils.ShallowDBObject {
return utils.GenericLoadOne[*CollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) { return func(d utils.DBObject) utils.ShallowDBObject {
return a.enrich(d.(*CollaborativeArea), false, a.Request), 200, nil return a.enrich(d.(*CollaborativeArea), isDraft, a.Request)
}, a) }
}
func (a *collaborativeAreaMongoAccessor) LoadAll(isDrafted bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*CollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
return a.enrich(d.(*CollaborativeArea), isDrafted, a.Request)
}, isDrafted, a)
}
func (a *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string, isDrafted bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*CollaborativeArea](filters, search, (&CollaborativeArea{}).GetObjectFilters(search),
func(d utils.DBObject) utils.ShallowDBObject {
return a.enrich(d.(*CollaborativeArea), isDrafted, a.Request)
}, isDrafted, a)
} }
/* /*
@@ -158,7 +141,9 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeAr
eld := eldest.(*CollaborativeArea) eld := eldest.(*CollaborativeArea)
if eld.Workspaces != nil { // update all your workspaces in the eldest by replacing shared ref by an empty string if eld.Workspaces != nil { // update all your workspaces in the eldest by replacing shared ref by an empty string
for _, v := range eld.Workspaces { for _, v := range eld.Workspaces {
a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: ""}, v) a.workspaceAccessor.UpdateOne(map[string]interface{}{
"shared": "",
}, v)
if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil { if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
continue continue
} }
@@ -174,7 +159,10 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeAr
} }
if shared.Workspaces != nil { if shared.Workspaces != nil {
for _, v := range shared.Workspaces { // update all the collaborative areas for _, v := range shared.Workspaces { // update all the collaborative areas
workspace, code, _ := a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: shared.UUID}, v) // add the shared ref to workspace workspace, code, _ := a.workspaceAccessor.UpdateOne(
map[string]interface{}{
"shared": shared.UUID,
}, v) // add the shared ref to workspace
if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil { if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
continue continue
} }
@@ -214,7 +202,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre
} // kick the shared reference in your old shared workflow } // kick the shared reference in your old shared workflow
n := &w.Workflow{} n := &w.Workflow{}
n.Shared = new n.Shared = new
a.workflowAccessor.UpdateOne(n, v) a.workflowAccessor.UpdateOne(n.Serialize(n), v)
if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil { if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
continue continue
} }
@@ -236,7 +224,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre
s := data.(*w.Workflow) s := data.(*w.Workflow)
if !slices.Contains(s.Shared, id) { if !slices.Contains(s.Shared, id) {
s.Shared = append(s.Shared, id) s.Shared = append(s.Shared, id)
workflow, code, _ := a.workflowAccessor.UpdateOne(s, v) workflow, code, _ := a.workflowAccessor.UpdateOne(s.Serialize(s), v)
if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil { if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
continue continue
} }
@@ -259,6 +247,8 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre
// because you have no reference to the remote shared workflow // because you have no reference to the remote shared workflow
} }
// TODO it's a Shared API Problem with OC-DISCOVERY
// sharedWorkspace is a function that shares the collaborative area to the peers // sharedWorkspace is a function that shares the collaborative area to the peers
func (a *collaborativeAreaMongoAccessor) deleteToPeer(shared *CollaborativeArea) { func (a *collaborativeAreaMongoAccessor) deleteToPeer(shared *CollaborativeArea) {
a.contactPeer(shared, tools.POST) a.contactPeer(shared, tools.POST)

View File

@@ -1,62 +1,23 @@
package rule package rule
import ( import (
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type ruleMongoAccessor struct { type ruleMongoAccessor struct {
utils.AbstractAccessor utils.AbstractAccessor[*Rule]
} }
// New creates a new instance of the ruleMongoAccessor // New creates a new instance of the ruleMongoAccessor
func NewAccessor(request *tools.APIRequest) *ruleMongoAccessor { func NewAccessor(request *tools.APIRequest) *ruleMongoAccessor {
return &ruleMongoAccessor{ return &ruleMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*Rule]{
Logger: logs.CreateLogger(tools.RULE.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.RULE.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.RULE, Type: tools.RULE,
New: func() *Rule { return &Rule{} },
}, },
} }
} }
/*
* Nothing special here, just the basic CRUD operations
*/
func (a *ruleMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *ruleMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set, id, a, &Rule{})
}
func (a *ruleMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
}
func (a *ruleMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
}
func (a *ruleMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Rule](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *ruleMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Rule](a.getExec(), isDraft, a)
}
func (a *ruleMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Rule](filters, search, (&Rule{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *ruleMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject {
return d
}
}

View File

@@ -1,56 +1,22 @@
package shallow_collaborative_area package shallow_collaborative_area
import ( import (
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type shallowSharedWorkspaceMongoAccessor struct { type shallowSharedWorkspaceMongoAccessor struct {
utils.AbstractAccessor utils.AbstractAccessor[*ShallowCollaborativeArea]
} }
func NewAccessor(request *tools.APIRequest) *shallowSharedWorkspaceMongoAccessor { func NewAccessor(request *tools.APIRequest) *shallowSharedWorkspaceMongoAccessor {
return &shallowSharedWorkspaceMongoAccessor{ return &shallowSharedWorkspaceMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*ShallowCollaborativeArea]{
Logger: logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
Request: request, // Set the caller Request: request, // Set the caller
Type: tools.COLLABORATIVE_AREA, Type: tools.COLLABORATIVE_AREA,
New: func() *ShallowCollaborativeArea { return &ShallowCollaborativeArea{} },
}, },
} }
} }
func (a *shallowSharedWorkspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *shallowSharedWorkspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set.(*ShallowCollaborativeArea), id, a, &ShallowCollaborativeArea{})
}
func (a *shallowSharedWorkspaceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data.(*ShallowCollaborativeArea), a)
}
func (a *shallowSharedWorkspaceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return a.StoreOne(data)
}
func (a *shallowSharedWorkspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*ShallowCollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *shallowSharedWorkspaceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*ShallowCollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
return d
}, isDraft, a)
}
func (a *shallowSharedWorkspaceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*ShallowCollaborativeArea](filters, search, (&ShallowCollaborativeArea{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject {
return d
}, isDraft, a)
}

View File

@@ -9,6 +9,7 @@ import (
type PricedItemITF interface { type PricedItemITF interface {
GetID() string GetID() string
GetInstanceID() string
GetType() tools.DataType GetType() tools.DataType
IsPurchasable() bool IsPurchasable() bool
IsBooked() bool IsBooked() bool

View File

@@ -85,10 +85,6 @@ const (
PER_MONTH PER_MONTH
) )
func IsTimeStrategy(i int) bool {
return len(TimePricingStrategyList()) < i
}
func (t TimePricingStrategy) String() string { func (t TimePricingStrategy) String() string {
return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t] return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t]
} }

View File

@@ -15,9 +15,9 @@ func (d DummyStrategy) GetStrategy() string { return "DUMMY" }
func (d DummyStrategy) GetStrategyValue() int { return int(d) } func (d DummyStrategy) GetStrategyValue() int { return int(d) }
func TestBuyingStrategy_String(t *testing.T) { func TestBuyingStrategy_String(t *testing.T) {
assert.Equal(t, "UNLIMITED", pricing.PERMANENT.String()) assert.Equal(t, "PERMANENT", pricing.PERMANENT.String())
assert.Equal(t, "UNDEFINED_SUBSCRIPTION", pricing.UNDEFINED_SUBSCRIPTION.String())
assert.Equal(t, "SUBSCRIPTION", pricing.SUBSCRIPTION.String()) assert.Equal(t, "SUBSCRIPTION", pricing.SUBSCRIPTION.String())
//assert.Equal(t, "PAY PER USE", pricing.PAY_PER_USE.String())
} }
func TestBuyingStrategyList(t *testing.T) { func TestBuyingStrategyList(t *testing.T) {
@@ -121,8 +121,8 @@ func TestPricingStrategy_GetPriceHT(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 5.0, p) assert.Equal(t, 5.0, p)
// PAY_PER_USE case // UNDEFINED_SUBSCRIPTION case: price * quantity
//ps.BuyingStrategy = pricing.PAY_PER_USE ps.BuyingStrategy = pricing.UNDEFINED_SUBSCRIPTION
p, err = ps.GetPriceHT(3, 0, start, &end, nil) p, err = ps.GetPriceHT(3, 0, start, &end, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 15.0, p) assert.Equal(t, 15.0, p)

View File

@@ -4,23 +4,31 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type computeUnitsMongoAccessor[T LiveInterface] struct { type computeUnitsMongoAccessor[T LiveInterface] struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[LiveInterface] // AbstractAccessor contains the basic fields of an accessor (model, caller)
} }
// New creates a new instance of the computeUnitsMongoAccessor // New creates a new instance of the computeUnitsMongoAccessor
func NewAccessor[T LiveInterface](t tools.DataType, request *tools.APIRequest) *computeUnitsMongoAccessor[T] { func NewAccessor[T LiveInterface](t tools.DataType, request *tools.APIRequest) *computeUnitsMongoAccessor[T] {
return &computeUnitsMongoAccessor[T]{ return &computeUnitsMongoAccessor[T]{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[LiveInterface]{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(t.String()), // Create a logger with the data type
Request: request, Request: request,
Type: t, Type: t,
New: func() LiveInterface {
switch t {
case tools.LIVE_DATACENTER:
return &LiveDatacenter{}
case tools.LIVE_STORAGE:
return &LiveStorage{}
}
return &LiveDatacenter{}
},
}, },
} }
} }
@@ -28,19 +36,6 @@ func NewAccessor[T LiveInterface](t tools.DataType, request *tools.APIRequest) *
/* /*
* Nothing special here, just the basic CRUD operations * Nothing special here, just the basic CRUD operations
*/ */
func (a *computeUnitsMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *computeUnitsMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
// should verify if a source is existing...
return utils.GenericUpdateOne(set, id, a, &LiveDatacenter{})
}
func (a *computeUnitsMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data.(*LiveDatacenter), a)
}
func (a *computeUnitsMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { func (a *computeUnitsMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
// is a publisher... that become a resources. // is a publisher... that become a resources.
if data.IsDrafted() { if data.IsDrafted() {
@@ -71,7 +66,7 @@ func (a *computeUnitsMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObj
b, _ := json.Marshal(res) b, _ := json.Marshal(res)
json.Unmarshal(b, existingResource) json.Unmarshal(b, existingResource)
live.SetResourceInstance(existingResource, instance) live.SetResourceInstance(existingResource, instance)
resAccess.UpdateOne(existingResource, existingResource.GetID()) resAccess.UpdateOne(existingResource.Serialize(existingResource), existingResource.GetID())
} }
if live.GetID() != "" { if live.GetID() != "" {
return a.LoadOne(live.GetID()) return a.LoadOne(live.GetID())
@@ -89,29 +84,9 @@ func (a *computeUnitsMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObj
} }
live.SetResourcesID(res.GetID()) live.SetResourcesID(res.GetID())
if live.GetID() != "" { if live.GetID() != "" {
return a.UpdateOne(live, live.GetID()) return a.UpdateOne(live.Serialize(live), live.GetID())
} else { } else {
return a.StoreOne(live) return a.StoreOne(live)
} }
} }
} }
func (a *computeUnitsMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *computeUnitsMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[T](a.getExec(), isDraft, a)
}
func (a *computeUnitsMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*LiveDatacenter](filters, search, (&LiveDatacenter{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *computeUnitsMongoAccessor[T]) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject {
return d
}
}

View File

@@ -0,0 +1,144 @@
package live_test
import (
"testing"
"cloud.o-forge.io/core/oc-lib/models/live"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
)
// ---- AbstractLive (via LiveDatacenter embedding) ----
func TestAbstractLive_StoreDraftDefault(t *testing.T) {
dc := &live.LiveDatacenter{}
dc.StoreDraftDefault()
assert.True(t, dc.IsDraft)
}
func TestAbstractLive_CanDelete_Draft(t *testing.T) {
dc := &live.LiveDatacenter{}
dc.IsDraft = true
assert.True(t, dc.CanDelete())
}
func TestAbstractLive_CanDelete_NonDraft(t *testing.T) {
dc := &live.LiveDatacenter{}
dc.IsDraft = false
assert.False(t, dc.CanDelete())
}
func TestAbstractLive_GetMonitorPath(t *testing.T) {
dc := &live.LiveDatacenter{}
dc.MonitorPath = "/metrics"
assert.Equal(t, "/metrics", dc.GetMonitorPath())
}
func TestAbstractLive_GetResourcesID_Empty(t *testing.T) {
dc := &live.LiveDatacenter{}
assert.Empty(t, dc.GetResourcesID())
}
func TestAbstractLive_SetResourcesID_Append(t *testing.T) {
dc := &live.LiveDatacenter{}
dc.SetResourcesID("res-1")
assert.Equal(t, []string{"res-1"}, dc.GetResourcesID())
}
func TestAbstractLive_SetResourcesID_NoDuplication(t *testing.T) {
dc := &live.LiveDatacenter{}
dc.SetResourcesID("res-1")
dc.SetResourcesID("res-1") // second call should not duplicate
assert.Len(t, dc.GetResourcesID(), 1)
}
func TestAbstractLive_SetResourcesID_MultipleDistinct(t *testing.T) {
dc := &live.LiveDatacenter{}
dc.SetResourcesID("res-1")
dc.SetResourcesID("res-2")
assert.Len(t, dc.GetResourcesID(), 2)
}
func TestAbstractLive_GetResourceType(t *testing.T) {
dc := &live.LiveDatacenter{}
assert.Equal(t, tools.INVALID, dc.GetResourceType())
}
// ---- LiveDatacenter ----
func TestLiveDatacenter_GetAccessor(t *testing.T) {
dc := &live.LiveDatacenter{}
acc := dc.GetAccessor(&tools.APIRequest{})
assert.NotNil(t, acc)
}
func TestLiveDatacenter_GetAccessor_NilRequest(t *testing.T) {
dc := &live.LiveDatacenter{}
acc := dc.GetAccessor(nil)
assert.NotNil(t, acc)
}
func TestLiveDatacenter_GetResource(t *testing.T) {
dc := &live.LiveDatacenter{}
res := dc.GetResource()
assert.NotNil(t, res)
}
func TestLiveDatacenter_GetResourceInstance(t *testing.T) {
dc := &live.LiveDatacenter{}
inst := dc.GetResourceInstance()
assert.NotNil(t, inst)
}
func TestLiveDatacenter_IDAndName(t *testing.T) {
dc := &live.LiveDatacenter{}
dc.AbstractLive.AbstractObject = utils.AbstractObject{UUID: "dc-id", Name: "dc-name"}
assert.Equal(t, "dc-id", dc.GetID())
assert.Equal(t, "dc-name", dc.GetName())
}
// ---- LiveStorage ----
func TestLiveStorage_StoreDraftDefault(t *testing.T) {
s := &live.LiveStorage{}
s.StoreDraftDefault()
assert.True(t, s.IsDraft)
}
func TestLiveStorage_CanDelete_Draft(t *testing.T) {
s := &live.LiveStorage{}
s.IsDraft = true
assert.True(t, s.CanDelete())
}
func TestLiveStorage_CanDelete_NonDraft(t *testing.T) {
s := &live.LiveStorage{}
s.IsDraft = false
assert.False(t, s.CanDelete())
}
func TestLiveStorage_GetAccessor(t *testing.T) {
s := &live.LiveStorage{}
acc := s.GetAccessor(&tools.APIRequest{})
assert.NotNil(t, acc)
}
func TestLiveStorage_GetResource(t *testing.T) {
s := &live.LiveStorage{}
res := s.GetResource()
assert.NotNil(t, res)
}
func TestLiveStorage_GetResourceInstance(t *testing.T) {
s := &live.LiveStorage{}
inst := s.GetResourceInstance()
assert.NotNil(t, inst)
}
func TestLiveStorage_SetResourcesID_NoDuplication(t *testing.T) {
s := &live.LiveStorage{}
s.SetResourcesID("storage-1")
s.SetResourcesID("storage-1")
assert.Len(t, s.GetResourcesID(), 1)
}

View File

@@ -49,10 +49,15 @@ var ModelsCatalog = map[string]func() utils.DBObject{
// Model returns the model object based on the model type // Model returns the model object based on the model type
func Model(model int) utils.DBObject { func Model(model int) utils.DBObject {
log := logs.GetLogger() log := logs.GetLogger()
if _, ok := ModelsCatalog[tools.FromInt(model)]; ok { if model < 0 || model >= len(tools.Str) {
return ModelsCatalog[tools.FromInt(model)]() log.Error().Msg("Can't find model: index out of range")
return nil
} }
log.Error().Msg("Can't find model " + tools.FromInt(model) + ".") key := tools.FromInt(model)
if _, ok := ModelsCatalog[key]; ok {
return ModelsCatalog[key]()
}
log.Error().Msg("Can't find model " + key + ".")
return nil return nil
} }

View File

@@ -1,64 +1,24 @@
package order package order
import ( import (
"errors"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type orderMongoAccessor struct { type orderMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[*Order] // AbstractAccessor contains the basic fields of an accessor (model, caller)
} }
// New creates a new instance of the orderMongoAccessor // New creates a new instance of the orderMongoAccessor
func NewAccessor(request *tools.APIRequest) *orderMongoAccessor { func NewAccessor(request *tools.APIRequest) *orderMongoAccessor {
return &orderMongoAccessor{ return &orderMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*Order]{
Logger: logs.CreateLogger(tools.ORDER.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.ORDER.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.ORDER, Type: tools.ORDER,
New: func() *Order { return &Order{} },
NotImplemented: []string{"CopyOne"},
}, },
} }
} }
/*
* Nothing special here, just the basic CRUD operations
*/
func (a *orderMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *orderMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set, id, a, &Order{})
}
func (a *orderMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data,a)
}
func (a *orderMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return nil, 404, errors.New("Not implemented")
}
func (a *orderMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Order](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *orderMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Order](a.getExec(), isDraft, a)
}
func (a *orderMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Order](filters, search, (&Order{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *orderMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject {
return d
}
}

View File

@@ -1 +1,92 @@
package tests package order_test
import (
"testing"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/order"
"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
)
// ---- Order model ----
func TestOrder_StoreDraftDefault(t *testing.T) {
o := &order.Order{}
o.StoreDraftDefault()
assert.True(t, o.IsDraft)
}
func TestOrder_CanDelete_Draft(t *testing.T) {
o := &order.Order{}
o.IsDraft = true
assert.True(t, o.CanDelete())
}
func TestOrder_CanDelete_NonDraft(t *testing.T) {
o := &order.Order{}
o.IsDraft = false
assert.False(t, o.CanDelete())
}
func TestOrder_CanUpdate_StatusChange_NonDraft(t *testing.T) {
o := &order.Order{Status: enum.PENDING}
o.IsDraft = false
set := &order.Order{Status: enum.PAID}
ok, returned := o.CanUpdate(set)
assert.True(t, ok)
assert.Equal(t, enum.PAID, returned.(*order.Order).Status)
}
func TestOrder_CanUpdate_SameStatus_NonDraft(t *testing.T) {
o := &order.Order{Status: enum.PENDING}
o.IsDraft = false
set := &order.Order{Status: enum.PENDING}
ok, _ := o.CanUpdate(set)
// !r.IsDraft && r.Status == set.Status → first branch false → returns r.IsDraft = false
assert.False(t, ok)
}
func TestOrder_CanUpdate_Draft(t *testing.T) {
o := &order.Order{Status: enum.PENDING}
o.IsDraft = true
set := &order.Order{Status: enum.PAID}
ok, _ := o.CanUpdate(set)
// !r.IsDraft = false → first branch false → returns r.IsDraft = true
assert.True(t, ok)
}
func TestOrder_Quantity(t *testing.T) {
o := &order.Order{
Purchases: []*purchase_resource.PurchaseResource{{}, {}},
}
// Quantity = len(Purchases) + len(Purchases) (note: there is a bug in source: uses Purchases twice)
assert.Equal(t, 4, o.Quantity())
}
func TestOrder_Quantity_Empty(t *testing.T) {
o := &order.Order{}
assert.Equal(t, 0, o.Quantity())
}
func TestOrder_SetName(t *testing.T) {
o := &order.Order{}
o.UUID = "order-uuid"
o.SetName("ignored")
// Name is generated from UUID, not from the argument
assert.Contains(t, o.Name, "order-uuid")
assert.Contains(t, o.Name, "_order_")
}
func TestOrder_GetAccessor(t *testing.T) {
o := &order.Order{}
acc := o.GetAccessor(&tools.APIRequest{})
assert.NotNil(t, acc)
}
func TestOrder_GetAccessor_NilRequest(t *testing.T) {
o := &order.Order{}
acc := o.GetAccessor(nil)
assert.NotNil(t, acc)
}

View File

@@ -8,22 +8,6 @@ import (
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
// now write a go enum for the state partner with self, blacklist, partner
type PeerState int
const (
OFFLINE PeerState = iota
ONLINE
)
func (m PeerState) String() string {
return [...]string{"NONE", "SELF", "PARTNER", "BLACKLIST"}[m]
}
func (m PeerState) EnumIndex() int {
return int(m)
}
type PeerRelation int type PeerRelation int
const ( const (
@@ -69,7 +53,6 @@ type Peer struct {
NATSAddress string `json:"nats_address" bson:"nats_address" validate:"required"` NATSAddress string `json:"nats_address" bson:"nats_address" validate:"required"`
WalletAddress string `json:"wallet_address" bson:"wallet_address" validate:"required"` // WalletAddress is the wallet address of the peer WalletAddress string `json:"wallet_address" bson:"wallet_address" validate:"required"` // WalletAddress is the wallet address of the peer
PublicKey string `json:"public_key" bson:"public_key" validate:"required"` // PublicKey is the public key of the peer PublicKey string `json:"public_key" bson:"public_key" validate:"required"` // PublicKey is the public key of the peer
State PeerState `json:"state" bson:"state" default:"0"`
Relation PeerRelation `json:"relation" bson:"relation" default:"0"` Relation PeerRelation `json:"relation" bson:"relation" default:"0"`
ServicesState map[string]int `json:"services_state,omitempty" bson:"services_state,omitempty"` ServicesState map[string]int `json:"services_state,omitempty" bson:"services_state,omitempty"`
FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried

View File

@@ -29,7 +29,7 @@ type PeerCache struct {
// urlFormat formats the URL of the peer with the data type API function // urlFormat formats the URL of the peer with the data type API function
func urlFormat(hostUrl string, dt tools.DataType) string { func urlFormat(hostUrl string, dt tools.DataType) string {
return hostUrl + "/" + strings.ReplaceAll(dt.API(), "oc-", "") return hostUrl + "/" + strings.ReplaceAll(dt.String(), "oc-", "")
} }
// checkPeerStatus checks the status of a peer // checkPeerStatus checks the status of a peer
@@ -44,7 +44,7 @@ func CheckPeerStatus(peerID string, appName string) (*Peer, bool) {
fmt.Println(url) fmt.Println(url)
state, services := api.CheckRemotePeer(url) state, services := api.CheckRemotePeer(url)
res.(*Peer).ServicesState = services // Update the services states of the peer res.(*Peer).ServicesState = services // Update the services states of the peer
access.UpdateOne(res, peerID) // Update the peer in the db access.UpdateOne(res.Serialize(res), peerID) // Update the peer in the db
return res.(*Peer), state != tools.DEAD && services[appName] == 0 // Return the peer and its status return res.(*Peer), state != tools.DEAD && services[appName] == 0 // Return the peer and its status
} }
@@ -62,7 +62,7 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
url := "" url := ""
// Check the status of the peer // Check the status of the peer
if mypeer, ok := CheckPeerStatus(peerID, dt.API()); !ok && mypeer != nil { if mypeer, ok := CheckPeerStatus(peerID, dt.String()); !ok && mypeer != nil {
// If the peer is not reachable, add the execution to the failed executions list // If the peer is not reachable, add the execution to the failed executions list
pexec := &PeerExecution{ pexec := &PeerExecution{
Method: method.String(), Method: method.String(),
@@ -72,18 +72,18 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
DataID: dataID, DataID: dataID,
} }
mypeer.AddExecution(*pexec) mypeer.AddExecution(*pexec)
NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db NewShallowAccessor().UpdateOne(mypeer.Serialize(mypeer), peerID) // Update the peer in the db
return map[string]interface{}{}, errors.New("peer is " + peerID + " not reachable") return map[string]interface{}{}, errors.New("peer is " + peerID + " not reachable")
} else { } else {
if mypeer == nil { if mypeer == nil {
return map[string]interface{}{}, errors.New("peer " + peerID + " not found") return map[string]interface{}{}, errors.New("peer " + peerID + " not found")
} }
// If the peer is reachable, launch the execution // If the peer is reachable, launch the execution
url = urlFormat((mypeer.APIUrl), dt) + path // Format the URL url = urlFormat((mypeer.APIUrl), dt) + path // Format the URL
tmp := mypeer.FailedExecution // Get the failed executions list tmp := mypeer.FailedExecution // Get the failed executions list
mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list
NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db NewShallowAccessor().UpdateOne(mypeer.Serialize(mypeer), peerID) // Update the peer in the db
for _, v := range tmp { // Retry the failed executions for _, v := range tmp { // Retry the failed executions
go p.Exec(v.Url, tools.ToMethod(v.Method), v.Body, caller) go p.Exec(v.Url, tools.ToMethod(v.Method), v.Body, caller)
} }
} }

View File

@@ -10,17 +10,18 @@ import (
) )
type peerMongoAccessor struct { type peerMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[*Peer] // AbstractAccessor contains the basic fields of an accessor (model, caller)
OverrideAuth bool OverrideAuth bool
} }
// New creates a new instance of the peerMongoAccessor // New creates a new instance of the peerMongoAccessor
func NewShallowAccessor() *peerMongoAccessor { func NewShallowAccessor() *peerMongoAccessor {
return &peerMongoAccessor{ return &peerMongoAccessor{
OverrideAuth: true, OverrideAuth: true,
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*Peer]{
Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
Type: tools.PEER, Type: tools.PEER,
New: func() *Peer { return &Peer{} },
}, },
} }
} }
@@ -28,10 +29,11 @@ func NewShallowAccessor() *peerMongoAccessor {
func NewAccessor(request *tools.APIRequest) *peerMongoAccessor { func NewAccessor(request *tools.APIRequest) *peerMongoAccessor {
return &peerMongoAccessor{ return &peerMongoAccessor{
OverrideAuth: false, OverrideAuth: false,
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*Peer]{
Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.PEER, Type: tools.PEER,
New: func() *Peer { return &Peer{} },
}, },
} }
} }
@@ -43,42 +45,7 @@ func (wfa *peerMongoAccessor) ShouldVerifyAuth() bool {
/* /*
* Nothing special here, just the basic CRUD operations * Nothing special here, just the basic CRUD operations
*/ */
func (a *peerMongoAccessor) GetObjectFilters(search string) *dbs.Filters {
func (wfa *peerMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, wfa)
}
func (wfa *peerMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set.(*Peer), id, wfa, &Peer{})
}
func (wfa *peerMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data.(*Peer), wfa)
}
func (wfa *peerMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, wfa)
}
func (dca *peerMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Peer](id, func(d utils.DBObject) (utils.DBObject, int, error) {
return d, 200, nil
}, dca)
}
func (wfa *peerMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Peer](func(d utils.DBObject) utils.ShallowDBObject {
return d
}, isDraft, wfa)
}
func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Peer](filters, search, wfa.GetDefaultFilter(search),
func(d utils.DBObject) utils.ShallowDBObject {
return d
}, isDraft, wfa)
}
func (a *peerMongoAccessor) GetDefaultFilter(search string) *dbs.Filters {
if i, err := strconv.Atoi(search); err == nil { if i, err := strconv.Atoi(search); err == nil {
m := map[string][]dbs.Filter{ // search by name if no filters are provided m := map[string][]dbs.Filter{ // search by name if no filters are provided
"relation": {{Operator: dbs.EQUAL.String(), Value: i}}, "relation": {{Operator: dbs.EQUAL.String(), Value: i}},
@@ -94,9 +61,6 @@ func (a *peerMongoAccessor) GetDefaultFilter(search string) *dbs.Filters {
search = "" search = ""
} }
return &dbs.Filters{ return &dbs.Filters{
And: map[string][]dbs.Filter{ // search by name if no filters are provided
"state": {{Operator: dbs.EQUAL.String(), Value: ONLINE.EnumIndex()}},
},
Or: map[string][]dbs.Filter{ // search by name if no filters are provided Or: map[string][]dbs.Filter{ // search by name if no filters are provided
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
"url": {{Operator: dbs.LIKE.String(), Value: search}}, "url": {{Operator: dbs.LIKE.String(), Value: search}},

View File

@@ -1,100 +0,0 @@
package peer_test
import (
"encoding/json"
"testing"
"cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockHTTPCaller mocks tools.HTTPCaller
type MockHTTPCaller struct {
mock.Mock
URLS map[tools.DataType]map[tools.METHOD]string
}
func (c *MockHTTPCaller) GetUrls() map[tools.DataType]map[tools.METHOD]string {
return c.URLS
}
func (m *MockHTTPCaller) CallPost(url, token string, body interface{}, types ...string) ([]byte, error) {
args := m.Called(url, token, body)
return args.Get(0).([]byte), args.Error(1)
}
func (m *MockHTTPCaller) CallGet(url, token string, types ...string) ([]byte, error) {
args := m.Called(url, token)
return args.Get(0).([]byte), args.Error(1)
}
func (m *MockHTTPCaller) CallDelete(url, token string) ([]byte, error) {
args := m.Called(url, token)
return args.Get(0).([]byte), args.Error(1)
}
func TestLaunchPeerExecution_PeerNotReachable(t *testing.T) {
cache := &peer.PeerCache{}
caller := &MockHTTPCaller{
URLS: map[tools.DataType]map[tools.METHOD]string{
tools.PEER: {
tools.POST: "/execute/:id",
},
},
}
exec, err := cache.LaunchPeerExecution("peer-id", "data-id", tools.PEER, tools.POST, map[string]string{"a": "b"}, caller)
assert.Nil(t, exec)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not reachable")
}
func TestExecSuccess(t *testing.T) {
cache := &peer.PeerCache{}
caller := &MockHTTPCaller{}
url := "http://mockpeer/resource"
response := map[string]interface{}{"result": "ok"}
data, _ := json.Marshal(response)
caller.On("CallPost", url, "", mock.Anything).Return(data, nil)
_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
assert.NoError(t, err)
caller.AssertExpectations(t)
}
func TestExecReturnsErrorField(t *testing.T) {
cache := &peer.PeerCache{}
caller := &MockHTTPCaller{}
url := "http://mockpeer/resource"
response := map[string]interface{}{"error": "something failed"}
data, _ := json.Marshal(response)
caller.On("CallPost", url, "", mock.Anything).Return(data, nil)
_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
assert.Error(t, err)
assert.Equal(t, "something failed", err.Error())
}
func TestExecInvalidJSON(t *testing.T) {
cache := &peer.PeerCache{}
caller := &MockHTTPCaller{}
url := "http://mockpeer/resource"
caller.On("CallPost", url, "", mock.Anything).Return([]byte("{invalid json}"), nil)
_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid character")
}
type mockAccessor struct {
loadOne func(string) (interface{}, int, error)
updateOne func(interface{}, string) error
}
func (m *mockAccessor) LoadOne(id string) (interface{}, int, error) {
return m.loadOne(id)
}
func (m *mockAccessor) UpdateOne(i interface{}, id string) error {
return m.updateOne(i, id)
}

View File

@@ -3,127 +3,107 @@ package peer_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/peer" "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
) )
type MockAccessor struct { // ---- PeerRelation ----
mock.Mock
utils.AbstractAccessor func TestPeerRelation_String(t *testing.T) {
assert.Equal(t, "UNKNOWN", peer.NONE.String())
assert.Equal(t, "SELF", peer.SELF.String())
assert.Equal(t, "PARTNER", peer.PARTNER.String())
assert.Equal(t, "BLACKLIST", peer.BLACKLIST.String())
} }
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func TestPeerRelation_Path(t *testing.T) {
args := m.Called(id) assert.Equal(t, "unknown", peer.NONE.Path())
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2) assert.Equal(t, "self", peer.SELF.Path())
assert.Equal(t, "partner", peer.PARTNER.Path())
assert.Equal(t, "blacklist", peer.BLACKLIST.Path())
} }
func (m *MockAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func TestPeerRelation_EnumIndex(t *testing.T) {
args := m.Called(set, id) assert.Equal(t, 0, peer.NONE.EnumIndex())
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2) assert.Equal(t, 1, peer.SELF.EnumIndex())
assert.Equal(t, 2, peer.PARTNER.EnumIndex())
assert.Equal(t, 3, peer.BLACKLIST.EnumIndex())
assert.Equal(t, 4, peer.PENDING_PARTNER.EnumIndex())
} }
func (m *MockAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func TestGetRelationPath(t *testing.T) {
args := m.Called(data) assert.Equal(t, 1, peer.GetRelationPath("self"))
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2) assert.Equal(t, 2, peer.GetRelationPath("partner"))
assert.Equal(t, 3, peer.GetRelationPath("blacklist"))
assert.Equal(t, -1, peer.GetRelationPath("nonexistent"))
} }
func (m *MockAccessor) LoadOne(id string) (utils.DBObject, int, error) { // ---- Peer model ----
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2) func TestPeer_VerifyAuth(t *testing.T) {
p := &peer.Peer{}
assert.True(t, p.VerifyAuth("get", nil))
assert.True(t, p.VerifyAuth("delete", &tools.APIRequest{}))
} }
func (m *MockAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { func TestPeer_CanDelete(t *testing.T) {
args := m.Called(isDraft) p := &peer.Peer{}
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2) assert.False(t, p.CanDelete())
} }
func (m *MockAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { func TestPeer_GetAccessor(t *testing.T) {
args := m.Called(filters, search, isDraft) p := &peer.Peer{}
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2) req := &tools.APIRequest{}
acc := p.GetAccessor(req)
assert.NotNil(t, acc)
} }
func newTestPeer() *peer.Peer { func TestPeer_AddExecution_Deduplication(t *testing.T) {
return &peer.Peer{ p := &peer.Peer{}
NATSAddress: "", exec := peer.PeerExecution{Method: "POST", Url: "http://peer/data", Body: "body1"}
StreamAddress: "127.0.0.1",
APIUrl: "http://localhost", p.AddExecution(exec)
WalletAddress: "0x123", assert.Len(t, p.FailedExecution, 1)
PublicKey: "pubkey",
Relation: peer.SELF, // Second add of same execution should not duplicate
} p.AddExecution(exec)
assert.Len(t, p.FailedExecution, 1)
// Different execution should be added
exec2 := peer.PeerExecution{Method: "GET", Url: "http://peer/data", Body: nil}
p.AddExecution(exec2)
assert.Len(t, p.FailedExecution, 2)
} }
func TestDeleteOne_UsingMock(t *testing.T) { func TestPeer_RemoveExecution(t *testing.T) {
mockAcc := new(MockAccessor) p := &peer.Peer{}
mockAcc.On("DeleteOne", "id").Return(newTestPeer(), 200, nil) exec1 := peer.PeerExecution{Method: "POST", Url: "http://peer/a", Body: nil}
exec2 := peer.PeerExecution{Method: "DELETE", Url: "http://peer/b", Body: nil}
obj, code, err := mockAcc.DeleteOne("id") p.AddExecution(exec1)
assert.NoError(t, err) p.AddExecution(exec2)
assert.Equal(t, 200, code) assert.Len(t, p.FailedExecution, 2)
assert.NotNil(t, obj)
mockAcc.AssertExpectations(t) p.RemoveExecution(exec1)
assert.Len(t, p.FailedExecution, 1)
assert.Equal(t, exec2, p.FailedExecution[0])
} }
func TestUpdateOne_UsingMock(t *testing.T) { func TestPeer_RemoveExecution_NotFound(t *testing.T) {
mockAcc := new(MockAccessor) p := &peer.Peer{}
peerObj := newTestPeer() exec := peer.PeerExecution{Method: "POST", Url: "http://peer/x", Body: nil}
mockAcc.On("UpdateOne", peerObj, "id").Return(peerObj, 200, nil) p.AddExecution(exec)
obj, code, err := mockAcc.UpdateOne(peerObj, "id") other := peer.PeerExecution{Method: "DELETE", Url: "http://other/x", Body: nil}
assert.NoError(t, err) p.RemoveExecution(other)
assert.Equal(t, 200, code) assert.Len(t, p.FailedExecution, 1) // unchanged
assert.Equal(t, peerObj, obj)
mockAcc.AssertExpectations(t)
} }
func TestStoreOne_UsingMock(t *testing.T) { func TestPeer_RemoveExecution_Empty(t *testing.T) {
mockAcc := new(MockAccessor) p := &peer.Peer{}
peerObj := newTestPeer() // Should not panic on empty list
mockAcc.On("StoreOne", peerObj).Return(peerObj, 200, nil) exec := peer.PeerExecution{Method: "GET", Url: "http://peer/x", Body: nil}
p.RemoveExecution(exec)
obj, code, err := mockAcc.StoreOne(peerObj) assert.Empty(t, p.FailedExecution)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, peerObj, obj)
mockAcc.AssertExpectations(t)
}
func TestLoadOne_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
mockAcc.On("LoadOne", "test-id").Return(newTestPeer(), 200, nil)
obj, code, err := mockAcc.LoadOne("test-id")
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.NotNil(t, obj)
mockAcc.AssertExpectations(t)
}
func TestLoadAll_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
expected := []utils.ShallowDBObject{newTestPeer()}
mockAcc.On("LoadAll", false).Return(expected, 200, nil)
objs, code, err := mockAcc.LoadAll(false)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, expected, objs)
mockAcc.AssertExpectations(t)
}
func TestSearch_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
filters := &dbs.Filters{}
expected := []utils.ShallowDBObject{newTestPeer()}
mockAcc.On("Search", filters, "test", false).Return(expected, 200, nil)
objs, code, err := mockAcc.Search(filters, "test", false)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, expected, objs)
mockAcc.AssertExpectations(t)
} }

View File

@@ -2,7 +2,6 @@ package resources
import ( import (
"errors" "errors"
"fmt"
"time" "time"
"cloud.o-forge.io/core/oc-lib/models/common/models" "cloud.o-forge.io/core/oc-lib/models/common/models"
@@ -173,7 +172,6 @@ func (r *PricedDataResource) GetPriceHT() (float64, error) {
if r.BookingConfiguration == nil { if r.BookingConfiguration == nil {
r.BookingConfiguration = &BookingConfiguration{} r.BookingConfiguration = &BookingConfiguration{}
} }
fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd)
now := time.Now() now := time.Now()
if r.BookingConfiguration.UsageStart == nil { if r.BookingConfiguration.UsageStart == nil {
r.BookingConfiguration.UsageStart = &now r.BookingConfiguration.UsageStart = &now

View File

@@ -10,17 +10,14 @@ import (
type ResourceInterface interface { type ResourceInterface interface {
utils.DBObject utils.DBObject
Trim()
FilterPeer(peerID string) *dbs.Filters FilterPeer(peerID string) *dbs.Filters
GetBookingModes() map[booking.BookingMode]*pricing.PricingVariation GetBookingModes() map[booking.BookingMode]*pricing.PricingVariation
ConvertToPricedResource(t tools.DataType, a *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, b *int, request *tools.APIRequest) (pricing.PricedItemITF, error) ConvertToPricedResource(t tools.DataType, a *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, b *int, request *tools.APIRequest) (pricing.PricedItemITF, error)
GetType() string GetType() string
GetSelectedInstance(selected *int) ResourceInstanceITF
ClearEnv() utils.DBObject ClearEnv() utils.DBObject
SetAllowedInstances(request *tools.APIRequest) SetAllowedInstances(request *tools.APIRequest, instance_id ...string)
AddInstances(instance ResourceInstanceITF) AddInstances(instance ResourceInstanceITF)
RefineResourceByPartnership(peerID string) ResourceInterface GetSelectedInstance(index *int) ResourceInstanceITF
GetSignature() []byte
} }
type ResourceInstanceITF interface { type ResourceInstanceITF interface {
@@ -29,17 +26,17 @@ type ResourceInstanceITF interface {
GetName() string GetName() string
StoreDraftDefault() StoreDraftDefault()
ClearEnv() ClearEnv()
FilterInstance(peerID string)
GetProfile(peerID string, partnershipIndex *int, buying *int, strategy *int) pricing.PricingProfileITF GetProfile(peerID string, partnershipIndex *int, buying *int, strategy *int) pricing.PricingProfileITF
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string)
ClearPeerGroups() ClearPeerGroups()
RefineResourceByPartnership(peerID string) (ResourceInstanceITF, bool)
} }
type ResourcePartnerITF interface { type ResourcePartnerITF interface {
RefineResourceByPartnership(peerID string) (ResourcePartnerITF, bool)
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() map[string][]string GetPeerGroups() map[string][]string
ClearPeerGroups() ClearPeerGroups()
GetProfile(buying *int, strategy *int) pricing.PricingProfileITF GetProfile(buying *int, strategy *int) pricing.PricingProfileITF
FilterPartnership(peerID string)
} }

View File

@@ -37,10 +37,7 @@ func (d *NativeTool) ClearEnv() utils.DBObject {
return d return d
} }
func (d *NativeTool) Trim() { func (w *NativeTool) SetAllowedInstances(request *tools.APIRequest, ids ...string) {
/* EMPTY */
}
func (w *NativeTool) SetAllowedInstances(request *tools.APIRequest) {
/* EMPTY */ /* EMPTY */
} }
@@ -55,8 +52,8 @@ func (w *NativeTool) ConvertToPricedResource(t tools.DataType, selectedInstance
}, nil }, nil
} }
func (abs *NativeTool) RefineResourceByPartnership(peerID string) ResourceInterface { func (r *NativeTool) GetSelectedInstance(selected *int) ResourceInstanceITF {
return abs return nil
} }
func InitNative() { func InitNative() {

View File

@@ -2,7 +2,6 @@ package resources
import ( import (
"errors" "errors"
"fmt"
"time" "time"
"cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/booking"
@@ -27,7 +26,9 @@ type PricedResource struct {
Variations []*pricing.PricingVariation `json:"pricing_variations" bson:"pricing_variations"` Variations []*pricing.PricingVariation `json:"pricing_variations" bson:"pricing_variations"`
CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"` CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty"` ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty"`
ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"` InstanceID string `json:"instance_id,omitempty" bson:"resource_id,omitempty"`
ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"`
} }
func (abs *PricedResource) GetQuantity() int { func (abs *PricedResource) GetQuantity() int {
@@ -46,6 +47,10 @@ func (abs *PricedResource) GetID() string {
return abs.ResourceID return abs.ResourceID
} }
func (abs *PricedResource) GetInstanceID() string {
return abs.InstanceID
}
func (abs *PricedResource) GetType() tools.DataType { func (abs *PricedResource) GetType() tools.DataType {
return abs.ResourceType return abs.ResourceType
} }
@@ -62,7 +67,6 @@ func (abs *PricedResource) IsPurchasable() bool {
} }
func (abs *PricedResource) IsBooked() bool { func (abs *PricedResource) IsBooked() bool {
return true // For dev purposes, prevent that DB objects that don't have a Pricing are considered as not booked
if abs.SelectedPricing == nil { if abs.SelectedPricing == nil {
return false return false
} }
@@ -126,7 +130,6 @@ func (r *PricedResource) GetPriceHT() (float64, error) {
if r.BookingConfiguration == nil { if r.BookingConfiguration == nil {
r.BookingConfiguration = &BookingConfiguration{} r.BookingConfiguration = &BookingConfiguration{}
} }
fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd)
if r.BookingConfiguration.UsageStart == nil { if r.BookingConfiguration.UsageStart == nil {
r.BookingConfiguration.UsageStart = &now r.BookingConfiguration.UsageStart = &now
} }

View File

@@ -9,12 +9,16 @@ import (
type PurchaseResource struct { type PurchaseResource struct {
utils.AbstractObject utils.AbstractObject
DestPeerID string `json:"dest_peer_id" bson:"dest_peer_id"` DestPeerID string `json:"dest_peer_id" bson:"dest_peer_id"`
PricedItem map[string]interface{} `json:"priced_item,omitempty" bson:"priced_item,omitempty" validate:"required"` PricedItem map[string]interface{} `json:"priced_item,omitempty" bson:"priced_item,omitempty" validate:"required"`
ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions ExecutionID string `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
EndDate *time.Time `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"`
ResourceID string `json:"resource_id" bson:"resource_id" validate:"required"` ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
ResourceType tools.DataType `json:"resource_type" bson:"resource_type" validate:"required"` EndDate *time.Time `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"`
ResourceID string `json:"resource_id" bson:"resource_id" validate:"required"`
InstanceID string `json:"instance_id,omitempty" bson:"instance_id,omitempty" validate:"required"` // could be a Compute or a Storage
ResourceType tools.DataType `json:"resource_type" bson:"resource_type" validate:"required"`
} }
func (d *PurchaseResource) GetAccessor(request *tools.APIRequest) utils.Accessor { func (d *PurchaseResource) GetAccessor(request *tools.APIRequest) utils.Accessor {

View File

@@ -3,23 +3,23 @@ package purchase_resource
import ( import (
"time" "time"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type PurchaseResourceMongoAccessor struct { type PurchaseResourceMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[*PurchaseResource] // AbstractAccessor contains the basic fields of an accessor (model, caller)
} }
// New creates a new instance of the bookingMongoAccessor // New creates a new instance of the bookingMongoAccessor
func NewAccessor(request *tools.APIRequest) *PurchaseResourceMongoAccessor { func NewAccessor(request *tools.APIRequest) *PurchaseResourceMongoAccessor {
return &PurchaseResourceMongoAccessor{ return &PurchaseResourceMongoAccessor{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*PurchaseResource]{
Logger: logs.CreateLogger(tools.PURCHASE_RESOURCE.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.PURCHASE_RESOURCE.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.PURCHASE_RESOURCE, Type: tools.PURCHASE_RESOURCE,
New: func() *PurchaseResource { return &PurchaseResource{} },
}, },
} }
} }
@@ -27,24 +27,8 @@ func NewAccessor(request *tools.APIRequest) *PurchaseResourceMongoAccessor {
/* /*
* Nothing special here, just the basic CRUD operations * Nothing special here, just the basic CRUD operations
*/ */
func (a *PurchaseResourceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, a)
}
func (a *PurchaseResourceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
return utils.GenericUpdateOne(set, id, a, &PurchaseResource{})
}
func (a *PurchaseResourceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
}
func (a *PurchaseResourceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
}
func (a *PurchaseResourceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *PurchaseResourceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*PurchaseResource](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne(id, a.New(), func(d utils.DBObject) (utils.DBObject, int, error) {
if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) { if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) {
utils.GenericDeleteOne(id, a) utils.GenericDeleteOne(id, a)
return nil, 404, nil return nil, 404, nil
@@ -53,15 +37,7 @@ func (a *PurchaseResourceMongoAccessor) LoadOne(id string) (utils.DBObject, int,
}, a) }, a)
} }
func (a *PurchaseResourceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { func (a *PurchaseResourceMongoAccessor) GetExec(isDraft bool) func(utils.DBObject) utils.ShallowDBObject {
return utils.GenericLoadAll[*PurchaseResource](a.getExec(), isDraft, a)
}
func (a *PurchaseResourceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*PurchaseResource](filters, search, (&PurchaseResource{}).GetObjectFilters(search), a.getExec(), isDraft, a)
}
func (a *PurchaseResourceMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject { return func(d utils.DBObject) utils.ShallowDBObject {
if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) { if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) {
utils.GenericDeleteOne(d.GetID(), a) utils.GenericDeleteOne(d.GetID(), a)

View File

@@ -1,7 +1,6 @@
package resources package resources
import ( import (
"crypto/sha256"
"encoding/json" "encoding/json"
"errors" "errors"
"slices" "slices"
@@ -20,33 +19,15 @@ import (
// AbstractResource is the struct containing all of the attributes commons to all ressources // AbstractResource is the struct containing all of the attributes commons to all ressources
type AbstractResource struct { type AbstractResource struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the resource
Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource
Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the resource
ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
Owners []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"` // Owners is the list of owners of the resource
UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
AllowedBookingModes map[booking.BookingMode]*pricing.PricingVariation `bson:"allowed_booking_modes" json:"allowed_booking_modes"`
Signature []byte `bson:"signature,omitempty" json:"signature,omitempty"`
}
func (r *AbstractResource) Unsign() { Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the resource
r.Signature = nil Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource
} Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the resource
ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
func (r *AbstractResource) Sign() { Owners []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"` // Owners is the list of owners of the resource
priv, err := tools.LoadKeyFromFilePrivate() // your node private key UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
if err != nil { AllowedBookingModes map[booking.BookingMode]*pricing.PricingVariation `bson:"allowed_booking_modes" json:"allowed_booking_modes"`
return
}
b, _ := json.Marshal(r)
hash := sha256.Sum256(b)
r.Signature, err = priv.Sign(hash[:])
}
func (abs *AbstractResource) GetSignature() []byte {
return abs.Signature
} }
func (abs *AbstractResource) FilterPeer(peerID string) *dbs.Filters { func (abs *AbstractResource) FilterPeer(peerID string) *dbs.Filters {
@@ -66,10 +47,6 @@ func (r *AbstractResource) GetBookingModes() map[booking.BookingMode]*pricing.Pr
return r.AllowedBookingModes return r.AllowedBookingModes
} }
func (r *AbstractResource) GetSelectedInstance(selected *int) ResourceInstanceITF {
return nil
}
func (r *AbstractResource) GetType() string { func (r *AbstractResource) GetType() string {
return tools.INVALID.String() return tools.INVALID.String()
} }
@@ -79,10 +56,7 @@ func (r *AbstractResource) StoreDraftDefault() {
} }
func (r *AbstractResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) { func (r *AbstractResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
if r.IsDraft != set.IsDrafted() && set.IsDrafted() { return r.IsDraft, set
return true, set // only state can be updated
}
return r.IsDraft != set.IsDrafted() && set.IsDrafted(), set
} }
func (r *AbstractResource) CanDelete() bool { func (r *AbstractResource) CanDelete() bool {
@@ -90,51 +64,47 @@ func (r *AbstractResource) CanDelete() bool {
} }
type AbstractInstanciatedResource[T ResourceInstanceITF] struct { type AbstractInstanciatedResource[T ResourceInstanceITF] struct {
AbstractResource // AbstractResource contains the basic fields of an object (id, name) AbstractResource // AbstractResource contains the basic fields of an object (id, name)
Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource
}
// PEERID found Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource
func (abs *AbstractInstanciatedResource[T]) RefineResourceByPartnership(peerID string) ResourceInterface {
instances := []T{}
for _, i := range instances {
i, ok := i.RefineResourceByPartnership(peerID)
if ok {
instances = append(instances, i.(T))
}
}
abs.Instances = instances
return abs
} }
func (abs *AbstractInstanciatedResource[T]) AddInstances(instance ResourceInstanceITF) { func (abs *AbstractInstanciatedResource[T]) AddInstances(instance ResourceInstanceITF) {
abs.Instances = append(abs.Instances, instance.(T)) abs.Instances = append(abs.Instances, instance.(T))
} }
func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) { func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType,
selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int,
selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {
instances := map[string]string{} instances := map[string]string{}
profiles := []pricing.PricingProfileITF{}
for _, instance := range abs.Instances { // TODO why it crush before ?
instances[instance.GetID()] = instance.GetName()
profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups)
}
var profile pricing.PricingProfileITF var profile pricing.PricingProfileITF
var inst ResourceInstanceITF
if t := abs.GetSelectedInstance(selectedInstance); t != nil { if t := abs.GetSelectedInstance(selectedInstance); t != nil {
inst = t
instances[t.GetID()] = t.GetName()
profile = t.GetProfile(request.PeerID, selectedPartnership, selectedBuyingStrategy, selectedStrategy) profile = t.GetProfile(request.PeerID, selectedPartnership, selectedBuyingStrategy, selectedStrategy)
} } else {
if profile == nil { for i, instance := range abs.Instances { // TODO why it crush before ?
if len(profiles) > 0 { if i == 0 {
profile = profiles[0] inst = instance
} else { }
if ok, _ := utils.IsMySelf(request.PeerID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{ instances[instance.GetID()] = instance.GetName()
Admin: true, profiles := instance.GetPricingsProfiles(request.PeerID, request.Groups)
})); ok { if len(profiles) > 0 {
profile = pricing.GetDefaultPricingProfile() profile = profiles[0]
} else { break
return nil, errors.New("no pricing profile found")
} }
} }
} }
if profile == nil {
/*if ok, _ := utils.IsMySelf(request.PeerID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); ok {*/
profile = pricing.GetDefaultPricingProfile()
/*} else {
return nil, errors.New("no pricing profile found")
}*/
}
variations := []*pricing.PricingVariation{} variations := []*pricing.PricingVariation{}
if selectedBookingModeIndex != nil && abs.AllowedBookingModes[booking.BookingMode(*selectedBookingModeIndex)] != nil { if selectedBookingModeIndex != nil && abs.AllowedBookingModes[booking.BookingMode(*selectedBookingModeIndex)] != nil {
variations = append(variations, abs.AllowedBookingModes[booking.BookingMode(*selectedBookingModeIndex)]) variations = append(variations, abs.AllowedBookingModes[booking.BookingMode(*selectedBookingModeIndex)])
@@ -143,6 +113,7 @@ func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.Data
Name: abs.Name, Name: abs.Name,
Logo: abs.Logo, Logo: abs.Logo,
ResourceID: abs.UUID, ResourceID: abs.UUID,
InstanceID: inst.GetID(),
ResourceType: t, ResourceType: t,
Quantity: 1, Quantity: 1,
InstancesRefs: instances, InstancesRefs: instances,
@@ -169,31 +140,23 @@ func (r *AbstractInstanciatedResource[T]) GetSelectedInstance(selected *int) Res
return nil return nil
} }
func (abs *AbstractInstanciatedResource[T]) SetAllowedInstances(request *tools.APIRequest) { func (abs *AbstractInstanciatedResource[T]) SetAllowedInstances(request *tools.APIRequest, instanceID ...string) {
if request != nil && request.PeerID == abs.CreatorID && request.PeerID != "" { if (request != nil && request.PeerID == abs.CreatorID && request.PeerID != "") || request.Admin {
return return
} }
abs.Instances = VerifyAuthAction(abs.Instances, request) abs.Instances = VerifyAuthAction(abs.Instances, request, instanceID...)
}
func (d *AbstractInstanciatedResource[T]) Trim() {
d.Type = d.GetType()
if ok, _ := utils.IsMySelf(d.CreatorID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); !ok {
for _, instance := range d.Instances {
instance.ClearPeerGroups()
}
}
} }
func (abs *AbstractInstanciatedResource[T]) VerifyAuth(callName string, request *tools.APIRequest) bool { func (abs *AbstractInstanciatedResource[T]) VerifyAuth(callName string, request *tools.APIRequest) bool {
return len(VerifyAuthAction(abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(callName, request) return len(VerifyAuthAction(abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(callName, request)
} }
func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T { func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest, instanceID ...string) []T {
instances := []T{} instances := []T{}
for _, instance := range baseInstance { for _, instance := range baseInstance {
if len(instanceID) > 0 && !slices.Contains(instanceID, instance.GetID()) {
continue
}
_, peerGroups := instance.GetPeerGroups() _, peerGroups := instance.GetPeerGroups()
for _, peers := range peerGroups { for _, peers := range peerGroups {
if request == nil { if request == nil {
@@ -201,11 +164,14 @@ func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.AP
} }
if grps, ok := peers[request.PeerID]; ok || config.GetConfig().Whitelist { if grps, ok := peers[request.PeerID]; ok || config.GetConfig().Whitelist {
if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) { if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) {
instance.FilterInstance(request.PeerID)
instances = append(instances, instance) instances = append(instances, instance)
// TODO filter Partners + Profiles...
continue continue
} }
for _, grp := range grps { for _, grp := range grps {
if slices.Contains(request.Groups, grp) { if slices.Contains(request.Groups, grp) {
instance.FilterInstance(request.PeerID)
instances = append(instances, instance) instances = append(instances, instance)
} }
} }
@@ -244,17 +210,15 @@ func NewInstance[T ResourcePartnerITF](name string) *ResourceInstance[T] {
} }
} }
func (abs *ResourceInstance[T]) RefineResourceByPartnership(peerID string) (ResourceInstanceITF, bool) { func (ri *ResourceInstance[T]) FilterInstance(peerID string) {
okk := false partnerships := []T{}
partners := []T{} for _, p := range ri.Partnerships {
for _, p := range abs.Partnerships { if p.GetPeerGroups()[peerID] != nil {
partner, ok := p.RefineResourceByPartnership(peerID) p.FilterPartnership(peerID)
if ok { partnerships = append(partnerships, p)
partners = append(partners, partner.(T))
okk = true
} }
} }
return abs, okk ri.Partnerships = partnerships
} }
func (ri *ResourceInstance[T]) ClearEnv() { func (ri *ResourceInstance[T]) ClearEnv() {
@@ -299,15 +263,15 @@ func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[stri
groups = append(groups, p.GetPeerGroups()) groups = append(groups, p.GetPeerGroups())
} }
if len(groups) == 0 { if len(groups) == 0 {
id, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{ pp, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true, Admin: true,
})) }))
if err != nil { if err != nil || pp == nil {
return partners, groups return partners, groups
} }
groups = []map[string][]string{ groups = []map[string][]string{
{ {
id: {"*"}, pp.GetID(): {"*"},
}, },
} }
// TODO make allow all only for self. // TODO make allow all only for self.
@@ -328,17 +292,14 @@ type ResourcePartnerShip[T pricing.PricingProfileITF] struct {
// to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy // to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy
} }
func (ri *ResourcePartnerShip[T]) RefineResourceByPartnership(peerID string) (ResourcePartnerITF, bool) { func (ri *ResourcePartnerShip[T]) FilterPartnership(peerID string) {
ok := false if ri.PeerGroups[peerID] == nil {
peerGrp := map[string][]string{} ri.PeerGroups = map[string][]string{}
for k, v := range ri.PeerGroups { } else {
if k == peerID { ri.PeerGroups = map[string][]string{
peerGrp[k] = v peerID: ri.PeerGroups[peerID],
ok = true
} }
} }
ri.PeerGroups = peerGrp
return ri, ok
} }
func (ri *ResourcePartnerShip[T]) GetProfile(buying *int, strategy *int) pricing.PricingProfileITF { func (ri *ResourcePartnerShip[T]) GetProfile(buying *int, strategy *int) pricing.PricingProfileITF {
@@ -387,14 +348,14 @@ func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []st
func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string { func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string {
if len(rp.PeerGroups) == 0 { if len(rp.PeerGroups) == 0 {
id, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{ pp, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true, Admin: true,
})) }))
if err != nil { if err != nil || pp == nil {
return rp.PeerGroups return rp.PeerGroups
} }
return map[string][]string{ return map[string][]string{
id: {"*"}, pp.GetID(): {"*"},
} }
} }
return rp.PeerGroups return rp.PeerGroups

View File

@@ -11,8 +11,8 @@ import (
) )
type ResourceMongoAccessor[T ResourceInterface] struct { type ResourceMongoAccessor[T ResourceInterface] struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[ResourceInterface] // AbstractAccessor contains the basic fields of an accessor (model, caller)
generateData func() utils.DBObject generateData func() utils.DBObject
} }
// New creates a new instance of the computeMongoAccessor // New creates a new instance of the computeMongoAccessor
@@ -25,10 +25,27 @@ func NewAccessor[T ResourceInterface](t tools.DataType, request *tools.APIReques
return nil return nil
} }
return &ResourceMongoAccessor[T]{ return &ResourceMongoAccessor[T]{
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[ResourceInterface]{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(t.String()), // Create a logger with the data type
Request: request, Request: request,
Type: t, Type: t,
New: func() ResourceInterface {
switch t {
case tools.COMPUTE_RESOURCE:
return &ComputeResource{}
case tools.STORAGE_RESOURCE:
return &StorageResource{}
case tools.PROCESSING_RESOURCE:
return &ProcessingResource{}
case tools.WORKFLOW_RESOURCE:
return &WorkflowResource{}
case tools.DATA_RESOURCE:
return &DataResource{}
case tools.NATIVE_TOOL:
return &NativeTool{}
}
return nil
},
}, },
generateData: g, generateData: g,
} }
@@ -37,35 +54,23 @@ func NewAccessor[T ResourceInterface](t tools.DataType, request *tools.APIReques
/* /*
* Nothing special here, just the basic CRUD operations * Nothing special here, just the basic CRUD operations
*/ */
func (dca *ResourceMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, error) {
return utils.GenericDeleteOne(id, dca)
}
func (dca *ResourceMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (dca *ResourceMongoAccessor[T]) UpdateOne(set map[string]interface{}, id string) (utils.DBObject, int, error) {
if dca.GetType() == tools.COMPUTE_RESOURCE { if dca.GetType() == tools.COMPUTE_RESOURCE {
return nil, 404, errors.New("can't update a non existing computing units resource not reported onto compute units catalog") return nil, 404, errors.New("can't update a non existing computing units resource not reported onto compute units catalog")
} }
set.(T).Trim() return utils.GenericUpdateOne(set, id, dca)
}
if d, c, err := utils.GenericUpdateOne(set, id, dca, dca.generateData()); err != nil { func (dca *ResourceMongoAccessor[T]) ShouldVerifyAuth() bool {
return d, c, err return false // TEMP : by pass
} else {
d.Unsign()
d.Sign()
return utils.GenericUpdateOne(set, id, dca, dca.generateData())
}
} }
func (dca *ResourceMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (dca *ResourceMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
if dca.GetType() == tools.COMPUTE_RESOURCE { if dca.GetType() == tools.COMPUTE_RESOURCE {
return nil, 404, errors.New("can't create a non existing computing units resource not reported onto compute units catalog") return nil, 404, errors.New("can't create a non existing computing units resource not reported onto compute units catalog")
} }
data.(T).Trim() return utils.GenericStoreOne(data, dca)
d, c, err := utils.GenericStoreOne(data, dca)
if err != nil {
return d, c, err
}
return dca.UpdateOne(d, d.GetID())
} }
func (dca *ResourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { func (dca *ResourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
@@ -75,18 +80,8 @@ func (dca *ResourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObjec
return dca.StoreOne(data) return dca.StoreOne(data)
} }
func (dca *ResourceMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) {
d.(T).SetAllowedInstances(dca.Request)
return d, 200, nil
}, dca)
}
func (wfa *ResourceMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { func (wfa *ResourceMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[T](func(d utils.DBObject) utils.ShallowDBObject { return utils.GenericLoadAll[T](wfa.GetExec(isDraft), isDraft, wfa)
d.(T).SetAllowedInstances(wfa.Request)
return d
}, isDraft, wfa)
} }
func (wfa *ResourceMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { func (wfa *ResourceMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
@@ -96,14 +91,21 @@ func (wfa *ResourceMongoAccessor[T]) Search(filters *dbs.Filters, search string,
return d return d
}, isDraft, wfa) }, isDraft, wfa)
} }
return utils.GenericSearch[T](filters, search, wfa.getResourceFilter(search), return utils.GenericSearch[T](filters, search, wfa.GetObjectFilters(search),
func(d utils.DBObject) utils.ShallowDBObject { func(d utils.DBObject) utils.ShallowDBObject {
d.(T).SetAllowedInstances(wfa.Request) d.(T).SetAllowedInstances(wfa.Request)
return d return d
}, isDraft, wfa) }, isDraft, wfa)
} }
func (abs *ResourceMongoAccessor[T]) getResourceFilter(search string) *dbs.Filters { func (a *ResourceMongoAccessor[T]) GetExec(isDraft bool) func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject {
d.(T).SetAllowedInstances(a.Request)
return d
}
}
func (abs *ResourceMongoAccessor[T]) GetObjectFilters(search string) *dbs.Filters {
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
"abstractintanciatedresource.abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractintanciatedresource.abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},

View File

@@ -64,7 +64,8 @@ func TestPricedComputeResource_GetPriceHT(t *testing.T) {
end := start.Add(1 * time.Hour) end := start.Add(1 * time.Hour)
r := resources.PricedComputeResource{ r := resources.PricedComputeResource{
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
ResourceID: "comp456", ResourceID: "comp456",
SelectedPricing: &MockPricingProfile{ReturnCost: 1.0},
BookingConfiguration: &resources.BookingConfiguration{ BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start, UsageStart: &start,
UsageEnd: &end, UsageEnd: &end,

View File

@@ -92,6 +92,7 @@ func TestPricedDataResource_GetPriceHT(t *testing.T) {
r := &resources.PricedDataResource{ r := &resources.PricedDataResource{
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
SelectedPricing: pricingProfile,
BookingConfiguration: &resources.BookingConfiguration{ BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &now, UsageStart: &now,
UsageEnd: &later, UsageEnd: &later,

View File

@@ -105,14 +105,10 @@ func TestGetPriceHT(t *testing.T) {
assert.Equal(t, 0.0, price) assert.Equal(t, 0.0, price)
}) })
t.Run("uses first profile if selected is nil", func(t *testing.T) { t.Run("defaults BookingConfiguration when nil", func(t *testing.T) {
start := time.Now() mock := &MockPricingProfile{ReturnCost: 42.0}
end := start.Add(30 * time.Minute)
r := &resources.PricedResource{ r := &resources.PricedResource{
BookingConfiguration: &resources.BookingConfiguration{ SelectedPricing: mock,
UsageStart: &start,
UsageEnd: &end,
},
} }
price, err := r.GetPriceHT() price, err := r.GetPriceHT()
require.NoError(t, err) require.NoError(t, err)

View File

@@ -16,10 +16,11 @@ type MockInstance struct {
resources.ResourceInstance[*MockPartner] resources.ResourceInstance[*MockPartner]
} }
func (m *MockInstance) GetID() string { return m.ID } func (m *MockInstance) GetID() string { return m.ID }
func (m *MockInstance) GetName() string { return m.Name } func (m *MockInstance) GetName() string { return m.Name }
func (m *MockInstance) ClearEnv() {} func (m *MockInstance) ClearEnv() {}
func (m *MockInstance) ClearPeerGroups() {} func (m *MockInstance) ClearPeerGroups() {}
func (m *MockPartner) FilterPartnership(peerID string) {}
func (m *MockInstance) GetProfile(peerID string, a *int, b *int, c *int) pricing.PricingProfileITF { func (m *MockInstance) GetProfile(peerID string, a *int, b *int, c *int) pricing.PricingProfileITF {
return nil return nil
} }
@@ -36,10 +37,6 @@ type MockPartner struct {
groups map[string][]string groups map[string][]string
} }
func (abs *MockPartner) RefineResourceByPartnership(peerID string) (resources.ResourcePartnerITF, bool) {
return nil, false
}
func (m *MockPartner) GetProfile(buying *int, strategy *int) pricing.PricingProfileITF { func (m *MockPartner) GetProfile(buying *int, strategy *int) pricing.PricingProfileITF {
return nil return nil
} }
@@ -48,6 +45,7 @@ func (m *MockPartner) GetPeerGroups() map[string][]string {
return m.groups return m.groups
} }
func (m *MockPartner) ClearPeerGroups() {} func (m *MockPartner) ClearPeerGroups() {}
func (m *MockPartner) GetPricingsProfiles(string, []string) []pricing.PricingProfileITF { func (m *MockPartner) GetPricingsProfiles(string, []string) []pricing.PricingProfileITF {
return nil return nil
} }
@@ -107,9 +105,8 @@ type FakeResource struct {
resources.AbstractInstanciatedResource[*MockInstance] resources.AbstractInstanciatedResource[*MockInstance]
} }
func (f *FakeResource) Trim() {} func (f *FakeResource) SetAllowedInstances(*tools.APIRequest, ...string) {}
func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {} func (f *FakeResource) VerifyAuth(string, *tools.APIRequest) bool { return true }
func (f *FakeResource) VerifyAuth(string, *tools.APIRequest) bool { return true }
func TestNewAccessor_ReturnsValid(t *testing.T) { func TestNewAccessor_ReturnsValid(t *testing.T) {
acc := resources.NewAccessor[*FakeResource](tools.COMPUTE_RESOURCE, &tools.APIRequest{}, func() utils.DBObject { acc := resources.NewAccessor[*FakeResource](tools.COMPUTE_RESOURCE, &tools.APIRequest{}, func() utils.DBObject {

View File

@@ -41,13 +41,6 @@ func TestWorkflowResource_ClearEnv(t *testing.T) {
w := &resources.WorkflowResource{} w := &resources.WorkflowResource{}
assert.Equal(t, w, w.ClearEnv()) assert.Equal(t, w, w.ClearEnv())
} }
func TestWorkflowResource_Trim(t *testing.T) {
w := &resources.WorkflowResource{}
w.Trim()
// nothing to assert; just test that it doesn't panic
}
func TestWorkflowResource_SetAllowedInstances(t *testing.T) { func TestWorkflowResource_SetAllowedInstances(t *testing.T) {
w := &resources.WorkflowResource{} w := &resources.WorkflowResource{}
w.SetAllowedInstances(&tools.APIRequest{}) w.SetAllowedInstances(&tools.APIRequest{})

View File

@@ -19,10 +19,6 @@ func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor
return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} }) return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} })
} }
func (abs *WorkflowResource) RefineResourceByPartnership(peerID string) ResourceInterface {
return abs
}
func (r *WorkflowResource) AddInstances(instance ResourceInstanceITF) { func (r *WorkflowResource) AddInstances(instance ResourceInstanceITF) {
} }
@@ -34,11 +30,12 @@ func (d *WorkflowResource) ClearEnv() utils.DBObject {
return d return d
} }
func (d *WorkflowResource) Trim() { func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest, ids ...string) {
/* EMPTY */ /* EMPTY */
} }
func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) {
/* EMPTY */ func (r *WorkflowResource) GetSelectedInstance(selected *int) ResourceInstanceITF {
return nil
} }
func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) { func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {

View File

@@ -1,17 +1,17 @@
package models package models
import ( import (
"strconv"
"testing" "testing"
"cloud.o-forge.io/core/oc-lib/models" "cloud.o-forge.io/core/oc-lib/models"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestModel_ReturnsValidInstances(t *testing.T) { func TestModel_ReturnsValidInstances(t *testing.T) {
for name, _ := range models.ModelsCatalog { for name := range models.ModelsCatalog {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
modelInt, _ := strconv.Atoi(name) modelInt := tools.FromString(name)
obj := models.Model(modelInt) obj := models.Model(modelInt)
assert.NotNil(t, obj, "Model() returned nil for valid model name %s", name) assert.NotNil(t, obj, "Model() returned nil for valid model name %s", name)
}) })

View File

@@ -1,7 +1,10 @@
package utils package utils
import ( import (
"crypto/sha256"
"encoding/json" "encoding/json"
"errors"
"slices"
"time" "time"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
@@ -37,20 +40,43 @@ type AbstractObject struct {
UpdaterID string `json:"updater_id,omitempty" bson:"updater_id,omitempty"` UpdaterID string `json:"updater_id,omitempty" bson:"updater_id,omitempty"`
UserUpdaterID string `json:"user_updater_id,omitempty" bson:"user_updater_id,omitempty"` UserUpdaterID string `json:"user_updater_id,omitempty" bson:"user_updater_id,omitempty"`
AccessMode AccessMode `json:"access_mode" bson:"access_mode" default:"0"` AccessMode AccessMode `json:"access_mode" bson:"access_mode" default:"0"`
Signature []byte `bson:"signature,omitempty" json:"signature,omitempty"`
} }
func (ri *AbstractObject) GetAccessor(request *tools.APIRequest) Accessor { func (ri *AbstractObject) GetAccessor(request *tools.APIRequest) Accessor {
return nil return nil
} }
func (r *AbstractObject) Unsign() {} func (r *AbstractObject) Unsign() {
r.Signature = nil
}
func (r *AbstractObject) Sign() {} func (r *AbstractObject) Sign() {
priv, err := tools.LoadKeyFromFilePrivate() // your node private key
if err != nil {
return
}
b, _ := json.Marshal(r.DeepCopy())
hash := sha256.Sum256(b)
r.Signature, err = priv.Sign(hash[:])
}
func (r *AbstractObject) SetID(id string) { func (r *AbstractObject) SetID(id string) {
r.UUID = id r.UUID = id
} }
func (r *AbstractObject) DeepCopy() *AbstractObject {
var obj AbstractObject
b, err := json.Marshal(r)
if err != nil {
return nil
}
if err := json.Unmarshal(b, &obj); err != nil {
return nil
}
return &obj
}
func (r *AbstractObject) SetName(name string) { func (r *AbstractObject) SetName(name string) {
r.Name = name r.Name = name
} }
@@ -82,6 +108,10 @@ func (ao AbstractObject) GetID() string {
return ao.UUID return ao.UUID
} }
func (ao AbstractObject) GetSignature() []byte {
return ao.Signature
}
// GetName implements ShallowDBObject. // GetName implements ShallowDBObject.
func (ao AbstractObject) GetName() string { func (ao AbstractObject) GetName() string {
return ao.Name return ao.Name
@@ -103,7 +133,7 @@ func (ao *AbstractObject) UpToDate(user string, peer string, create bool) {
} }
func (ao *AbstractObject) VerifyAuth(callName string, request *tools.APIRequest) bool { func (ao *AbstractObject) VerifyAuth(callName string, request *tools.APIRequest) bool {
return (ao.AccessMode == Public && callName == "get") || request.Admin || (request != nil && ao.CreatorID == request.PeerID && request.PeerID != "") return (ao.AccessMode == Public && callName == "get") || (request != nil && (request.Admin || (ao.CreatorID == request.PeerID && request.PeerID != "")))
} }
// TODO : check write per auth // TODO : check write per auth
@@ -137,50 +167,104 @@ func (dma *AbstractObject) Serialize(obj DBObject) map[string]interface{} {
return m return m
} }
type AbstractAccessor struct { type AbstractAccessor[T DBObject] struct {
Logger zerolog.Logger // Logger is the logger of the accessor, it's a specilized logger for the accessor Logger zerolog.Logger // Logger is the logger of the accessor, it's a specilized logger for the accessor
Type tools.DataType // Type is the data type of the accessor Type tools.DataType // Type is the data type of the accessor
Request *tools.APIRequest // Caller is the http caller of the accessor (optionnal) only need in a peer connection Request *tools.APIRequest // Caller is the http caller of the accessor (optionnal) only need in a peer connection
ResourceModelAccessor Accessor ResourceModelAccessor Accessor
New func() T
NotImplemented []string
} }
func (r *AbstractAccessor) ShouldVerifyAuth() bool { func (r *AbstractAccessor[T]) ShouldVerifyAuth() bool {
return true return true
} }
func (r *AbstractAccessor) GetRequest() *tools.APIRequest { func (r *AbstractAccessor[T]) GetRequest() *tools.APIRequest {
return r.Request return r.Request
} }
func (dma *AbstractAccessor) GetUser() string { func (dma *AbstractAccessor[T]) GetUser() string {
if dma.Request == nil { if dma.Request == nil {
return "" return ""
} }
return dma.Request.Username return dma.Request.Username
} }
func (dma *AbstractAccessor) GetPeerID() string { func (dma *AbstractAccessor[T]) GetPeerID() string {
if dma.Request == nil { if dma.Request == nil {
return "" return ""
} }
return dma.Request.PeerID return dma.Request.PeerID
} }
func (dma *AbstractAccessor) GetGroups() []string { func (dma *AbstractAccessor[T]) GetGroups() []string {
if dma.Request == nil { if dma.Request == nil {
return []string{} return []string{}
} }
return dma.Request.Groups return dma.Request.Groups
} }
func (dma *AbstractAccessor) GetLogger() *zerolog.Logger { func (dma *AbstractAccessor[T]) GetLogger() *zerolog.Logger {
return &dma.Logger return &dma.Logger
} }
func (dma *AbstractAccessor) GetType() tools.DataType { func (dma *AbstractAccessor[T]) GetType() tools.DataType {
return dma.Type return dma.Type
} }
func (dma *AbstractAccessor) GetCaller() *tools.HTTPCaller { func (dma *AbstractAccessor[T]) GetCaller() *tools.HTTPCaller {
if dma.Request == nil { if dma.Request == nil {
return nil return nil
} }
return dma.Request.Caller return dma.Request.Caller
} }
/*
* Nothing special here, just the basic CRUD operations
*/
func (a *AbstractAccessor[T]) DeleteOne(id string) (DBObject, int, error) {
if len(a.NotImplemented) > 0 && slices.Contains(a.NotImplemented, "DeleteOne") {
return nil, 404, errors.New("not implemented")
}
return GenericDeleteOne(id, a)
}
func (a *AbstractAccessor[T]) UpdateOne(set map[string]interface{}, id string) (DBObject, int, error) {
if len(a.NotImplemented) > 0 && slices.Contains(a.NotImplemented, "UpdateOne") {
return nil, 404, errors.New("not implemented")
}
// should verify if a source is existing...
return GenericUpdateOne(set, id, a)
}
func (a *AbstractAccessor[T]) StoreOne(data DBObject) (DBObject, int, error) {
if len(a.NotImplemented) > 0 && slices.Contains(a.NotImplemented, "StoreOne") {
return nil, 404, errors.New("not implemented")
}
return GenericStoreOne(data.(T), a)
}
func (a *AbstractAccessor[T]) CopyOne(data DBObject) (DBObject, int, error) {
if len(a.NotImplemented) > 0 && slices.Contains(a.NotImplemented, "CopyOne") {
return nil, 404, errors.New("not implemented")
}
return GenericStoreOne(data.(T), a)
}
func (a *AbstractAccessor[T]) LoadOne(id string) (DBObject, int, error) {
return GenericLoadOne(id, a.New(), func(d DBObject) (DBObject, int, error) {
return d, 200, nil
}, a)
}
func (a *AbstractAccessor[T]) LoadAll(isDraft bool) ([]ShallowDBObject, int, error) {
return GenericLoadAll[T](a.GetExec(isDraft), isDraft, a)
}
func (a *AbstractAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]ShallowDBObject, int, error) {
return GenericSearch[T](filters, search, a.New().GetObjectFilters(search), a.GetExec(isDraft), isDraft, a)
}
func (a *AbstractAccessor[T]) GetExec(isDraft bool) func(DBObject) ShallowDBObject {
return func(d DBObject) ShallowDBObject {
return d
}
}

View File

@@ -1,6 +1,7 @@
package utils package utils
import ( import (
"encoding/json"
"errors" "errors"
"os" "os"
@@ -34,6 +35,8 @@ func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) {
data.SetID(data.GetID()) data.SetID(data.GetID())
data.StoreDraftDefault() data.StoreDraftDefault()
data.UpToDate(a.GetUser(), a.GetPeerID(), true) data.UpToDate(a.GetUser(), a.GetPeerID(), true)
data.Unsign()
data.Sign()
f := dbs.Filters{ f := dbs.Filters{
Or: map[string][]dbs.Filter{ Or: map[string][]dbs.Filter{
"abstractresource.abstractobject.name": {{ "abstractresource.abstractobject.name": {{
@@ -84,29 +87,42 @@ func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) {
return res, 200, nil return res, 200, nil
} }
// GenericLoadOne loads one object from the database (generic) func ModelGenericUpdateOne(change map[string]interface{}, id string, a Accessor) (DBObject, map[string]interface{}, int, error) {
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObject, int, error) {
r, c, err := a.LoadOne(id) r, c, err := a.LoadOne(id)
if err != nil { if err != nil {
return nil, c, err return nil, nil, c, err
} }
ok, newSet := r.CanUpdate(set) obj := &AbstractObject{}
b, _ := json.Marshal(r)
json.Unmarshal(b, obj)
ok, r := r.CanUpdate(obj)
if !ok { if !ok {
return nil, 403, errors.New("you are not allowed to delete :" + a.GetType().String()) return nil, nil, 403, errors.New("you are not allowed to update :" + a.GetType().String())
} }
set = newSet
r.UpToDate(a.GetUser(), a.GetPeerID(), false) r.UpToDate(a.GetUser(), a.GetPeerID(), false)
if a.ShouldVerifyAuth() && !r.VerifyAuth("update", a.GetRequest()) { if a.GetPeerID() == r.GetCreatorID() {
return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String()) r.Unsign()
r.Sign()
} }
change := set.Serialize(set) // get the changes if a.ShouldVerifyAuth() && !r.VerifyAuth("update", a.GetRequest()) {
loaded := r.Serialize(r) // get the loaded object return nil, nil, 403, errors.New("you are not allowed to access :" + a.GetType().String())
}
loaded := r.Serialize(r) // get the loaded object
for k, v := range change { // apply the changes, with a flatten method for k, v := range change { // apply the changes, with a flatten method
loaded[k] = v loaded[k] = v
} }
id, code, err := mongo.MONGOService.UpdateOne(new.Deserialize(loaded, new), id, a.GetType().String()) return r, loaded, 200, nil
}
// GenericLoadOne loads one object from the database (generic)
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
func GenericUpdateOne(change map[string]interface{}, id string, a Accessor) (DBObject, int, error) {
obj, loaded, c, err := ModelGenericUpdateOne(change, id, a)
if err != nil {
return nil, c, err
}
id, code, err := mongo.MONGOService.UpdateOne(obj.Deserialize(loaded, obj), id, a.GetType().String())
if err != nil { if err != nil {
a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error()) a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
return nil, code, err return nil, code, err
@@ -114,13 +130,15 @@ func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObje
return a.LoadOne(id) return a.LoadOne(id)
} }
func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, error), a Accessor) (DBObject, int, error) { func GenericLoadOne[T DBObject](id string, data T, f func(DBObject) (DBObject, int, error), a Accessor) (DBObject, int, error) {
var data T
res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String()) res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String())
if err != nil { if err != nil {
return nil, code, err return nil, code, err
} }
res_mongo.Decode(&data) if err = res_mongo.Decode(data); err != nil {
return nil, 400, err
}
if a.ShouldVerifyAuth() && !data.VerifyAuth("get", a.GetRequest()) { if a.ShouldVerifyAuth() && !data.VerifyAuth("get", a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String()) return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String())
} }
@@ -170,24 +188,24 @@ func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, er
return a.LoadOne(id) return a.LoadOne(id)
} }
func GetMySelf(wfa Accessor) (string, error) { func GetMySelf(wfa Accessor) (ShallowDBObject, error) {
id, err := GenerateNodeID() datas, _, _ := wfa.Search(&dbs.Filters{
if err != nil { And: map[string][]dbs.Filter{
return "", err "relation": {{Operator: dbs.EQUAL.String(), Value: 1}},
},
}, "", false)
if len(datas) > 0 && datas[0] != nil {
return datas[0], nil
} }
datas, _, _ := wfa.Search(nil, id, false) return nil, errors.New("peer not found")
if len(datas) > 0 {
return datas[0].GetID(), nil
}
return "", errors.New("peer not found")
} }
func IsMySelf(peerID string, wfa Accessor) (bool, string) { func IsMySelf(peerID string, wfa Accessor) (bool, string) {
id, err := GetMySelf(wfa) pp, err := GetMySelf(wfa)
if err != nil { if err != nil || pp == nil {
return false, "" return false, ""
} }
return peerID == id, id return peerID == pp.GetID(), pp.GetID()
} }
func GenerateNodeID() (string, error) { func GenerateNodeID() (string, error) {

View File

@@ -34,6 +34,8 @@ type DBObject interface {
Deserialize(j map[string]interface{}, obj DBObject) DBObject Deserialize(j map[string]interface{}, obj DBObject) DBObject
Sign() Sign()
Unsign() Unsign()
GetSignature() []byte
GetObjectFilters(search string) *dbs.Filters
} }
// Accessor is an interface that defines the basic methods for an Accessor // Accessor is an interface that defines the basic methods for an Accessor
@@ -51,6 +53,7 @@ type Accessor interface {
CopyOne(data DBObject) (DBObject, int, error) CopyOne(data DBObject) (DBObject, int, error)
StoreOne(data DBObject) (DBObject, int, error) StoreOne(data DBObject) (DBObject, int, error)
LoadAll(isDraft bool) ([]ShallowDBObject, int, error) LoadAll(isDraft bool) ([]ShallowDBObject, int, error)
UpdateOne(set DBObject, id string) (DBObject, int, error) UpdateOne(set map[string]interface{}, id string) (DBObject, int, error)
Search(filters *dbs.Filters, search string, isDraft bool) ([]ShallowDBObject, int, error) Search(filters *dbs.Filters, search string, isDraft bool) ([]ShallowDBObject, int, error)
GetExec(isDraft bool) func(DBObject) ShallowDBObject
} }

View File

@@ -1,128 +1,263 @@
package models_test package utils_test
import ( import (
"testing" "testing"
"time"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestGenerateID(t *testing.T) { // ---- AbstractObject ----
ao := &utils.AbstractObject{}
ao.GenerateID() func TestAbstractObject_GetID(t *testing.T) {
assert.NotEmpty(t, ao.UUID) obj := &utils.AbstractObject{UUID: "abc-123"}
_, err := uuid.Parse(ao.UUID) assert.Equal(t, "abc-123", obj.GetID())
assert.NoError(t, err)
} }
func TestStoreDraftDefault(t *testing.T) { func TestAbstractObject_GetName(t *testing.T) {
ao := &utils.AbstractObject{IsDraft: true} obj := &utils.AbstractObject{Name: "test-name"}
ao.StoreDraftDefault() assert.Equal(t, "test-name", obj.GetName())
assert.False(t, ao.IsDraft)
} }
func TestCanUpdate(t *testing.T) { func TestAbstractObject_GetCreatorID(t *testing.T) {
ao := &utils.AbstractObject{} obj := &utils.AbstractObject{CreatorID: "peer-xyz"}
res, set := ao.CanUpdate(nil) assert.Equal(t, "peer-xyz", obj.GetCreatorID())
assert.True(t, res)
assert.Nil(t, set)
} }
func TestCanDelete(t *testing.T) { func TestAbstractObject_SetID(t *testing.T) {
ao := &utils.AbstractObject{} obj := &utils.AbstractObject{}
assert.True(t, ao.CanDelete()) obj.SetID("new-id")
assert.Equal(t, "new-id", obj.UUID)
} }
func TestIsDrafted(t *testing.T) { func TestAbstractObject_SetName(t *testing.T) {
ao := &utils.AbstractObject{IsDraft: true} obj := &utils.AbstractObject{}
assert.True(t, ao.IsDrafted()) obj.SetName("hello")
assert.Equal(t, "hello", obj.Name)
} }
func TestGetID(t *testing.T) { func TestAbstractObject_GenerateID_WhenEmpty(t *testing.T) {
u := uuid.New().String() obj := &utils.AbstractObject{}
ao := &utils.AbstractObject{UUID: u} obj.GenerateID()
assert.Equal(t, u, ao.GetID()) assert.NotEmpty(t, obj.UUID)
} }
func TestGetName(t *testing.T) { func TestAbstractObject_GenerateID_KeepsExisting(t *testing.T) {
name := "MyObject" obj := &utils.AbstractObject{UUID: "existing-id"}
ao := &utils.AbstractObject{Name: name} obj.GenerateID()
assert.Equal(t, name, ao.GetName()) assert.Equal(t, "existing-id", obj.UUID)
} }
func TestGetCreatorID(t *testing.T) { func TestAbstractObject_StoreDraftDefault(t *testing.T) {
id := "creator-123" obj := &utils.AbstractObject{IsDraft: true}
ao := &utils.AbstractObject{CreatorID: id} obj.StoreDraftDefault()
assert.Equal(t, id, ao.GetCreatorID()) assert.False(t, obj.IsDraft)
} }
func TestUpToDate_CreateFalse(t *testing.T) { func TestAbstractObject_IsDrafted(t *testing.T) {
ao := &utils.AbstractObject{} obj := &utils.AbstractObject{IsDraft: true}
now := time.Now() assert.True(t, obj.IsDrafted())
time.Sleep(time.Millisecond) // ensure time difference
ao.UpToDate("user123", "peer456", false) obj.IsDraft = false
assert.WithinDuration(t, now, ao.UpdateDate, time.Second) assert.False(t, obj.IsDrafted())
assert.Equal(t, "peer456", ao.UpdaterID)
assert.Equal(t, "user123", ao.UserUpdaterID)
assert.True(t, ao.CreationDate.IsZero())
} }
func TestUpToDate_CreateTrue(t *testing.T) { func TestAbstractObject_CanDelete(t *testing.T) {
ao := &utils.AbstractObject{} obj := &utils.AbstractObject{}
now := time.Now() assert.True(t, obj.CanDelete())
time.Sleep(time.Millisecond)
ao.UpToDate("user123", "peer456", true)
assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
assert.WithinDuration(t, now, ao.CreationDate, time.Second)
assert.Equal(t, "peer456", ao.UpdaterID)
assert.Equal(t, "peer456", ao.CreatorID)
assert.Equal(t, "user123", ao.UserUpdaterID)
assert.Equal(t, "user123", ao.UserCreatorID)
} }
func TestVerifyAuth(t *testing.T) { func TestAbstractObject_CanUpdate(t *testing.T) {
request := &tools.APIRequest{PeerID: "peer123"} obj := &utils.AbstractObject{UUID: "id-1"}
ao := &utils.AbstractObject{CreatorID: "peer123"} other := &utils.AbstractObject{UUID: "id-2"}
assert.True(t, ao.VerifyAuth("get", request)) ok, returned := obj.CanUpdate(other)
assert.True(t, ok)
ao = &utils.AbstractObject{AccessMode: utils.Public} assert.Equal(t, other, returned)
assert.True(t, ao.VerifyAuth("get", nil))
ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"}
request = &tools.APIRequest{PeerID: "wrong"}
assert.False(t, ao.VerifyAuth("get", request))
} }
func TestGetObjectFilters(t *testing.T) { func TestAbstractObject_Unsign(t *testing.T) {
ao := &utils.AbstractObject{} obj := &utils.AbstractObject{Signature: []byte("sig")}
f := ao.GetObjectFilters("*") obj.Unsign()
assert.Nil(t, obj.Signature)
}
func TestAbstractObject_GetSignature(t *testing.T) {
obj := &utils.AbstractObject{Signature: []byte("sig")}
assert.Equal(t, []byte("sig"), obj.GetSignature())
}
func TestAbstractObject_DeepCopy(t *testing.T) {
obj := &utils.AbstractObject{UUID: "id-1", Name: "original"}
copy := obj.DeepCopy()
assert.NotNil(t, copy)
assert.Equal(t, obj.UUID, copy.UUID)
assert.Equal(t, obj.Name, copy.Name)
// Mutating the copy should not affect the original
copy.Name = "modified"
assert.Equal(t, "original", obj.Name)
}
func TestAbstractObject_UpToDate_Create(t *testing.T) {
obj := &utils.AbstractObject{CreatorID: ""}
obj.UpToDate("user1", "peer1", true)
assert.Equal(t, "peer1", obj.UpdaterID)
assert.Equal(t, "user1", obj.UserUpdaterID)
// CreatorID was empty so create branch is skipped
assert.Empty(t, obj.CreatorID)
}
func TestAbstractObject_UpToDate_CreateWithExistingCreator(t *testing.T) {
obj := &utils.AbstractObject{CreatorID: "existing-peer"}
obj.UpToDate("user1", "peer1", true)
assert.Equal(t, "peer1", obj.CreatorID)
assert.Equal(t, "user1", obj.UserCreatorID)
}
func TestAbstractObject_UpToDate_Update(t *testing.T) {
obj := &utils.AbstractObject{CreatorID: "original-peer"}
obj.UpToDate("user2", "peer2", false)
assert.Equal(t, "peer2", obj.UpdaterID)
assert.Equal(t, "original-peer", obj.CreatorID) // unchanged
}
// ---- VerifyAuth ----
func TestAbstractObject_VerifyAuth_NilRequest_GetPublic(t *testing.T) {
obj := &utils.AbstractObject{AccessMode: 1} // Public = 1
assert.True(t, obj.VerifyAuth("get", nil))
}
func TestAbstractObject_VerifyAuth_NilRequest_DeletePublic(t *testing.T) {
obj := &utils.AbstractObject{AccessMode: 1} // Public = 1
// non-"get" call with nil request → false
assert.False(t, obj.VerifyAuth("delete", nil))
}
func TestAbstractObject_VerifyAuth_NilRequest_Private(t *testing.T) {
obj := &utils.AbstractObject{AccessMode: 0} // Private
assert.False(t, obj.VerifyAuth("get", nil))
}
func TestAbstractObject_VerifyAuth_AdminRequest(t *testing.T) {
obj := &utils.AbstractObject{}
req := &tools.APIRequest{Admin: true}
assert.True(t, obj.VerifyAuth("get", req))
assert.True(t, obj.VerifyAuth("delete", req))
}
func TestAbstractObject_VerifyAuth_MatchingPeerID(t *testing.T) {
obj := &utils.AbstractObject{CreatorID: "peer-abc"}
req := &tools.APIRequest{PeerID: "peer-abc"}
assert.True(t, obj.VerifyAuth("get", req))
}
func TestAbstractObject_VerifyAuth_MismatchedPeerID(t *testing.T) {
obj := &utils.AbstractObject{CreatorID: "peer-abc"}
req := &tools.APIRequest{PeerID: "peer-xyz"}
assert.False(t, obj.VerifyAuth("get", req))
}
func TestAbstractObject_VerifyAuth_EmptyPeerID(t *testing.T) {
obj := &utils.AbstractObject{CreatorID: ""}
req := &tools.APIRequest{PeerID: ""}
// both empty → condition `ao.CreatorID == request.PeerID && request.PeerID != ""` is false
assert.False(t, obj.VerifyAuth("get", req))
}
// ---- GetObjectFilters ----
func TestAbstractObject_GetObjectFilters_Star(t *testing.T) {
obj := &utils.AbstractObject{}
f := obj.GetObjectFilters("*")
assert.NotNil(t, f) assert.NotNil(t, f)
assert.Contains(t, f.Or, "abstractobject.name")
assert.Equal(t, dbs.LIKE.String(), f.Or["abstractobject.name"][0].Operator)
} }
func TestDeserialize(t *testing.T) { func TestAbstractObject_GetObjectFilters_Search(t *testing.T) {
ao := &utils.AbstractObject{} obj := &utils.AbstractObject{}
input := map[string]interface{}{"name": "test", "id": uuid.New().String()} f := obj.GetObjectFilters("my-search")
res := ao.Deserialize(input, &utils.AbstractObject{}) assert.NotNil(t, f)
assert.NotNil(t, res)
} }
func TestSerialize(t *testing.T) { // ---- Serialize / Deserialize ----
ao := &utils.AbstractObject{Name: "test", UUID: uuid.New().String()}
m := ao.Serialize(ao) func TestAbstractObject_SerializeDeserialize(t *testing.T) {
assert.Equal(t, "test", m["name"]) obj := &utils.AbstractObject{UUID: "serial-id", Name: "serial-name"}
m := obj.Serialize(obj)
assert.NotNil(t, m)
dst := &utils.AbstractObject{}
result := obj.Deserialize(m, dst)
assert.NotNil(t, result)
assert.Equal(t, "serial-id", result.GetID())
} }
func TestAbstractAccessorMethods(t *testing.T) { // ---- GetAccessor ----
r := &utils.AbstractAccessor{Request: &tools.APIRequest{Username: "alice", PeerID: "peer1", Groups: []string{"dev"}}}
assert.True(t, r.ShouldVerifyAuth()) func TestAbstractObject_GetAccessor_ReturnsNil(t *testing.T) {
assert.Equal(t, "alice", r.GetUser()) obj := &utils.AbstractObject{}
assert.Equal(t, "peer1", r.GetPeerID()) acc := obj.GetAccessor(nil)
assert.Equal(t, []string{"dev"}, r.GetGroups()) assert.Nil(t, acc)
assert.Equal(t, r.Request.Caller, r.GetCaller()) }
// ---- AbstractAccessor ----
func TestAbstractAccessor_GetUser_NilRequest(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: nil}
assert.Equal(t, "", acc.GetUser())
}
func TestAbstractAccessor_GetUser_WithRequest(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{
Request: &tools.APIRequest{Username: "alice"},
}
assert.Equal(t, "alice", acc.GetUser())
}
func TestAbstractAccessor_GetPeerID_NilRequest(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: nil}
assert.Equal(t, "", acc.GetPeerID())
}
func TestAbstractAccessor_GetPeerID_WithRequest(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{
Request: &tools.APIRequest{PeerID: "peer-42"},
}
assert.Equal(t, "peer-42", acc.GetPeerID())
}
func TestAbstractAccessor_GetGroups_NilRequest(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: nil}
assert.Equal(t, []string{}, acc.GetGroups())
}
func TestAbstractAccessor_GetGroups_WithRequest(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{
Request: &tools.APIRequest{Groups: []string{"g1", "g2"}},
}
assert.Equal(t, []string{"g1", "g2"}, acc.GetGroups())
}
func TestAbstractAccessor_ShouldVerifyAuth(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{}
assert.True(t, acc.ShouldVerifyAuth())
}
func TestAbstractAccessor_GetType(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{
Type: tools.WORKFLOW,
}
assert.Equal(t, tools.WORKFLOW, acc.GetType())
}
func TestAbstractAccessor_GetRequest(t *testing.T) {
req := &tools.APIRequest{Admin: true}
acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: req}
assert.Equal(t, req, acc.GetRequest())
}
func TestAbstractAccessor_GetCaller_NilRequest(t *testing.T) {
acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: nil}
assert.Nil(t, acc.GetCaller())
} }

View File

@@ -1,168 +0,0 @@
package models_test
import (
"errors"
"testing"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// --- Mock Definitions ---
type MockDBObject struct {
mock.Mock
}
func (m *MockAccessor) GetLogger() *zerolog.Logger {
return nil
}
func (m *MockAccessor) GetGroups() []string {
return []string{}
}
func (m *MockAccessor) GetCaller() *tools.HTTPCaller {
return nil
}
func (m *MockDBObject) GenerateID() { m.Called() }
func (m *MockDBObject) StoreDraftDefault() { m.Called() }
func (m *MockDBObject) UpToDate(user, peer string, create bool) {
m.Called(user, peer, create)
}
func (m *MockDBObject) VerifyAuth(req *tools.APIRequest) bool {
args := m.Called(req)
return args.Bool(0)
}
func (m *MockDBObject) CanDelete() bool {
args := m.Called()
return args.Bool(0)
}
func (m *MockDBObject) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
args := m.Called(set)
return args.Bool(0), args.Get(1).(utils.DBObject)
}
func (m *MockDBObject) IsDrafted() bool {
args := m.Called()
return args.Bool(0)
}
func (m *MockDBObject) Serialize(obj utils.DBObject) map[string]interface{} {
args := m.Called(obj)
return args.Get(0).(map[string]interface{})
}
func (m *MockDBObject) Deserialize(mdata map[string]interface{}, obj utils.DBObject) utils.DBObject {
args := m.Called(mdata, obj)
return args.Get(0).(utils.DBObject)
}
func (m *MockDBObject) GetID() string {
args := m.Called()
return args.String(0)
}
func (m *MockDBObject) GetName() string {
args := m.Called()
return args.String(0)
}
type MockAccessor struct {
mock.Mock
}
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) LoadOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
args := m.Called(set, id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
args := m.Called(data)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
args := m.Called(data)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) ShouldVerifyAuth() bool {
args := m.Called()
return args.Bool(0)
}
func (m *MockAccessor) GetRequest() *tools.APIRequest {
args := m.Called()
return args.Get(0).(*tools.APIRequest)
}
func (m *MockAccessor) GetType() tools.DataType {
args := m.Called()
return args.Get(0).(tools.DataType)
}
func (m *MockAccessor) Search(filters *dbs.Filters, s string, d bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(filters, s, d)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) GetUser() string {
args := m.Called()
return args.String(0)
}
func (m *MockAccessor) GetPeerID() string {
args := m.Called()
return args.String(0)
}
// --- Test Cases ---
func TestVerifyAccess_Authorized(t *testing.T) {
mockObj := new(MockDBObject)
mockAcc := new(MockAccessor)
req := &tools.APIRequest{PeerID: "peer"}
mockAcc.On("LoadOne", "123").Return(mockObj, 200, nil)
mockAcc.On("ShouldVerifyAuth").Return(true)
mockObj.On("VerifyAuth", req).Return(true)
mockAcc.On("GetRequest").Return(req)
err := utils.VerifyAccess(mockAcc, "123")
assert.NoError(t, err)
}
func TestVerifyAccess_Unauthorized(t *testing.T) {
mockObj := new(MockDBObject)
mockAcc := new(MockAccessor)
req := &tools.APIRequest{PeerID: "peer"}
mockAcc.On("LoadOne", "123").Return(mockObj, 200, nil)
mockAcc.On("ShouldVerifyAuth").Return(true)
mockObj.On("VerifyAuth", req).Return(false)
mockAcc.On("GetRequest").Return(req)
err := utils.VerifyAccess(mockAcc, "123")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not allowed")
}
func TestVerifyAccess_LoadError(t *testing.T) {
mockAcc := new(MockAccessor)
mockAcc.On("LoadOne", "bad-id").Return(nil, 404, errors.New("not found"))
err := utils.VerifyAccess(mockAcc, "bad-id")
assert.Error(t, err)
assert.Equal(t, "not found", err.Error())
}

View File

@@ -12,6 +12,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/booking"
"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
"cloud.o-forge.io/core/oc-lib/models/common" "cloud.o-forge.io/core/oc-lib/models/common"
"cloud.o-forge.io/core/oc-lib/models/common/models"
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/peer" "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/resources" "cloud.o-forge.io/core/oc-lib/models/resources"
@@ -41,7 +42,9 @@ type Workflow struct {
Graph *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow Graph *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow
ScheduleActive bool `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set ScheduleActive bool `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set
// Schedule *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow // Schedule *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow
Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow // AbstractWorkflow contains the basic fields of a workflow Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow // AbstractWorkflow contains the basic fields of a workflow
Env []models.Param `json:"env,omitempty" bson:"env,omitempty"`
Inputs []models.Param `json:"inputs,omitempty" bson:"inputs,omitempty"`
} }
func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor { func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor {
@@ -390,11 +393,13 @@ func (w *Workflow) GetPricedItem(
for _, item := range w.Graph.Items { for _, item := range w.Graph.Items {
if f(item) { if f(item) {
dt, res := item.GetResource() dt, res := item.GetResource()
ord, err := res.ConvertToPricedResource(dt, &instance, &partnership, &buying, &strategy, &bookingMode, request) ord, err := res.ConvertToPricedResource(dt, &instance, &partnership, &buying, &strategy, &bookingMode, request)
if err != nil { if err != nil {
return list_datas, err return list_datas, err
} }
list_datas[res.GetID()] = ord list_datas[res.GetID()] = ord
} }
} }
return list_datas, nil return list_datas, nil
@@ -445,6 +450,7 @@ func (ao *Workflow) VerifyAuth(callName string, request *tools.APIRequest) bool
return ao.AbstractObject.VerifyAuth(callName, request) || isAuthorized return ao.AbstractObject.VerifyAuth(callName, request) || isAuthorized
} }
// TODO : Check Booking... + Storage
/* /*
* CheckBooking is a function that checks the booking of the workflow on peers (even ourselves) * CheckBooking is a function that checks the booking of the workflow on peers (even ourselves)
*/ */

View File

@@ -14,10 +14,10 @@ import (
) )
type workflowMongoAccessor struct { type workflowMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[*Workflow] // AbstractAccessor contains the basic fields of an accessor (model, caller)
computeResourceAccessor utils.Accessor computeResourceAccessor utils.Accessor
collaborativeAreaAccessor utils.Accessor collaborativeAreaAccessor utils.Accessor
workspaceAccessor utils.Accessor workspaceAccessor utils.Accessor
} }
func NewAccessorHistory(request *tools.APIRequest) *workflowMongoAccessor { func NewAccessorHistory(request *tools.APIRequest) *workflowMongoAccessor {
@@ -34,10 +34,11 @@ func new(t tools.DataType, request *tools.APIRequest) *workflowMongoAccessor {
computeResourceAccessor: (&resources.ComputeResource{}).GetAccessor(request), computeResourceAccessor: (&resources.ComputeResource{}).GetAccessor(request),
collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(request), collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(request),
workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request), workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request),
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*Workflow]{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(t.String()), // Create a logger with the data type
Request: request, Request: request,
Type: t, Type: t,
New: func() *Workflow { return &Workflow{} },
}, },
} }
} }
@@ -91,13 +92,17 @@ func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *t
} }
// UpdateOne updates a workflow in the database // UpdateOne updates a workflow in the database
func (a *workflowMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (a *workflowMongoAccessor) UpdateOne(set map[string]interface{}, id string) (utils.DBObject, int, error) {
// avoid the update if the schedule is the same // avoid the update if the schedule is the same
set = a.verifyResource(set) res, code, err := utils.GenericUpdateOne(set, id, a)
if set.(*Workflow).Graph != nil && set.(*Workflow).Graph.Partial { if code != 200 {
return nil, code, err
}
res = a.verifyResource(res)
if res.(*Workflow).Graph != nil && res.(*Workflow).Graph.Partial {
return nil, 403, errors.New("you are not allowed to update a partial workflow") return nil, 403, errors.New("you are not allowed to update a partial workflow")
} }
res, code, err := utils.GenericUpdateOne(set, id, a, &Workflow{}) res, code, err = utils.GenericUpdateOne(res.Serialize(res), id, a)
if code != 200 { if code != 200 {
return nil, code, err return nil, code, err
} }
@@ -153,7 +158,7 @@ func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active
return return
} }
if err == nil && len(resource) > 0 { // if the workspace already exists, update it if err == nil && len(resource) > 0 { // if the workspace already exists, update it
a.workspaceAccessor.UpdateOne(&workspace.Workspace{ w := &workspace.Workspace{
Active: active, Active: active,
ResourceSet: resources.ResourceSet{ ResourceSet: resources.ResourceSet{
Datas: workflow.Datas, Datas: workflow.Datas,
@@ -162,7 +167,8 @@ func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active
Workflows: workflow.Workflows, Workflows: workflow.Workflows,
Computes: workflow.Computes, Computes: workflow.Computes,
}, },
}, resource[0].GetID()) }
a.workspaceAccessor.UpdateOne(w.Serialize(w), resource[0].GetID())
} else { // if the workspace does not exist, create it } else { // if the workspace does not exist, create it
a.workspaceAccessor.StoreOne(&workspace.Workspace{ a.workspaceAccessor.StoreOne(&workspace.Workspace{
Active: active, Active: active,
@@ -179,19 +185,16 @@ func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active
} }
func (a *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Workflow](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne(id, a.New(), func(d utils.DBObject) (utils.DBObject, int, error) {
w := d.(*Workflow) w := d.(*Workflow)
a.execute(w, false, true) // if no workspace is attached to the workflow, create it a.execute(w, false, true) // if no workspace is attached to the workflow, create it
return d, 200, nil return d, 200, nil
}, a) }, a)
} }
func (a *workflowMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*Workflow](func(d utils.DBObject) utils.ShallowDBObject { return &d.(*Workflow).AbstractObject }, isDraft, a)
}
func (a *workflowMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { func (a *workflowMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Workflow](filters, search, (&Workflow{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return a.verifyResource(d) }, isDraft, a) return utils.GenericSearch[*Workflow](filters, search, a.New().GetObjectFilters(search),
func(d utils.DBObject) utils.ShallowDBObject { return a.verifyResource(d) }, isDraft, a)
} }
func (a *workflowMongoAccessor) verifyResource(obj utils.DBObject) utils.DBObject { func (a *workflowMongoAccessor) verifyResource(obj utils.DBObject) utils.DBObject {
@@ -205,17 +208,18 @@ func (a *workflowMongoAccessor) verifyResource(obj utils.DBObject) utils.DBObjec
continue continue
} }
var access utils.Accessor var access utils.Accessor
if t == tools.COMPUTE_RESOURCE { switch t {
case tools.COMPUTE_RESOURCE:
access = resources.NewAccessor[*resources.ComputeResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ComputeResource{} }) access = resources.NewAccessor[*resources.ComputeResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ComputeResource{} })
} else if t == tools.PROCESSING_RESOURCE { case tools.PROCESSING_RESOURCE:
access = resources.NewAccessor[*resources.ProcessingResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ProcessingResource{} }) access = resources.NewAccessor[*resources.ProcessingResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ProcessingResource{} })
} else if t == tools.STORAGE_RESOURCE { case tools.STORAGE_RESOURCE:
access = resources.NewAccessor[*resources.StorageResource](t, a.GetRequest(), func() utils.DBObject { return &resources.StorageResource{} }) access = resources.NewAccessor[*resources.StorageResource](t, a.GetRequest(), func() utils.DBObject { return &resources.StorageResource{} })
} else if t == tools.WORKFLOW_RESOURCE { case tools.WORKFLOW_RESOURCE:
access = resources.NewAccessor[*resources.WorkflowResource](t, a.GetRequest(), func() utils.DBObject { return &resources.WorkflowResource{} }) access = resources.NewAccessor[*resources.WorkflowResource](t, a.GetRequest(), func() utils.DBObject { return &resources.WorkflowResource{} })
} else if t == tools.DATA_RESOURCE { case tools.DATA_RESOURCE:
access = resources.NewAccessor[*resources.DataResource](t, a.GetRequest(), func() utils.DBObject { return &resources.DataResource{} }) access = resources.NewAccessor[*resources.DataResource](t, a.GetRequest(), func() utils.DBObject { return &resources.DataResource{} })
} else { default:
wf.Graph.Clear(resource.GetID()) wf.Graph.Clear(resource.GetID())
} }
if error := utils.VerifyAccess(access, resource.GetID()); error != nil { if error := utils.VerifyAccess(access, resource.GetID()); error != nil {

View File

@@ -4,26 +4,35 @@ import (
"testing" "testing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestStoreOneWorkflow(t *testing.T) { func TestNewWorkflowAccessor(t *testing.T) {
w := Workflow{ req := &tools.APIRequest{}
AbstractObject: utils.AbstractObject{Name: "testWorkflow"}, acc := NewAccessor(req)
} assert.NotNil(t, acc)
wma := NewAccessor(nil)
id, _, _ := wma.StoreOne(&w)
assert.NotEmpty(t, id)
} }
func TestLoadOneWorkflow(t *testing.T) { func TestWorkflow_StoreDraftDefault(t *testing.T) {
w := Workflow{ w := &Workflow{
AbstractObject: utils.AbstractObject{Name: "testWorkflow"}, AbstractObject: utils.AbstractObject{Name: "testWorkflow"},
} }
w.StoreDraftDefault()
wma := NewAccessor(nil) assert.False(t, w.IsDraft)
new_w, _, _ := wma.StoreOne(&w) }
assert.Equal(t, w, new_w)
func TestWorkflow_VerifyAuth_NilRequest(t *testing.T) {
w := &Workflow{
AbstractObject: utils.AbstractObject{},
}
result := w.VerifyAuth("get", nil)
assert.False(t, result)
}
func TestWorkflow_VerifyAuth_AdminRequest(t *testing.T) {
w := &Workflow{}
req := &tools.APIRequest{Admin: true}
result := w.VerifyAuth("get", req)
assert.True(t, result)
} }

View File

@@ -0,0 +1,172 @@
package workflow_execution_test
import (
"testing"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
)
// ---- WorkflowExecution model ----
func TestWorkflowExecution_StoreDraftDefault(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.StoreDraftDefault()
assert.False(t, we.IsDraft)
assert.Equal(t, enum.SCHEDULED, we.State)
}
func TestWorkflowExecution_CanDelete_Draft(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.IsDraft = true
assert.True(t, we.CanDelete())
}
func TestWorkflowExecution_CanDelete_NonDraft(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.IsDraft = false
assert.False(t, we.CanDelete())
}
func TestWorkflowExecution_CanUpdate_StateChange(t *testing.T) {
we := &workflow_execution.WorkflowExecution{State: enum.SCHEDULED}
set := &workflow_execution.WorkflowExecution{State: enum.STARTED}
ok, returned := we.CanUpdate(set)
assert.True(t, ok)
// Only the state should be propagated
assert.Equal(t, enum.STARTED, returned.(*workflow_execution.WorkflowExecution).State)
}
func TestWorkflowExecution_CanUpdate_SameState_NonDraft(t *testing.T) {
we := &workflow_execution.WorkflowExecution{State: enum.SCHEDULED}
we.IsDraft = false
set := &workflow_execution.WorkflowExecution{State: enum.SCHEDULED}
ok, _ := we.CanUpdate(set)
// !r.IsDraft == true → ok
assert.True(t, ok)
}
func TestWorkflowExecution_CanUpdate_SameState_Draft(t *testing.T) {
we := &workflow_execution.WorkflowExecution{State: enum.SCHEDULED}
we.IsDraft = true
set := &workflow_execution.WorkflowExecution{State: enum.SCHEDULED}
ok, _ := we.CanUpdate(set)
// !r.IsDraft == false (it is draft) → ok false
assert.False(t, ok)
}
func TestWorkflowExecution_Equals_True(t *testing.T) {
now := time.Now()
a := &workflow_execution.WorkflowExecution{WorkflowID: "wf-1"}
a.ExecDate = now
b := &workflow_execution.WorkflowExecution{WorkflowID: "wf-1"}
b.ExecDate = now
assert.True(t, a.Equals(b))
}
func TestWorkflowExecution_Equals_DifferentDate(t *testing.T) {
a := &workflow_execution.WorkflowExecution{WorkflowID: "wf-1"}
a.ExecDate = time.Now()
b := &workflow_execution.WorkflowExecution{WorkflowID: "wf-1"}
b.ExecDate = time.Now().Add(time.Hour)
assert.False(t, a.Equals(b))
}
func TestWorkflowExecution_Equals_DifferentWorkflow(t *testing.T) {
now := time.Now()
a := &workflow_execution.WorkflowExecution{WorkflowID: "wf-1"}
a.ExecDate = now
b := &workflow_execution.WorkflowExecution{WorkflowID: "wf-2"}
b.ExecDate = now
assert.False(t, a.Equals(b))
}
func TestWorkflowExecution_GetName(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.UUID = "exec-uuid"
we.ExecDate = time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
name := we.GetName()
assert.Contains(t, name, "exec-uuid")
assert.Contains(t, name, "2026")
}
func TestWorkflowExecution_GenerateID(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.GenerateID()
assert.NotEmpty(t, we.UUID)
}
func TestWorkflowExecution_GenerateID_KeepsExisting(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.UUID = "existing-uuid"
we.GenerateID()
assert.Equal(t, "existing-uuid", we.UUID)
}
func TestWorkflowExecution_VerifyAuth_AlwaysTrue(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
assert.True(t, we.VerifyAuth("get", nil))
assert.True(t, we.VerifyAuth("delete", &tools.APIRequest{}))
}
func TestWorkflowExecution_GetAccessor(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
acc := we.GetAccessor(&tools.APIRequest{})
assert.NotNil(t, acc)
}
// ---- ArgoStatusToState ----
func TestArgoStatusToState_Succeeded(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.ArgoStatusToState("succeeded")
assert.Equal(t, enum.SUCCESS, we.State)
}
func TestArgoStatusToState_Pending(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.ArgoStatusToState("pending")
assert.Equal(t, enum.SCHEDULED, we.State)
}
func TestArgoStatusToState_Running(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.ArgoStatusToState("running")
assert.Equal(t, enum.STARTED, we.State)
}
func TestArgoStatusToState_Default_Failed(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.ArgoStatusToState("failed")
assert.Equal(t, enum.FAILURE, we.State)
}
func TestArgoStatusToState_CaseInsensitive(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
we.ArgoStatusToState("SUCCEEDED")
assert.Equal(t, enum.SUCCESS, we.State)
}
func TestArgoStatusToState_ReturnsPointer(t *testing.T) {
we := &workflow_execution.WorkflowExecution{}
result := we.ArgoStatusToState("running")
assert.Equal(t, we, result)
}
// ---- NewAccessor ----
func TestNewWorkflowExecutionAccessor(t *testing.T) {
acc := workflow_execution.NewAccessor(&tools.APIRequest{Admin: true})
assert.NotNil(t, acc)
assert.Equal(t, tools.WORKFLOW_EXECUTION, acc.GetType())
}
func TestNewWorkflowExecutionAccessor_NilRequest(t *testing.T) {
acc := workflow_execution.NewAccessor(nil)
assert.NotNil(t, acc)
assert.Equal(t, "", acc.GetUser())
assert.Equal(t, "", acc.GetPeerID())
}

View File

@@ -1,164 +0,0 @@
package workflow_execution_test
import (
"testing"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockAccessor struct {
mock.Mock
}
func (m *MockAccessor) LoadOne(id string) (interface{}, int, error) {
args := m.Called(id)
return args.Get(0), args.Int(1), args.Error(2)
}
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return nil, args.Int(1), args.Error(2)
}
func (m *MockAccessor) Search(filters interface{}, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(filters, search, isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func TestStoreDraftDefault(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
exec.StoreDraftDefault()
assert.False(t, exec.IsDraft)
assert.Equal(t, enum.SCHEDULED, exec.State)
}
func TestCanUpdate_StateChange(t *testing.T) {
existing := &workflow_execution.WorkflowExecution{State: enum.DRAFT}
newExec := &workflow_execution.WorkflowExecution{State: enum.SCHEDULED}
canUpdate, updated := existing.CanUpdate(newExec)
assert.True(t, canUpdate)
assert.Equal(t, enum.SCHEDULED, updated.(*workflow_execution.WorkflowExecution).State)
}
func TestCanUpdate_SameState_Draft(t *testing.T) {
existing := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}, State: enum.DRAFT}
newExec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}, State: enum.DRAFT}
canUpdate, _ := existing.CanUpdate(newExec)
assert.False(t, canUpdate)
}
func TestCanDelete_TrueIfDraft(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}}
assert.True(t, exec.CanDelete())
}
func TestCanDelete_FalseIfNotDraft(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: false}}
assert.False(t, exec.CanDelete())
}
func TestEquals_True(t *testing.T) {
d := time.Now()
exec1 := &workflow_execution.WorkflowExecution{ExecDate: d, WorkflowID: "123"}
exec2 := &workflow_execution.WorkflowExecution{ExecDate: d, WorkflowID: "123"}
assert.True(t, exec1.Equals(exec2))
}
func TestEquals_False(t *testing.T) {
exec1 := &workflow_execution.WorkflowExecution{ExecDate: time.Now(), WorkflowID: "abc"}
exec2 := &workflow_execution.WorkflowExecution{ExecDate: time.Now().Add(time.Hour), WorkflowID: "def"}
assert.False(t, exec1.Equals(exec2))
}
func TestArgoStatusToState_Success(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
exec.ArgoStatusToState("succeeded")
assert.Equal(t, enum.SUCCESS, exec.State)
}
func TestArgoStatusToState_DefaultToFailure(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
exec.ArgoStatusToState("unknown")
assert.Equal(t, enum.FAILURE, exec.State)
}
func TestGenerateID_AssignsUUID(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
exec.GenerateID()
assert.NotEmpty(t, exec.UUID)
}
func TestGetName_ReturnsCorrectFormat(t *testing.T) {
time := time.Now()
exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{UUID: "abc"}, ExecDate: time}
assert.Contains(t, exec.GetName(), "abc")
assert.Contains(t, exec.GetName(), time.String())
}
func TestVerifyAuth_AlwaysTrue(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
assert.True(t, exec.VerifyAuth("get", nil))
}
func TestUpdateOne_RejectsZeroState(t *testing.T) {
accessor := &workflow_execution.WorkflowExecutionMongoAccessor{}
set := &workflow_execution.WorkflowExecution{State: 0}
_, code, err := accessor.UpdateOne(set, "someID")
assert.Equal(t, 400, code)
assert.Error(t, err)
}
func TestLoadOne_DraftExpired_ShouldDelete(t *testing.T) {
// Normally would mock time.Now and delete call; for now we test structure
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
exec := &workflow_execution.WorkflowExecution{
ExecDate: time.Now().Add(-2 * time.Minute),
State: enum.DRAFT,
AbstractObject: utils.AbstractObject{UUID: "to-delete"},
}
_, _, _ = accessor.LoadOne(exec.GetID())
// No panic = good enough placeholder
}
func TestLoadOne_ScheduledExpired_ShouldUpdateToForgotten(t *testing.T) {
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
exec := &workflow_execution.WorkflowExecution{
ExecDate: time.Now().Add(-2 * time.Minute),
State: enum.SCHEDULED,
AbstractObject: utils.AbstractObject{UUID: "to-forget"},
}
_, _, _ = accessor.LoadOne(exec.GetID())
}
func TestDeleteOne_NotImplemented(t *testing.T) {
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
_, code, err := accessor.DeleteOne("someID")
assert.Equal(t, 404, code)
assert.Error(t, err)
}
func TestStoreOne_NotImplemented(t *testing.T) {
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
_, code, err := accessor.StoreOne(nil)
assert.Equal(t, 404, code)
assert.Error(t, err)
}
func TestCopyOne_NotImplemented(t *testing.T) {
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
_, code, err := accessor.CopyOne(nil)
assert.Equal(t, 404, code)
assert.Error(t, err)
}
func TestGetExecFilters_BasicPattern(t *testing.T) {
a := workflow_execution.NewAccessor(&tools.APIRequest{})
filters := a.GetExecFilters("foo")
assert.Contains(t, filters.Or["abstractobject.name"][0].Value, "foo")
}

View File

@@ -33,6 +33,9 @@ type WorkflowExecution struct {
State enum.BookingStatus `json:"state" bson:"state" default:"0"` // TEMPORARY TODO DEFAULT 1 -> 0 State is the state of the workflow State enum.BookingStatus `json:"state" bson:"state" default:"0"` // TEMPORARY TODO DEFAULT 1 -> 0 State is the state of the workflow
WorkflowID string `json:"workflow_id" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow WorkflowID string `json:"workflow_id" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow
BookingsState map[string]bool `json:"bookings_state" bson:"bookings_state,omitempty"` // WorkflowID is the ID of the workflow
PurchasesState map[string]bool `json:"purchases_state" bson:"purchases_state,omitempty"` // WorkflowID is the ID of the workflow
SelectedInstances workflow.ConfigItem `json:"selected_instances"` SelectedInstances workflow.ConfigItem `json:"selected_instances"`
SelectedPartnerships workflow.ConfigItem `json:"selected_partnerships"` SelectedPartnerships workflow.ConfigItem `json:"selected_partnerships"`
SelectedBuyings workflow.ConfigItem `json:"selected_buyings"` SelectedBuyings workflow.ConfigItem `json:"selected_buyings"`
@@ -127,6 +130,11 @@ use of a datacenter or storage can't be buy for permanent access.
func (d *WorkflowExecution) Buy(bs pricing.BillingStrategy, executionsID string, wfID string, priceds map[tools.DataType]map[string]pricing.PricedItemITF) []*purchase_resource.PurchaseResource { func (d *WorkflowExecution) Buy(bs pricing.BillingStrategy, executionsID string, wfID string, priceds map[tools.DataType]map[string]pricing.PricedItemITF) []*purchase_resource.PurchaseResource {
purchases := d.buyEach(bs, executionsID, wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE]) purchases := d.buyEach(bs, executionsID, wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE])
purchases = append(purchases, d.buyEach(bs, executionsID, wfID, tools.DATA_RESOURCE, priceds[tools.DATA_RESOURCE])...) purchases = append(purchases, d.buyEach(bs, executionsID, wfID, tools.DATA_RESOURCE, priceds[tools.DATA_RESOURCE])...)
d.PurchasesState = map[string]bool{}
for _, p := range purchases {
p.SetID(uuid.NewString())
d.PurchasesState[p.GetID()] = false
}
return purchases return purchases
} }
@@ -159,9 +167,11 @@ func (d *WorkflowExecution) buyEach(bs pricing.BillingStrategy, executionsID str
Name: d.GetName() + "_" + executionsID + "_" + wfID, Name: d.GetName() + "_" + executionsID + "_" + wfID,
}, },
PricedItem: m, PricedItem: m,
ExecutionID: d.GetID(),
ExecutionsID: executionsID, ExecutionsID: executionsID,
DestPeerID: priced.GetCreatorID(), DestPeerID: priced.GetCreatorID(),
ResourceID: priced.GetID(), ResourceID: priced.GetID(),
InstanceID: priced.GetInstanceID(),
ResourceType: dt, ResourceType: dt,
EndDate: &end, EndDate: &end,
} }
@@ -177,6 +187,10 @@ func (d *WorkflowExecution) Book(executionsID string, wfID string, priceds map[t
booking = append(booking, d.bookEach(executionsID, wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE])...) booking = append(booking, d.bookEach(executionsID, wfID, tools.PROCESSING_RESOURCE, priceds[tools.PROCESSING_RESOURCE])...)
booking = append(booking, d.bookEach(executionsID, wfID, tools.COMPUTE_RESOURCE, priceds[tools.COMPUTE_RESOURCE])...) booking = append(booking, d.bookEach(executionsID, wfID, tools.COMPUTE_RESOURCE, priceds[tools.COMPUTE_RESOURCE])...)
booking = append(booking, d.bookEach(executionsID, wfID, tools.DATA_RESOURCE, priceds[tools.DATA_RESOURCE])...) booking = append(booking, d.bookEach(executionsID, wfID, tools.DATA_RESOURCE, priceds[tools.DATA_RESOURCE])...)
for _, p := range booking {
p.SetID(uuid.NewString())
d.BookingsState[p.GetID()] = false
}
return booking return booking
} }
@@ -212,6 +226,7 @@ func (d *WorkflowExecution) bookEach(executionsID string, wfID string, dt tools.
ExecutionsID: executionsID, ExecutionsID: executionsID,
State: enum.SCHEDULED, State: enum.SCHEDULED,
ResourceID: priced.GetID(), ResourceID: priced.GetID(),
InstanceID: priced.GetInstanceID(),
ResourceType: dt, ResourceType: dt,
DestPeerID: priced.GetCreatorID(), DestPeerID: priced.GetCreatorID(),
WorkflowID: wfID, WorkflowID: wfID,

View File

@@ -12,17 +12,19 @@ import (
) )
type WorkflowExecutionMongoAccessor struct { type WorkflowExecutionMongoAccessor struct {
utils.AbstractAccessor utils.AbstractAccessor[*WorkflowExecution]
shallow bool shallow bool
} }
func newShallowAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccessor { func newShallowAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccessor {
return &WorkflowExecutionMongoAccessor{ return &WorkflowExecutionMongoAccessor{
shallow: true, shallow: true,
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*WorkflowExecution]{
Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.WORKFLOW_EXECUTION, Type: tools.WORKFLOW_EXECUTION,
New: func() *WorkflowExecution { return &WorkflowExecution{} },
NotImplemented: []string{"DeleteOne", "StoreOne", "CopyOne"},
}, },
} }
} }
@@ -30,36 +32,27 @@ func newShallowAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccess
func NewAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccessor { func NewAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccessor {
return &WorkflowExecutionMongoAccessor{ return &WorkflowExecutionMongoAccessor{
shallow: false, shallow: false,
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor[*WorkflowExecution]{
Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
Request: request, Request: request,
Type: tools.WORKFLOW_EXECUTION, Type: tools.WORKFLOW_EXECUTION,
New: func() *WorkflowExecution { return &WorkflowExecution{} },
NotImplemented: []string{"DeleteOne", "StoreOne", "CopyOne"},
}, },
} }
} }
func (wfa *WorkflowExecutionMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (wfa *WorkflowExecutionMongoAccessor) UpdateOne(set map[string]interface{}, id string) (utils.DBObject, int, error) {
return nil, 404, errors.New("not implemented") if set["state"] == nil {
}
func (wfa *WorkflowExecutionMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
if set.(*WorkflowExecution).State == 0 {
return nil, 400, errors.New("state is required") return nil, 400, errors.New("state is required")
} }
realSet := WorkflowExecution{State: set.(*WorkflowExecution).State} return utils.GenericUpdateOne(map[string]interface{}{
return utils.GenericUpdateOne(&realSet, id, wfa, &WorkflowExecution{}) "state": set["state"],
} }, id, wfa)
func (wfa *WorkflowExecutionMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return nil, 404, errors.New("not implemented")
}
func (wfa *WorkflowExecutionMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return nil, 404, errors.New("not implemented")
} }
func (a *WorkflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *WorkflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*WorkflowExecution](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[*WorkflowExecution](id, a.New(), func(d utils.DBObject) (utils.DBObject, int, error) {
now := time.Now() now := time.Now()
now = now.Add(time.Second * -60) now = now.Add(time.Second * -60)
if d.(*WorkflowExecution).State == enum.DRAFT && !a.shallow && now.UTC().After(d.(*WorkflowExecution).ExecDate) { if d.(*WorkflowExecution).State == enum.DRAFT && !a.shallow && now.UTC().After(d.(*WorkflowExecution).ExecDate) {
@@ -73,16 +66,7 @@ func (a *WorkflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int
return d, 200, nil return d, 200, nil
}, a) }, a)
} }
func (a *WorkflowExecutionMongoAccessor) GetExec(isDraft bool) func(utils.DBObject) utils.ShallowDBObject {
func (a *WorkflowExecutionMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*WorkflowExecution](a.getExec(), isDraft, a)
}
func (a *WorkflowExecutionMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*WorkflowExecution](filters, search, a.GetExecFilters(search), a.getExec(), isDraft, a)
}
func (a *WorkflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject { return func(d utils.DBObject) utils.ShallowDBObject {
now := time.Now() now := time.Now()
now = now.Add(time.Second * -60) now = now.Add(time.Second * -60)
@@ -99,7 +83,7 @@ func (a *WorkflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.Sh
} }
} }
func (a *WorkflowExecutionMongoAccessor) GetExecFilters(search string) *dbs.Filters { func (a *WorkflowExecutionMongoAccessor) GetObjectFilters(search string) *dbs.Filters {
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search + "_execution"}}, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search + "_execution"}},

View File

@@ -18,34 +18,48 @@ type MockWorkspaceAccessor struct {
workspace.Workspace workspace.Workspace
} }
func safeDBObject(v interface{}) utils.DBObject {
if v == nil {
return nil
}
return v.(utils.DBObject)
}
func safeShallowList(v interface{}) []utils.ShallowDBObject {
if v == nil {
return nil
}
return v.([]utils.ShallowDBObject)
}
func (m *MockWorkspaceAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (m *MockWorkspaceAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
args := m.Called(data) args := m.Called(data)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2) return safeDBObject(args.Get(0)), args.Int(1), args.Error(2)
} }
func (m *MockWorkspaceAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (m *MockWorkspaceAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
args := m.Called(set, id) args := m.Called(set, id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2) return safeDBObject(args.Get(0)), args.Int(1), args.Error(2)
} }
func (m *MockWorkspaceAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (m *MockWorkspaceAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
args := m.Called(id) args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2) return safeDBObject(args.Get(0)), args.Int(1), args.Error(2)
} }
func (m *MockWorkspaceAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (m *MockWorkspaceAccessor) LoadOne(id string) (utils.DBObject, int, error) {
args := m.Called(id) args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2) return safeDBObject(args.Get(0)), args.Int(1), args.Error(2)
} }
func (m *MockWorkspaceAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { func (m *MockWorkspaceAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(isDraft) args := m.Called(isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2) return safeShallowList(args.Get(0)), args.Int(1), args.Error(2)
} }
func (m *MockWorkspaceAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { func (m *MockWorkspaceAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(filters, search, isDraft) args := m.Called(filters, search, isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2) return safeShallowList(args.Get(0)), args.Int(1), args.Error(2)
} }
func TestStoreOne_Success(t *testing.T) { func TestStoreOne_Success(t *testing.T) {

View File

@@ -13,7 +13,7 @@ import (
// Workspace is a struct that represents a workspace // Workspace is a struct that represents a workspace
type workspaceMongoAccessor struct { type workspaceMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor[*Workspace] // AbstractAccessor contains the basic fields of an accessor (model, caller)
} }
// New creates a new instance of the workspaceMongoAccessor // New creates a new instance of the workspaceMongoAccessor
@@ -28,10 +28,11 @@ func NewAccessor(request *tools.APIRequest) *workspaceMongoAccessor {
// New creates a new instance of the workspaceMongoAccessor // New creates a new instance of the workspaceMongoAccessor
func new(t tools.DataType, request *tools.APIRequest) *workspaceMongoAccessor { func new(t tools.DataType, request *tools.APIRequest) *workspaceMongoAccessor {
return &workspaceMongoAccessor{ return &workspaceMongoAccessor{
utils.AbstractAccessor{ utils.AbstractAccessor[*Workspace]{
Logger: logs.CreateLogger(t.String()), // Create a logger with the data type Logger: logs.CreateLogger(t.String()), // Create a logger with the data type
Request: request, Request: request,
Type: t, Type: t,
New: func() *Workspace { return &Workspace{} },
}, },
} }
} }
@@ -47,21 +48,19 @@ func (a *workspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, erro
} }
// UpdateOne updates a workspace in the database, given its ID, it automatically share to peers if the workspace is shared // UpdateOne updates a workspace in the database, given its ID, it automatically share to peers if the workspace is shared
func (a *workspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (a *workspaceMongoAccessor) UpdateOne(set map[string]interface{}, id string) (utils.DBObject, int, error) {
d := set.(*Workspace) // Get the workspace from the set if set["active"] == true { // If the workspace is active, deactivate all the other workspaces
d.Clear()
if d.Active { // If the workspace is active, deactivate all the other workspaces
res, _, err := a.LoadAll(true) res, _, err := a.LoadAll(true)
if err == nil { if err == nil {
for _, r := range res { for _, r := range res {
if r.GetID() != id { if r.GetID() != id {
r.(*Workspace).Active = false set["active"] = false
a.UpdateOne(r.(*Workspace), r.GetID()) a.UpdateOne(set, r.GetID())
} }
} }
} }
} }
res, code, err := utils.GenericUpdateOne(set, id, a, &Workspace{}) res, code, err := utils.GenericUpdateOne(set, id, a)
if code == 200 && res != nil { if code == 200 && res != nil {
a.share(res.(*Workspace), tools.PUT, a.GetCaller()) a.share(res.(*Workspace), tools.PUT, a.GetCaller())
} }
@@ -87,13 +86,8 @@ func (a *workspaceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject,
return utils.GenericStoreOne(d, a) return utils.GenericStoreOne(d, a)
} }
// CopyOne copies a workspace in the database
func (a *workspaceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return utils.GenericStoreOne(data, a)
}
func (a *workspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *workspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*Workspace](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[*Workspace](id, a.New(), func(d utils.DBObject) (utils.DBObject, int, error) {
d.(*Workspace).Fill(a.GetRequest()) d.(*Workspace).Fill(a.GetRequest())
return d, 200, nil return d, 200, nil
}, a) }, a)

View File

@@ -157,7 +157,7 @@ func (a *API) CheckRemoteAPIs(apis []DataType) (State, map[string]string, error)
reachable := false reachable := false
for _, api := range apis { // Check the state of each remote API in the list for _, api := range apis { // Check the state of each remote API in the list
var resp APIStatusResponse var resp APIStatusResponse
b, err := caller.CallGet("http://"+api.API()+":8080", "/oc/version/status") // Call the status endpoint of the remote API (standard OC status endpoint) b, err := caller.CallGet("http://"+api.InnerAPI()+":8080", "/oc/version/status") // Call the status endpoint of the remote API (standard OC status endpoint)
if err != nil { if err != nil {
l.Error().Msg(api.String() + " not reachable") l.Error().Msg(api.String() + " not reachable")
state = REDUCED_SERVICE // If a remote API is not reachable, return reduced service state = REDUCED_SERVICE // If a remote API is not reachable, return reduced service

View File

@@ -13,6 +13,7 @@ import (
func LoadKeyFromFilePrivate() (crypto.PrivKey, error) { func LoadKeyFromFilePrivate() (crypto.PrivKey, error) {
path := config.GetConfig().PrivateKeyPath path := config.GetConfig().PrivateKeyPath
fmt.Println(path)
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -27,16 +27,9 @@ const (
WORKSPACE_HISTORY WORKSPACE_HISTORY
ORDER ORDER
PURCHASE_RESOURCE PURCHASE_RESOURCE
ADMIRALTY_SOURCE
ADMIRALTY_TARGET
ADMIRALTY_SECRET
ADMIRALTY_KUBECONFIG
ADMIRALTY_NODES
LIVE_DATACENTER LIVE_DATACENTER
LIVE_STORAGE LIVE_STORAGE
BILL BILL
MINIO_SVCACC
MINIO_SVCACC_SECRET
NATIVE_TOOL NATIVE_TOOL
) )
@@ -64,27 +57,9 @@ var DATACENTERAPI = func() string {
var PURCHASEAPI = func() string { var PURCHASEAPI = func() string {
return config.GetConfig().InternalCatalogAPI + "/purchase" return config.GetConfig().InternalCatalogAPI + "/purchase"
} }
var ADMIRALTY_SOURCEAPI = func() string {
return config.GetConfig().InternalDatacenterAPI + "/admiralty/source"
}
var ADMIRALTY_TARGETAPI = func() string {
return config.GetConfig().InternalDatacenterAPI + "/admiralty/target"
}
var ADMIRALTY_SECRETAPI = func() string {
return config.GetConfig().InternalDatacenterAPI + "/admiralty/secret"
}
var ADMIRALTY_KUBECONFIGAPI = func() string {
return config.GetConfig().InternalDatacenterAPI + "/admiralty/kubeconfig"
}
var ADMIRALTY_NODESAPI = func() string {
return config.GetConfig().InternalDatacenterAPI + "/admiralty/node"
}
var MINIO = func() string {
return config.GetConfig().InternalDatacenterAPI + "/minio"
}
// Bind the standard API name to the data type // Bind the standard API name to the data type
var DefaultAPI = [...]func() string{ var InnerDefaultAPI = [...]func() string{
NOAPI, NOAPI,
CATALOGAPI, CATALOGAPI,
CATALOGAPI, CATALOGAPI,
@@ -102,16 +77,9 @@ var DefaultAPI = [...]func() string{
NOAPI, NOAPI,
NOAPI, NOAPI,
PURCHASEAPI, PURCHASEAPI,
ADMIRALTY_SOURCEAPI,
ADMIRALTY_TARGETAPI,
ADMIRALTY_SECRETAPI,
ADMIRALTY_KUBECONFIGAPI,
ADMIRALTY_NODESAPI,
DATACENTERAPI, DATACENTERAPI,
DATACENTERAPI, DATACENTERAPI,
NOAPI, NOAPI,
MINIO,
MINIO,
CATALOGAPI, CATALOGAPI,
} }
@@ -134,25 +102,27 @@ var Str = [...]string{
"workspace_history", "workspace_history",
"order", "order",
"purchase_resource", "purchase_resource",
"admiralty_source",
"admiralty_target",
"admiralty_secret",
"admiralty_kubeconfig",
"admiralty_node",
"live_datacenter", "live_datacenter",
"live_storage", "live_storage",
"bill", "bill",
"service_account",
"secret",
"native_tool", "native_tool",
} }
func FromString(comp string) int {
for i, str := range Str {
if str == comp {
return i
}
}
return -1
}
func FromInt(i int) string { func FromInt(i int) string {
return Str[i] return Str[i]
} }
func (d DataType) API() string { // API - Returns the API name of the data type func (d DataType) InnerAPI() string { // API - Returns the API name of the data type
return DefaultAPI[d]() return InnerDefaultAPI[d]()
} }
func (d DataType) String() string { // String - Returns the string name of the data type func (d DataType) String() string { // String - Returns the string name of the data type
@@ -170,7 +140,7 @@ func (d DataType) EnumIndex() int {
func DataTypeList() []DataType { func DataTypeList() []DataType {
return []DataType{DATA_RESOURCE, PROCESSING_RESOURCE, STORAGE_RESOURCE, COMPUTE_RESOURCE, WORKFLOW_RESOURCE, return []DataType{DATA_RESOURCE, PROCESSING_RESOURCE, STORAGE_RESOURCE, COMPUTE_RESOURCE, WORKFLOW_RESOURCE,
WORKFLOW, WORKFLOW_EXECUTION, WORKSPACE, PEER, COLLABORATIVE_AREA, RULE, BOOKING, WORKFLOW_HISTORY, WORKSPACE_HISTORY, WORKFLOW, WORKFLOW_EXECUTION, WORKSPACE, PEER, COLLABORATIVE_AREA, RULE, BOOKING, WORKFLOW_HISTORY, WORKSPACE_HISTORY,
ORDER, PURCHASE_RESOURCE, ADMIRALTY_SOURCE, ADMIRALTY_TARGET, ADMIRALTY_SECRET, ADMIRALTY_KUBECONFIG, ADMIRALTY_NODES, ORDER, PURCHASE_RESOURCE,
LIVE_DATACENTER, LIVE_STORAGE, BILL, NATIVE_TOOL} LIVE_DATACENTER, LIVE_STORAGE, BILL, NATIVE_TOOL}
} }
@@ -188,6 +158,11 @@ const (
PB_CREATE PB_CREATE
PB_UPDATE PB_UPDATE
PB_DELETE PB_DELETE
PB_PLANNER
PB_CLOSE_PLANNER
PB_CONSIDERS
PB_ADMIRALTY_CONFIG
PB_MINIO_CONFIG
NONE NONE
) )
@@ -203,12 +178,22 @@ func GetActionString(ss string) PubSubAction {
return PB_DELETE return PB_DELETE
case "search_response": case "search_response":
return PB_SEARCH_RESPONSE return PB_SEARCH_RESPONSE
case "planner":
return PB_PLANNER
case "close_planner":
return PB_CLOSE_PLANNER
case "considers":
return PB_CONSIDERS
case "admiralty_config":
return PB_ADMIRALTY_CONFIG
case "minio_config":
return PB_MINIO_CONFIG
default: default:
return NONE return NONE
} }
} }
var path = []string{"search", "search_response", "create", "update", "delete"} var path = []string{"search", "search_response", "create", "update", "delete", "planner", "close_planner", "considers", "admiralty_config", "minio_config"}
func (m PubSubAction) String() string { func (m PubSubAction) String() string {
return strings.ToUpper(path[m]) return strings.ToUpper(path[m])

628
tools/kubernetes.go Normal file
View File

@@ -0,0 +1,628 @@
package tools
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"cloud.o-forge.io/core/oc-lib/logs"
authv1 "k8s.io/api/authentication/v1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
apply "k8s.io/client-go/applyconfigurations/core/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
var gvrSources = schema.GroupVersionResource{Group: "multicluster.admiralty.io", Version: "v1alpha1", Resource: "sources"}
var gvrTargets = schema.GroupVersionResource{Group: "multicluster.admiralty.io", Version: "v1alpha1", Resource: "targets"}
type KubernetesService struct {
Set *kubernetes.Clientset
Host string
CA string
Cert string
Data string
}
func NewDynamicClient(host string, ca string, cert string, data string) (*dynamic.DynamicClient, error) {
config := &rest.Config{
Host: host,
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte(ca),
CertData: []byte(cert),
KeyData: []byte(data),
},
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, errors.New("Error creating Dynamic client: " + err.Error())
}
if dynamicClient == nil {
return nil, errors.New("Error creating Dynamic client: dynamicClient is nil")
}
return dynamicClient, nil
}
func NewKubernetesService(host string, ca string, cert string, data string) (*KubernetesService, error) {
config := &rest.Config{
Host: host,
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte(ca),
CertData: []byte(cert),
KeyData: []byte(data),
},
}
// Create clientset
clientset, err := kubernetes.NewForConfig(config)
fmt.Println("NewForConfig", clientset, err)
if err != nil {
return nil, errors.New("Error creating Kubernetes client: " + err.Error())
}
if clientset == nil {
return nil, errors.New("Error creating Kubernetes client: clientset is nil")
}
return &KubernetesService{
Set: clientset,
Host: host,
CA: ca,
Cert: cert,
Data: data,
}, nil
}
func NewRemoteKubernetesService(url string, ca string, cert string, key string) (*KubernetesService, error) {
decodedCa, _ := base64.StdEncoding.DecodeString(ca)
decodedCert, _ := base64.StdEncoding.DecodeString(cert)
decodedKey, _ := base64.StdEncoding.DecodeString(key)
config := &rest.Config{
Host: url + ":6443",
TLSClientConfig: rest.TLSClientConfig{
CAData: decodedCa,
CertData: decodedCert,
KeyData: decodedKey,
},
}
// Create clientset
clientset, err := kubernetes.NewForConfig(config)
fmt.Println("NewForConfig", clientset, err)
if err != nil {
return nil, errors.New("Error creating Kubernetes client: " + err.Error())
}
if clientset == nil {
return nil, errors.New("Error creating Kubernetes client: clientset is nil")
}
return &KubernetesService{
Set: clientset,
Host: url,
CA: string(decodedCa),
Cert: string(decodedCert),
Data: string(decodedKey),
}, nil
}
func (k *KubernetesService) CreateNamespace(ctx context.Context, ns string) error {
// Define the namespace
fmt.Println("ExecutionID in CreateNamespace() : ", ns)
namespace := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
Labels: map[string]string{
"multicluster-scheduler": "enabled",
},
},
}
// Create the namespace
fmt.Println("Creating namespace...", k.Set)
if _, err := k.Set.CoreV1().Namespaces().Create(ctx, namespace, metav1.CreateOptions{}); err != nil {
return errors.New("Error creating namespace: " + err.Error())
}
fmt.Println("Namespace created successfully!")
return nil
}
func (k *KubernetesService) CreateServiceAccount(ctx context.Context, ns string) error {
// Create the ServiceAccount object
serviceAccount := &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "sa-" + ns,
Namespace: ns,
},
}
// Create the ServiceAccount in the specified namespace
_, err := k.Set.CoreV1().ServiceAccounts(ns).Create(ctx, serviceAccount, metav1.CreateOptions{})
if err != nil {
return errors.New("Failed to create ServiceAccount: " + err.Error())
}
return nil
}
func (k *KubernetesService) CreateRole(ctx context.Context, ns string, role string, groups [][]string, resources [][]string, verbs [][]string) error {
// Create the Role object
if len(groups) != len(resources) || len(resources) != len(verbs) {
return errors.New("Invalid input: groups, resources, and verbs must have the same length")
}
rules := []rbacv1.PolicyRule{}
for i, group := range groups {
rules = append(rules, rbacv1.PolicyRule{
APIGroups: group,
Resources: resources[i],
Verbs: verbs[i],
})
}
r := &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: role,
Namespace: ns,
},
Rules: rules,
}
// Create the Role in the specified namespace
_, err := k.Set.RbacV1().Roles(ns).Create(ctx, r, metav1.CreateOptions{})
if err != nil {
return errors.New("Failed to create Role: " + err.Error())
}
return nil
}
func (k *KubernetesService) CreateRoleBinding(ctx context.Context, ns string, roleBinding string, role string) error {
// Create the RoleBinding object
rb := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: roleBinding,
Namespace: ns,
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: "sa-" + ns,
Namespace: ns,
},
},
RoleRef: rbacv1.RoleRef{
Kind: "Role",
Name: role,
APIGroup: "rbac.authorization.k8s.io",
},
}
// Create the RoleBinding in the specified namespace
_, err := k.Set.RbacV1().RoleBindings(ns).Create(ctx, rb, metav1.CreateOptions{})
if err != nil {
return errors.New("Failed to create RoleBinding: " + err.Error())
}
return nil
}
func (k *KubernetesService) DeleteNamespace(ctx context.Context, ns string, f func()) error {
targetGVR := schema.GroupVersionResource{
Group: "multicluster.admiralty.io",
Version: "v1alpha1",
Resource: "targets",
}
// Delete the Target
dyn, err := NewDynamicClient(k.Host, k.CA, k.Cert, k.Data)
if err != nil {
return err
}
err = dyn.Resource(targetGVR).Namespace(ns).Delete(context.TODO(), "target-"+ns, metav1.DeleteOptions{})
if err != nil {
return err
}
err = k.Set.CoreV1().ServiceAccounts(ns).Delete(context.TODO(), "sa-"+ns, metav1.DeleteOptions{})
if err != nil {
return err
}
// Delete the namespace
if err := k.Set.CoreV1().Namespaces().Delete(ctx, ns, metav1.DeleteOptions{}); err != nil {
return errors.New("Error deleting namespace: " + err.Error())
}
f()
// monitor.StreamRegistry.Cancel(ns)
fmt.Println("Namespace deleted successfully!")
return nil
}
// Returns the string representing the token generated for the serviceAccount
// in the namespace identified by the value `ns` with the name sa-`ns`, which is valid for
// `duration` seconds
func (k *KubernetesService) GenerateToken(ctx context.Context, ns string, duration int) (string, error) {
// Define TokenRequest (valid for 1 hour)
d := int64(duration)
tokenRequest := &authv1.TokenRequest{
Spec: authv1.TokenRequestSpec{
ExpirationSeconds: &d, // 1 hour validity
},
}
// Generate the token
token, err := k.Set.CoreV1().
ServiceAccounts(ns).
CreateToken(ctx, "sa-"+ns, tokenRequest, metav1.CreateOptions{})
if err != nil {
return "", errors.New("Failed to create token for ServiceAccount: " + err.Error())
}
return token.Status.Token, nil
}
// Needs refactoring :
// - Retrieving the metada (in a method that Unmarshall the part of the json in a metadata object)
func (k *KubernetesService) GetTargets(ctx context.Context) ([]string, error) {
var listTargets []string
resp, err := getCDRapiKube(*k.Set, ctx, "/apis/multicluster.admiralty.io/v1alpha1/targets")
if err != nil {
return nil, err
}
fmt.Println(string(resp))
var targetDict map[string]interface{}
err = json.Unmarshal(resp, &targetDict)
if err != nil {
fmt.Println("TODO: handle the error when unmarshalling k8s API response")
return nil, err
}
b, _ := json.MarshalIndent(targetDict, "", " ")
fmt.Println(string(b))
data := targetDict["items"].([]interface{})
for _, item := range data {
var metadata metav1.ObjectMeta
item := item.(map[string]interface{})
byteMetada, err := json.Marshal(item["metadata"])
if err != nil {
fmt.Println("Error while Marshalling metadata field")
return nil, err
}
err = json.Unmarshal(byteMetada, &metadata)
if err != nil {
fmt.Println("Error while Unmarshalling metadata field to the library object")
return nil, err
}
listTargets = append(listTargets, metadata.Name)
}
return listTargets, nil
}
// Admiralty Target allows a cluster to deploy pods to remote cluster
//
// The remote cluster must :
//
// - have declared a Source resource
//
// - have declared the same namespace as the one where the pods are created in the local cluster
//
// - have delcared a serviceAccount with sufficient permission to create pods
func (k *KubernetesService) CreateAdmiraltyTarget(context context.Context, executionId string, peerId string) ([]byte, error) {
exists, err := k.GetKubeconfigSecret(context, executionId, peerId)
if err != nil {
fmt.Println("Error verifying kube-secret before creating target")
return nil, err
}
if exists == nil {
fmt.Println("Target needs to be binded to a secret in namespace ", executionId)
return nil, nil // Maybe we could create a wrapper for errors and add more info to have
}
targetName := "target-" + GetConcatenatedName(peerId, executionId)
target := map[string]interface{}{
"apiVersion": "multicluster.admiralty.io/v1alpha1",
"kind": "Target",
"metadata": map[string]interface{}{
"name": targetName,
"namespace": executionId,
// "labels": map[string]interface{}{
// "peer": peerId,
// },
},
"spec": map[string]interface{}{
"kubeconfigSecret": map[string]string{
"name": "kube-secret-" + GetConcatenatedName(peerId, executionId),
},
},
}
res, err := dynamicClientApply(k.Host, k.CA, k.Cert, k.Data, executionId, targetName, gvrTargets, context, target)
if err != nil {
return nil, errors.New("Error when trying to apply Target definition :" + err.Error())
}
return res, nil
}
// Admiralty Source allows a cluster to receive pods from a remote cluster
//
// The source must be associated to a serviceAccount, which will execute the pods locally.
// This serviceAccount must have sufficient permission to create and patch pods
//
// This method is temporary to implement the use of Admiralty, but must be edited
// to rather contact the oc-datacenter from the remote cluster to create the source
// locally and retrieve the token for the serviceAccount
func (k *KubernetesService) CreateAdmiraltySource(context context.Context, executionId string) ([]byte, error) {
source := map[string]interface{}{
"apiVersion": "multicluster.admiralty.io/v1alpha1",
"kind": "Source",
"metadata": map[string]interface{}{
"name": "source-" + executionId,
"namespace": executionId,
},
"spec": map[string]interface{}{
"serviceAccountName": "sa-" + executionId,
},
}
res, err := dynamicClientApply(k.Host, k.CA, k.Cert, k.Data, executionId, "source-"+executionId, gvrSources, context, source)
if err != nil {
return nil, errors.New("Error when trying to apply Source definition :" + err.Error())
}
return res, nil
}
// Create a secret from a kubeconfing. Use it to create the secret binded to an Admiralty
// target, which must contain the serviceAccount's token value
func (k *KubernetesService) CreateKubeconfigSecret(context context.Context, kubeconfig string, executionId string, peerId string) ([]byte, error) {
config, err := base64.StdEncoding.DecodeString(kubeconfig)
// config, err := base64.RawStdEncoding.DecodeString(kubeconfig)
if err != nil {
fmt.Println("Error while encoding kubeconfig")
fmt.Println(err)
return nil, err
}
secretApplyConfig := apply.Secret("kube-secret-"+GetConcatenatedName(peerId, executionId),
executionId).
WithData(map[string][]byte{
"config": config,
},
)
// exists, err := k.GetKubeconfigSecret(context,executionId)
// if err != nil {
// fmt.Println("Error verifying if kube secret exists in namespace ", executionId)
// return nil, err
// }
// if exists != nil {
// fmt.Println("kube-secret already exists in namespace", executionId)
// fmt.Println("Overriding existing kube-secret with a newer resource")
// // TODO : implement DeleteKubeConfigSecret(executionID)
// deleted, err := k.DeleteKubeConfigSecret(executionId)
// _ = deleted
// _ = err
// }
resp, err := k.Set.CoreV1().
Secrets(executionId).
Apply(context,
secretApplyConfig,
metav1.ApplyOptions{
FieldManager: "admiralty-manager",
})
if err != nil {
fmt.Println("Error while trying to contact API to get secret kube-secret-" + executionId)
fmt.Println(err)
return nil, err
}
data, err := json.Marshal(resp)
if err != nil {
fmt.Println("Couldn't marshal resp from : ", data)
fmt.Println(err)
return nil, err
}
return data, nil
}
func (k *KubernetesService) GetKubeconfigSecret(context context.Context, executionId string, peerId string) ([]byte, error) {
resp, err := k.Set.CoreV1().
Secrets(executionId).
Get(context, "kube-secret-"+GetConcatenatedName(peerId, executionId), metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
fmt.Println("kube-secret not found for execution", executionId)
return nil, nil
}
fmt.Println("Error while trying to contact API to get secret kube-secret-" + executionId)
fmt.Println(err)
return nil, err
}
data, err := json.Marshal(resp)
if err != nil {
fmt.Println("Couldn't marshal resp from : ", data)
fmt.Println(err)
return nil, err
}
return data, nil
}
func (k *KubernetesService) DeleteKubeConfigSecret(executionID string) ([]byte, error) {
return []byte{}, nil
}
func (k *KubernetesService) GetNamespace(context context.Context, executionID string) (*v1.Namespace, error) {
resp, err := k.Set.CoreV1().Namespaces().Get(context, executionID, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return nil, nil
}
if err != nil {
logger := logs.GetLogger()
logger.Error().Msg("An error occured when trying to get namespace " + executionID + " : " + err.Error())
return nil, err
}
return resp, nil
}
func getCDRapiKube(client kubernetes.Clientset, ctx context.Context, path string) ([]byte, error) {
resp, err := client.RESTClient().Get().
AbsPath(path).
DoRaw(ctx) // from https://stackoverflow.com/questions/60764908/how-to-access-kubernetes-crd-using-client-go
if err != nil {
fmt.Println("Error from k8s API when getting "+path+" : ", err)
return nil, err
}
return resp, nil
}
func dynamicClientApply(host string, ca string, cert string, data string, executionId string, resourceName string, resourceDefinition schema.GroupVersionResource, ctx context.Context, object map[string]interface{}) ([]byte, error) {
cli, err := NewDynamicClient(host, ca, cert, data)
if err != nil {
return nil, errors.New("Could not retrieve dynamic client when creating Admiralty Source : " + err.Error())
}
res, err := cli.Resource(resourceDefinition).
Namespace(executionId).
Apply(ctx,
resourceName,
&unstructured.Unstructured{Object: object},
metav1.ApplyOptions{
FieldManager: "kubectl-client-side-apply",
},
)
if err != nil {
o, err := json.Marshal(object)
fmt.Println("Error from k8s API when applying "+fmt.Sprint(string(o))+" to "+gvrSources.String()+" : ", err)
return nil, err
}
// We can add more info to the log with the content of resp if not nil
resByte, err := json.Marshal(res)
if err != nil {
// fmt.Println("Error trying to create a Source on remote cluster : ", err , " : ", res)
return nil, err
}
return resByte, nil
}
func (k *KubernetesService) CheckHealth() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Check API server connectivity
_, err := k.Set.ServerVersion()
if err != nil {
return fmt.Errorf("API server unreachable: %v", err)
}
// Check nodes status
nodes, err := k.Set.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
return fmt.Errorf("failed to list nodes: %v", err)
}
for _, node := range nodes.Items {
for _, condition := range node.Status.Conditions {
if condition.Type == "Ready" && condition.Status != "True" {
return fmt.Errorf("node %s not ready", node.Name)
}
}
}
// Optional: Check if all pods in kube-system are running
pods, err := k.Set.CoreV1().Pods("kube-system").List(ctx, metav1.ListOptions{})
if err != nil {
return fmt.Errorf("failed to list pods: %v", err)
}
for _, pod := range pods.Items {
if pod.Status.Phase != "Running" && pod.Status.Phase != "Succeeded" {
return fmt.Errorf("pod %s in namespace kube-system is %s", pod.Name, pod.Status.Phase)
}
}
return nil
}
// Returns the Kubernetes' Node object corresponding to the executionID if it exists on this host
//
// The node is created when an admiralty Target (on host) can connect to an admiralty Source (on remote)
func (k *KubernetesService) GetOneNode(context context.Context, executionID string, peerId string) (*v1.Node, error) {
concatenatedName := GetConcatenatedName(peerId, executionID)
res, err := k.Set.CoreV1().
Nodes().
List(
context,
metav1.ListOptions{},
)
if err != nil {
fmt.Println("Error getting the list of nodes from k8s API")
fmt.Println(err)
return nil, err
}
for _, node := range res.Items {
if isNode := strings.Contains(node.Name, "admiralty-"+executionID+"-target-"+concatenatedName+"-"); isNode {
return &node, nil
}
}
return nil, nil
}
func (k *KubernetesService) CreateSecret(context context.Context, minioId string, executionID string, access string, secret string) error {
data := map[string][]byte{
"access-key": []byte(access),
"secret-key": []byte(secret),
}
s := v1.Secret{
Type: v1.SecretTypeOpaque,
Data: data,
ObjectMeta: metav1.ObjectMeta{
Name: minioId + "-secret-s3",
},
}
_, err := k.Set.CoreV1().Secrets(executionID).Create(context, &s, metav1.CreateOptions{})
if err != nil {
logger := logs.GetLogger()
logger.Error().Msg("An error happened when creating the secret holding minio credentials in namespace " + executionID + " : " + err.Error())
return err
}
return nil
}
// ============== ADMIRALTY ==============
// Returns a concatenation of the peerId and namespace in order for
// kubernetes ressources to have a unique name, under 63 characters
// and yet identify which peer they are created for
func GetConcatenatedName(peerId string, namespace string) string {
s := strings.Split(namespace, "-")[:2]
n := s[0] + "-" + s[1]
return peerId + "-" + n
}

View File

@@ -14,36 +14,35 @@ import (
) )
type NATSResponse struct { type NATSResponse struct {
FromApp string `json:"from_app"` FromApp string `json:"from_app"`
Datatype DataType `json:"datatype"` Datatype DataType `json:"datatype"`
User string `json:"user"` User string `json:"user"`
Method int `json:"method"` Method int `json:"method"`
Payload []byte `json:"payload"` SearchAttr string `json:"search_attr"`
Payload []byte `json:"payload"`
} }
// NATS Method Enum defines the different methods that can be used to interact with the NATS server // NATS Method Enum defines the different methods that can be used to interact with the NATS server
type NATSMethod int type NATSMethod int
var meths = []string{"remove execution", "create execution", "discovery", var meths = []string{"remove execution", "create execution", "discovery",
"workflow event", "remove peer", "create peer", "create resource", "remove resource", "verify_resource", "workflow event", "argo kube event", "create resource", "remove resource",
"propalgation event", "catalogsearch event", "propalgation event", "search event",
} }
const ( const (
REMOVE_EXECUTION NATSMethod = iota REMOVE_EXECUTION NATSMethod = iota
CREATE_EXECTUTION CREATE_EXECTUTION
DISCOVERY DISCOVERY
WORKFLOW_EVENT
REMOVE_PEER WORKFLOW_EVENT
CREATE_PEER ARGO_KUBE_EVENT
CREATE_RESOURCE CREATE_RESOURCE
REMOVE_RESOURCE REMOVE_RESOURCE
VERIFY_RESOURCE
PROPALGATION_EVENT PROPALGATION_EVENT
CATALOG_SEARCH_EVENT SEARCH_EVENT
) )
func (n NATSMethod) String() string { func (n NATSMethod) String() string {
@@ -52,8 +51,8 @@ func (n NATSMethod) String() string {
// NameToMethod returns the NATSMethod enum value from a string // NameToMethod returns the NATSMethod enum value from a string
func NameToMethod(name string) NATSMethod { func NameToMethod(name string) NATSMethod {
for _, v := range [...]NATSMethod{REMOVE_EXECUTION, CREATE_EXECTUTION, DISCOVERY, WORKFLOW_EVENT, for _, v := range [...]NATSMethod{REMOVE_EXECUTION, CREATE_EXECTUTION, DISCOVERY, WORKFLOW_EVENT, ARGO_KUBE_EVENT,
REMOVE_PEER, CREATE_PEER, CREATE_RESOURCE, REMOVE_RESOURCE, VERIFY_RESOURCE, PROPALGATION_EVENT, CATALOG_SEARCH_EVENT} { CREATE_RESOURCE, REMOVE_RESOURCE, PROPALGATION_EVENT, SEARCH_EVENT} {
if strings.Contains(strings.ToLower(v.String()), strings.ToLower(name)) { if strings.Contains(strings.ToLower(v.String()), strings.ToLower(name)) {
return v return v
} }

View File

@@ -54,8 +54,6 @@ type HTTPCallerITF interface {
CallDelete(url string, subpath string) ([]byte, error) CallDelete(url string, subpath string) ([]byte, error)
} }
var HTTPCallerInstance = &HTTPCaller{} // Singleton instance of the HTTPCaller
type HTTPCaller struct { type HTTPCaller struct {
URLS map[DataType]map[METHOD]string // Map of the different methods and their urls URLS map[DataType]map[METHOD]string // Map of the different methods and their urls
Disabled bool // Disabled flag Disabled bool // Disabled flag
@@ -115,7 +113,7 @@ func (caller *HTTPCaller) CallDelete(url string, subpath string) ([]byte, error)
} }
client := &http.Client{} client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil || req == nil || req.Body == nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()