Compare commits
24 Commits
feature/mu
...
a7ffede3e2
| Author | SHA1 | Date | |
|---|---|---|---|
| a7ffede3e2 | |||
| 6c0b07b49d | |||
| e4834db518 | |||
| b45c795002 | |||
| 41750dc054 | |||
| 3d27da3e7c | |||
| c99f161a51 | |||
| bcd82675fc | |||
| 6dbee462b4 | |||
| 9bfcf910ed | |||
| bb8adef43e | |||
| 276c0f793a | |||
| 68ab051103 | |||
| 466ca6984c | |||
| 1a4dbb172b | |||
| df076755f6 | |||
| f3add8d75c | |||
| 1b3eb0e61c | |||
| 66de8d7541 | |||
| e1eb658037 | |||
| fde7031bf4 | |||
| 607c357273 | |||
| be18c0bfb3 | |||
| 03ae522e73 |
24
.gitignore
vendored
24
.gitignore
vendored
@@ -1 +1,23 @@
|
|||||||
swagger/
|
# ---> Go
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ FROM golang:alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add git
|
|
||||||
|
|
||||||
RUN go install github.com/beego/bee/v2@latest
|
RUN go install github.com/beego/bee/v2@latest
|
||||||
|
|
||||||
WORKDIR /oc-datacenter
|
WORKDIR /oc-datacenter
|
||||||
@@ -30,13 +28,8 @@ RUN export CGO_ENABLED=0 && \
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN sed -i '/replace/d' go.mod
|
RUN sed -i '/replace/d' go.mod
|
||||||
RUN if [ ! -f swagger/index.html ]; then timeout 15 bee run -gendoc=true -downdoc=true; fi
|
|
||||||
RUN bee generate routers
|
|
||||||
RUN bee generate docs
|
|
||||||
RUN bee pack
|
RUN bee pack
|
||||||
RUN mkdir -p /app/extracted && tar -zxvf oc-datacenter.tar.gz -C /app/extracted
|
RUN mkdir -p /app/extracted && tar -zxvf oc-datacenter.tar.gz -C /app/extracted
|
||||||
RUN sed -i 's/http:\/\/127.0.0.1:8080\/swagger\/swagger.json/swagger.json/g' /app/extracted/swagger/index.html
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------------------------------
|
#----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
FROM golang:alpine
|
FROM golang:alpine
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -21,20 +21,22 @@ clean:
|
|||||||
rm -rf oc-datacenter.tar.gz
|
rm -rf oc-datacenter.tar.gz
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
DOCKER_BUILDKIT=1 docker build -t oc-datacenter -f Dockerfile . --build-arg=HOST=$(HOST)
|
DOCKER_BUILDKIT=1 docker build -t oc-datacenter -f Dockerfile . --build-arg=HOST=$(HOST) --build-arg=KUBERNETES_HOST=$(KUBERNETES_HOST) --build-arg=KUBERNETES_SERVICE_PORT=$(KUBERNETES_SERVICE_PORT) --build-arg=KUBE_CA=$(KUBE_CA) --build-arg=KUBE_CERT=$(KUBE_CERT) --build-arg=KUBE_DATA=$(KUBE_DATA)
|
||||||
docker tag oc-datacenter:latest oc/oc-datacenter:0.0.1
|
docker tag oc-datacenter opencloudregistry/oc-datacenter:latest
|
||||||
|
|
||||||
publish-kind:
|
publish-kind:
|
||||||
kind load docker-image oc/oc-datacenter:0.0.1 --name opencloud | true
|
kind load docker-image opencloudregistry/oc-datacenter:latest --name $(CLUSTER_NAME) | true
|
||||||
|
|
||||||
publish-registry:
|
publish-registry:
|
||||||
@echo "TODO"
|
docker push opencloudregistry/oc-datacenter:latest
|
||||||
|
|
||||||
docker-deploy:
|
docker-deploy:
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
run-docker: docker publish-kind publish-registry docker-deploy
|
run-docker: docker publish-kind publish-registry docker-deploy
|
||||||
|
|
||||||
all: docker publish-kind publish-registry
|
all: docker publish-kind
|
||||||
|
|
||||||
|
ci: docker publish-registry
|
||||||
|
|
||||||
.PHONY: build run clean docker publish-kind publish-registry
|
.PHONY: build run clean docker publish-kind publish-registry
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ package conf
|
|||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Mode string
|
Mode string
|
||||||
KubeHost string
|
KubeHost string
|
||||||
KubePort string
|
KubePort string
|
||||||
KubeCA string
|
KubeCA string
|
||||||
KubeCert string
|
KubeCert string
|
||||||
KubeData string
|
KubeData string
|
||||||
MinioRootKey string
|
MinioRootKey string
|
||||||
MinioRootSecret string
|
MinioRootSecret string
|
||||||
|
MonitorMode string
|
||||||
|
MonitorAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
var instance *Config
|
var instance *Config
|
||||||
|
|||||||
@@ -1,601 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"oc-datacenter/conf"
|
|
||||||
"oc-datacenter/infrastructure"
|
|
||||||
"oc-datacenter/models"
|
|
||||||
"slices"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
|
||||||
|
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
|
||||||
jwt "github.com/golang-jwt/jwt/v5"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type KubeInfo struct {
|
|
||||||
Url *string
|
|
||||||
KubeCA *string
|
|
||||||
KubeCert *string
|
|
||||||
KubeKey *string
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoteKubeconfig struct {
|
|
||||||
Data *string
|
|
||||||
}
|
|
||||||
|
|
||||||
type KubeUser struct {
|
|
||||||
Name string
|
|
||||||
User struct {
|
|
||||||
Token string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type KubeconfigToken struct {
|
|
||||||
ApiVersion string `yaml:"apiVersion"`
|
|
||||||
Kind string `yaml:"kind"`
|
|
||||||
Preferences string `yaml:"preferences"`
|
|
||||||
CurrentContext string `yaml:"current-context"`
|
|
||||||
Clusters []struct {
|
|
||||||
Cluster struct {
|
|
||||||
CA string `yaml:"certificate-authority-data"`
|
|
||||||
Server string `yaml:"server"`
|
|
||||||
} `yaml:"cluster"`
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
} `yaml:"clusters"`
|
|
||||||
Contexts []struct {
|
|
||||||
Context struct {
|
|
||||||
Cluster string `yaml:"cluster"`
|
|
||||||
User string `yaml:"user"`
|
|
||||||
} `yaml:"context"`
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
} `yaml:"contexts"`
|
|
||||||
Users []struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
User struct {
|
|
||||||
Token string `yaml:"token"`
|
|
||||||
} `yaml:"user"`
|
|
||||||
} `yaml:"users"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operations about the admiralty objects of the datacenter
|
|
||||||
type AdmiraltyController struct {
|
|
||||||
beego.Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title GetAllTargets
|
|
||||||
// @Description find all Admiralty Target
|
|
||||||
// @Success 200
|
|
||||||
// @router /targets [get]
|
|
||||||
func (c *AdmiraltyController) GetAllTargets() {
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
HandleControllerErrors(c.Controller, 500, &err, nil)
|
|
||||||
// c.Ctx.Output.SetStatus(500)
|
|
||||||
// c.ServeJSON()
|
|
||||||
// c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := serv.GetTargets(c.Ctx.Request.Context())
|
|
||||||
c.Data["json"] = res
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title GetOneTarget
|
|
||||||
// @Description find one Admiralty Target
|
|
||||||
// @Param id path string true "the name of the target to get"
|
|
||||||
// @Success 200
|
|
||||||
// @router /targets/:execution [get]
|
|
||||||
func (c *AdmiraltyController) GetOneTarget() {
|
|
||||||
id := c.Ctx.Input.Param(":execution")
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.ServeJSON()
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := serv.GetTargets(c.Ctx.Request.Context())
|
|
||||||
if err != nil {
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.ServeJSON()
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id = "target-" + id
|
|
||||||
found := slices.Contains(res, id)
|
|
||||||
if !found {
|
|
||||||
c.Ctx.Output.SetStatus(404)
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = id
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title DeleteAdmiraltySession
|
|
||||||
// @Description find one Admiralty Target
|
|
||||||
// @Param execution path string true "the name of the target to get"
|
|
||||||
// @Success 200
|
|
||||||
// @router /targets/:execution [delete]
|
|
||||||
func (c *AdmiraltyController) DeleteAdmiraltySession() {
|
|
||||||
id := c.Ctx.Input.Param(":execution")
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.ServeJSON()
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = serv.DeleteNamespace(c.Ctx.Request.Context(), id)
|
|
||||||
if err != nil {
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.ServeJSON()
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = id
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title CreateAdmiraltySource
|
|
||||||
// @Description Create an Admiralty Source on remote cluster
|
|
||||||
// @Param execution path string true "execution id of the workflow"
|
|
||||||
// @Success 201
|
|
||||||
// @router /source/:execution [post]
|
|
||||||
func (c *AdmiraltyController) CreateAdmiraltySource() {
|
|
||||||
|
|
||||||
execution := c.Ctx.Input.Param(":execution")
|
|
||||||
fmt.Println("execution :: ", execution)
|
|
||||||
fmt.Println("input :: ", c.Ctx.Input)
|
|
||||||
serv, err := infrastructure.NewKubernetesService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.ServeJSON()
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := serv.CreateAdmiraltySource(c.Ctx.Request.Context(), execution)
|
|
||||||
if err != nil {
|
|
||||||
if apierrors.IsAlreadyExists(err) {
|
|
||||||
c.Ctx.Output.SetStatus(409)
|
|
||||||
c.Data["json"] = map[string]string{"info": "A source already exists for this namespace : " + execution}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO : Return a description of the created resource
|
|
||||||
var respData map[string]interface{}
|
|
||||||
err = json.Unmarshal(res, &respData)
|
|
||||||
if err != nil {
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.ServeJSON()
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Ctx.Output.SetStatus(201)
|
|
||||||
c.Data["json"] = respData
|
|
||||||
c.ServeJSON()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title CreateAdmiraltyTarget
|
|
||||||
// @Description Create an Admiralty Target in the namespace associated to the executionID
|
|
||||||
// @Param execution path string true "execution id of the workflow"
|
|
||||||
// @Param peer path string true "peerId of the peer the target points to"
|
|
||||||
// @Success 201
|
|
||||||
// @router /target/:execution/:peer [post]
|
|
||||||
func (c *AdmiraltyController) CreateAdmiraltyTarget() {
|
|
||||||
var data map[string]interface{}
|
|
||||||
|
|
||||||
execution := c.Ctx.Input.Param(":execution")
|
|
||||||
peerId := c.Ctx.Input.Param(":peer")
|
|
||||||
|
|
||||||
if execution == "" || peerId == "" {
|
|
||||||
c.Ctx.Output.SetStatus(400)
|
|
||||||
c.Data["json"] = map[string]string{"error": "parameters can be empty " + "execution: " + execution + " peer: " + peerId}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := serv.CreateAdmiraltyTarget(c.Ctx.Request.Context(), execution, peerId)
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
fmt.Println("Error while trying to create Admiralty target")
|
|
||||||
fmt.Println(resp)
|
|
||||||
fmt.Println(err)
|
|
||||||
c.Ctx.Output.SetStatus(401)
|
|
||||||
c.Data["json"] = map[string]string{"error": "Could not perform the action"}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(resp, &data)
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.ServeJSON()
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Ctx.Output.SetStatus(201)
|
|
||||||
c.Data["json"] = data
|
|
||||||
c.ServeJSON()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title GetKubeSecret
|
|
||||||
// @Description Retrieve the secret created from a Kubeconfig that will be associated to an Admiralty Target
|
|
||||||
// @Param execution path string true "execution id of the workflow"
|
|
||||||
// @Param peer path string true "UUID of the peer to which the resource is linked"
|
|
||||||
// @Success 200
|
|
||||||
// @router /secret/:execution/:peer [get]
|
|
||||||
func (c *AdmiraltyController) GetKubeSecret() {
|
|
||||||
var data map[string]interface{}
|
|
||||||
|
|
||||||
execution := c.Ctx.Input.Param(":execution")
|
|
||||||
peerId := c.Ctx.Input.Param(":peer")
|
|
||||||
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(), execution, peerId)
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
c.Ctx.Output.SetStatus(404)
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(resp, &data)
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.ServeJSON()
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = data
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title CreateKubeSecret
|
|
||||||
// @Description Creat a secret from a Kubeconfig that will be associated to an Admiralty Target
|
|
||||||
|
|
||||||
// @Param execution path string true "execution id of the workflow"
|
|
||||||
// @Param peer path string true "UUID of the peer to which the resource is linked"
|
|
||||||
// @Param kubeconfig body controllers.RemoteKubeconfig true "Kubeconfig to use when creating secret"
|
|
||||||
// @Success 201
|
|
||||||
// @router /secret/:execution/:peer [post]
|
|
||||||
func (c *AdmiraltyController) CreateKubeSecret() {
|
|
||||||
var kubeconfig RemoteKubeconfig
|
|
||||||
var respData map[string]interface{}
|
|
||||||
|
|
||||||
data := c.Ctx.Input.CopyBody(100000)
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, &kubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error when retrieving the data for kubeconfig from request")
|
|
||||||
fmt.Println(err)
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
execution := c.Ctx.Input.Param(":execution")
|
|
||||||
peerId := c.Ctx.Input.Param(":peer")
|
|
||||||
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := serv.CreateKubeconfigSecret(c.Ctx.Request.Context(), *kubeconfig.Data, execution, peerId)
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(resp, &respData)
|
|
||||||
c.Ctx.Output.SetStatus(201)
|
|
||||||
c.Data["json"] = respData
|
|
||||||
c.ServeJSON()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// @name GetAdmiraltyNodes
|
|
||||||
// @description Allows user to test if an admiralty connection has already been established : Target and valid Secret set up on the local host and Source set up on remote host
|
|
||||||
// @Param execution path string true "execution id of the workflow"
|
|
||||||
// @Param peer path string true "UUID of the peer to which the resource is linked"
|
|
||||||
// @Success 200
|
|
||||||
// @router /node/:execution/:peer [get]
|
|
||||||
func (c *AdmiraltyController) GetNodeReady() {
|
|
||||||
var secret v1.Secret
|
|
||||||
execution := c.Ctx.Input.Param(":execution")
|
|
||||||
peerId := c.Ctx.Input.Param(":peer")
|
|
||||||
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
node, err := serv.GetOneNode(c.Ctx.Request.Context(), execution, peerId)
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if node == nil {
|
|
||||||
c.Ctx.Output.SetStatus(404)
|
|
||||||
c.Data["json"] = map[string]string{
|
|
||||||
"node": "the node for " + execution + " can't be found, make sure both target and source resources are set up on local and remote hosts",
|
|
||||||
}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(), execution, peerId)
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": "Nodes was up but the secret can't be found"}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract JWT token RS265 encoded
|
|
||||||
var editedKubeconfig map[string]interface{}
|
|
||||||
json.Unmarshal(resp, &secret)
|
|
||||||
byteEditedKubeconfig := secret.Data["config"]
|
|
||||||
err = yaml.Unmarshal(byteEditedKubeconfig, &editedKubeconfig)
|
|
||||||
// err = json.Unmarshal(byteEditedKubeconfig,&editedKubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error while retrieving the kubeconfig from secret-", execution)
|
|
||||||
fmt.Println(err)
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = err
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := retrieveTokenFromKonfig(editedKubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error while trying to retrieve token for kubeconfing")
|
|
||||||
fmt.Println(err)
|
|
||||||
HandleControllerErrors(c.Controller, 500, &err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode token
|
|
||||||
isExpired, err := isTokenExpired(token)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error veryfing token's expiration")
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = err
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
if *isExpired {
|
|
||||||
c.Data["json"] = map[string]interface{}{
|
|
||||||
"token": "token in the secret is expired and must be regenerated",
|
|
||||||
"node": node,
|
|
||||||
}
|
|
||||||
c.Ctx.Output.SetStatus(410)
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = map[string]interface{}{"node": node, "token": true}
|
|
||||||
c.ServeJSON()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func retrieveTokenFromKonfig(editedKubeconfig map[string]interface{}) (string, error) {
|
|
||||||
var kubeUsers []KubeUser
|
|
||||||
b, err := yaml.Marshal(editedKubeconfig["users"])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error while retrieving the users attribute from the Kubeconfig")
|
|
||||||
fmt.Println(err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = yaml.Unmarshal(b, &kubeUsers)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error while unmarshalling users attribute from kubeconfig")
|
|
||||||
fmt.Println(err)
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
fmt.Println(kubeUsers)
|
|
||||||
token := kubeUsers[0].User.Token
|
|
||||||
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isTokenExpired(token string) (*bool, error) {
|
|
||||||
logger := oclib.GetLogger()
|
|
||||||
|
|
||||||
t, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("couldn't decode token")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
expiration, err := t.Claims.GetExpirationTime()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error while checking token's expiration time")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug().Msg("Expiration date : " + expiration.UTC().Format("2006-01-02T15:04:05"))
|
|
||||||
logger.Debug().Msg(fmt.Sprint("Now : ", time.Now().Unix()))
|
|
||||||
logger.Debug().Msg(fmt.Sprint("Token : ", expiration.Unix()))
|
|
||||||
|
|
||||||
expired := expiration.Unix() < time.Now().Unix()
|
|
||||||
|
|
||||||
return &expired, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// @name Get Admiralty Kubeconfig
|
|
||||||
// @description Retrieve a kubeconfig from the host with the token to authenticate as the SA from the namespace identified with execution id
|
|
||||||
|
|
||||||
// @Param execution path string true "execution id of the workflow"
|
|
||||||
// @Success 200
|
|
||||||
// @router /kubeconfig/:execution [get]
|
|
||||||
func (c *AdmiraltyController) GetAdmiraltyKubeconfig() {
|
|
||||||
|
|
||||||
execution := c.Ctx.Input.Param(":execution")
|
|
||||||
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
// change code to 500
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
generatedToken, err := serv.GenerateToken(c.Ctx.Request.Context(), execution, 3600)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Couldn't generate a token for ns-", execution)
|
|
||||||
fmt.Println(err)
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeconfig, err := NewHostKubeWithToken(generatedToken)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Could not retrieve the Kubeconfig edited with token")
|
|
||||||
fmt.Println(err)
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.Marshal(kubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error while marshalling kubeconfig")
|
|
||||||
c.Ctx.Output.SetStatus(500)
|
|
||||||
c.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedKubeconfig := base64.StdEncoding.EncodeToString(b)
|
|
||||||
c.Data["json"] = map[string]string{
|
|
||||||
"data": encodedKubeconfig,
|
|
||||||
}
|
|
||||||
json.NewEncoder(c.Ctx.ResponseWriter)
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHostKubeWithToken(token string) (*models.KubeConfigValue, error) {
|
|
||||||
if len(token) == 0 {
|
|
||||||
return nil, fmt.Errorf("you didn't provide a token to be inserted in the Kubeconfig")
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedCA := base64.StdEncoding.EncodeToString([]byte(conf.GetConfig().KubeCA))
|
|
||||||
|
|
||||||
hostKube := models.KubeConfigValue{
|
|
||||||
APIVersion: "v1",
|
|
||||||
CurrentContext: "default",
|
|
||||||
Kind: "Config",
|
|
||||||
Preferences: struct{}{},
|
|
||||||
Clusters: []models.KubeconfigNamedCluster{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
Cluster: models.KubeconfigCluster{
|
|
||||||
Server: "https://" + conf.GetConfig().KubeHost + ":6443",
|
|
||||||
CertificateAuthorityData: encodedCA,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Contexts: []models.KubeconfigNamedContext{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
Context: models.KubeconfigContext{
|
|
||||||
Cluster: "default",
|
|
||||||
User: "default",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Users: []models.KubeconfigUser{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
User: models.KubeconfigUserKeyPair{
|
|
||||||
Token: token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &hostKube, nil
|
|
||||||
}
|
|
||||||
@@ -1,489 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"oc-datacenter/infrastructure"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/dbs"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/models/booking"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/models/utils"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/tools"
|
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Operations about workspace
|
|
||||||
type BookingController struct {
|
|
||||||
beego.Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
var BookingExample booking.Booking
|
|
||||||
|
|
||||||
// @Title Search
|
|
||||||
// @Description search bookings by execution
|
|
||||||
// @Param id path string true "id execution"
|
|
||||||
// @Param is_draft query string false "draft wished"
|
|
||||||
// @Success 200 {workspace} models.workspace
|
|
||||||
// @router /search/execution/:id [get]
|
|
||||||
func (o *BookingController) ExecutionSearch() {
|
|
||||||
/*
|
|
||||||
* This is a sample of how to use the search function
|
|
||||||
* The search function is used to search for data in the database
|
|
||||||
* The search function takes in a filter and a data type
|
|
||||||
* The filter is a struct that contains the search parameters
|
|
||||||
* The data type is an enum that specifies the type of data to search for
|
|
||||||
* The search function returns a list of data that matches the filter
|
|
||||||
* The data is then returned as a json object
|
|
||||||
*/
|
|
||||||
// store and return Id or post with UUID
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
id := o.Ctx.Input.Param(":id")
|
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
|
||||||
f := dbs.Filters{
|
|
||||||
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
|
|
||||||
"execution_id": {{Operator: dbs.EQUAL.String(), Value: id}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true")
|
|
||||||
o.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title Search
|
|
||||||
// @Description search bookings
|
|
||||||
// @Param start_date path string true "the word search you want to get"
|
|
||||||
// @Param end_date path string true "the word search you want to get"
|
|
||||||
// @Param is_draft query string false "draft wished"
|
|
||||||
// @Success 200 {workspace} models.workspace
|
|
||||||
// @router /search/:start_date/:end_date [get]
|
|
||||||
func (o *BookingController) Search() {
|
|
||||||
/*
|
|
||||||
* This is a sample of how to use the search function
|
|
||||||
* The search function is used to search for data in the database
|
|
||||||
* The search function takes in a filter and a data type
|
|
||||||
* The filter is a struct that contains the search parameters
|
|
||||||
* The data type is an enum that specifies the type of data to search for
|
|
||||||
* The search function returns a list of data that matches the filter
|
|
||||||
* The data is then returned as a json object
|
|
||||||
*/
|
|
||||||
// store and return Id or post with UUID
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
start_date, _ := time.Parse("2006-01-02", o.Ctx.Input.Param(":start_date"))
|
|
||||||
end_date, _ := time.Parse("2006-01-02", o.Ctx.Input.Param(":end_date"))
|
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
|
||||||
sd := primitive.NewDateTimeFromTime(start_date)
|
|
||||||
ed := primitive.NewDateTimeFromTime(end_date)
|
|
||||||
f := dbs.Filters{
|
|
||||||
And: map[string][]dbs.Filter{
|
|
||||||
"execution_date": {{Operator: "gte", Value: sd}, {Operator: "lte", Value: ed}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true")
|
|
||||||
o.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title GetAll
|
|
||||||
// @Description find booking by id
|
|
||||||
// @Param is_draft query string false "draft wished"
|
|
||||||
// @Success 200 {booking} models.booking
|
|
||||||
// @router / [get]
|
|
||||||
func (o *BookingController) GetAll() {
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadAll(isDraft == "true")
|
|
||||||
o.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title Get
|
|
||||||
// @Description find booking by id
|
|
||||||
// @Param id path string true "the id you want to get"
|
|
||||||
// @Success 200 {booking} models.booking
|
|
||||||
// @router /:id [get]
|
|
||||||
func (o *BookingController) Get() {
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
id := o.Ctx.Input.Param(":id")
|
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadOne(id)
|
|
||||||
o.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
|
||||||
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title Log
|
|
||||||
// @Description find booking by id
|
|
||||||
// @Param id path string true "the id you want to get"
|
|
||||||
// @Success 200 {booking} models.booking
|
|
||||||
// @router /:id [get]
|
|
||||||
func (o *BookingController) Log() {
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
id := o.Ctx.Input.Param(":id")
|
|
||||||
conn, err := upgrader.Upgrade(o.Ctx.ResponseWriter, o.Ctx.Request, nil)
|
|
||||||
if err != nil {
|
|
||||||
o.Ctx.WriteString("WebSocket upgrade failed: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
infrastructure.NewPrometheusService().Stream(id, 1*time.Second, user, peerID, groups, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title Update
|
|
||||||
// @Description create computes
|
|
||||||
// @Param id path string true "the compute id you want to get"
|
|
||||||
// @Param body body models.compute true "The compute content"
|
|
||||||
// @Success 200 {compute} models.compute
|
|
||||||
// @router /:id [put]
|
|
||||||
func (o *BookingController) Put() {
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
// store and return Id or post with UUID
|
|
||||||
var res map[string]interface{}
|
|
||||||
id := o.Ctx.Input.Param(":id")
|
|
||||||
book := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadOne(id)
|
|
||||||
if book.Code != 200 {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": nil,
|
|
||||||
"code": book.Code,
|
|
||||||
"error": book.Err,
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
booking := book.Data.(*booking.Booking)
|
|
||||||
if time.Now().After(booking.ExpectedStartDate) {
|
|
||||||
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).UpdateOne(res, id)
|
|
||||||
} else {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": nil,
|
|
||||||
"code": 409,
|
|
||||||
"error": "booking is not already started",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title Check
|
|
||||||
// @Description check booking
|
|
||||||
// @Param id path string "id of the datacenter"
|
|
||||||
// @Param start_date path string "the booking start date" format "2006-01-02T15:04:05"
|
|
||||||
// @Param end_date path string "the booking end date" format "2006-01-02T15:04:05"
|
|
||||||
// @Param is_draft query string false "draft wished"
|
|
||||||
// @Success 200 {object} models.object
|
|
||||||
// @router /check/:id/:start_date/:end_date [get]
|
|
||||||
func (o *BookingController) Check() {
|
|
||||||
/*
|
|
||||||
* This function is used to check if a booking is available for a specific datacenter.
|
|
||||||
* It takes the following parameters:
|
|
||||||
* - id: the id of the datacenter
|
|
||||||
* - start_date: the start date of the booking/search/execution/:id
|
|
||||||
* - end_date: the end date of the booking
|
|
||||||
*/
|
|
||||||
id := o.Ctx.Input.Param(":id")
|
|
||||||
date, err := time.Parse("2006-01-02T15:04:05", o.Ctx.Input.Param(":start_date"))
|
|
||||||
date2, err2 := time.Parse("2006-01-02T15:04:05", o.Ctx.Input.Param(":end_date"))
|
|
||||||
if err != nil || err2 != nil {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"is_available": false,
|
|
||||||
},
|
|
||||||
"code": 400,
|
|
||||||
"error": errors.New("invalid date format"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bks := &booking.Booking{} // create a new booking object
|
|
||||||
code := 200
|
|
||||||
err := ""
|
|
||||||
if isAvailable, err2 := bks.Check(id, date, &date2, 1); !isAvailable {
|
|
||||||
code = 409
|
|
||||||
err = "booking not available"
|
|
||||||
if err2 != nil {
|
|
||||||
err += " - " + err2.Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"is_available": true,
|
|
||||||
},
|
|
||||||
"code": code,
|
|
||||||
"error": err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title Post.
|
|
||||||
// @Description create booking
|
|
||||||
// @Param booking body string true "the booking you want to post"
|
|
||||||
// @Param is_draft query string false "draft wished"
|
|
||||||
// @Success 200 {object} models.object
|
|
||||||
// @router / [post]
|
|
||||||
func (o *BookingController) Post() {
|
|
||||||
/*
|
|
||||||
* This function is used to create a booking.
|
|
||||||
* It takes the following parameters:
|
|
||||||
* - booking: the booking you want to post
|
|
||||||
* The booking is a JSON object that contains the following fields:
|
|
||||||
* - datacenter_resource_id: the id of the datacenter
|
|
||||||
* - workflow_execution: the workflow execution
|
|
||||||
*/
|
|
||||||
logger := oclib.GetLogger()
|
|
||||||
logger.Info().Msg("Received a Booking")
|
|
||||||
var resp booking.Booking
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
err := json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &resp)
|
|
||||||
if err != nil {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": nil,
|
|
||||||
"code": 422,
|
|
||||||
"error": err,
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.ResourceType == tools.COMPUTE_RESOURCE {
|
|
||||||
// later should check... health for any such as docker...
|
|
||||||
res := oclib.NewRequest(oclib.LibDataEnum(oclib.COMPUTE_RESOURCE), user, peerID, groups, nil).LoadOne(resp.ResourceID)
|
|
||||||
if res.Err != "" {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": nil,
|
|
||||||
"code": res.Code,
|
|
||||||
"error": res.Err,
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
/*serv, err := infrastructure.NewServiceByType(res.ToComputeResource().Infrastructure.String())
|
|
||||||
if err != nil {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": nil,
|
|
||||||
"code": 500,
|
|
||||||
"error": err,
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := serv.CheckHealth(); err != nil {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": nil,
|
|
||||||
"code": 500,
|
|
||||||
"error": err,
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
return
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
// delete all previous bookings
|
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
|
||||||
res := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&dbs.Filters{And: map[string][]dbs.Filter{
|
|
||||||
"workflow_id": {{Operator: dbs.EQUAL.String(), Value: resp.WorkflowID}},
|
|
||||||
"resource_id": {{Operator: dbs.EQUAL.String(), Value: resp.ResourceID}},
|
|
||||||
}}, "", isDraft == "true")
|
|
||||||
if res.Code != 200 {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": nil,
|
|
||||||
"code": res.Code,
|
|
||||||
"error": res.Err,
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, b := range res.Data { // delete all previous bookings
|
|
||||||
oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).DeleteOne(b.GetID())
|
|
||||||
}
|
|
||||||
b := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).StoreOne(resp.Serialize(&resp))
|
|
||||||
if b.Code != 200 {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": nil,
|
|
||||||
"code": b.Code,
|
|
||||||
"error": b.Err,
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if b.Data.(*booking.Booking).ResourceType == tools.COMPUTE_RESOURCE {
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Until(b.Data.(*booking.Booking).ExpectedStartDate))
|
|
||||||
infrastructure.NewPrometheusService().Stream(b.Data.GetID(), 1*time.Second, user, peerID, groups, nil)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info().Msg("Creating new namespace : " + resp.ExecutionsID)
|
|
||||||
if err := o.createNamespace(resp.ExecutionsID); err != nil {
|
|
||||||
logger.Debug().Msg("Error when creating a namespace")
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": b.Data,
|
|
||||||
"code": 200,
|
|
||||||
"error": "",
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title ExtendForNamespace
|
|
||||||
// @Description ExtendForNamespace booking
|
|
||||||
// @Param namespace path string "targetted namespace"
|
|
||||||
// @Param resource_id path string "resource id"
|
|
||||||
// @Param end_date path string "the booking end date" format "2006-01-02T15:04:05"
|
|
||||||
// @Param is_draft query string false "draft wished"
|
|
||||||
// @Success 200 {object} models.object
|
|
||||||
// @router /extend/:resource_id/from_namespace/:namespace/to/:duration [post]
|
|
||||||
func (o *BookingController) ExtendForNamespace() {
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
resourceID := o.Ctx.Input.Param(":resource_id")
|
|
||||||
namespace := o.Ctx.Input.Param(":namespace")
|
|
||||||
duration, err := strconv.Atoi(o.Ctx.Input.Param(":duration"))
|
|
||||||
if err != nil {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"is_available": false,
|
|
||||||
},
|
|
||||||
"code": 400,
|
|
||||||
"error": errors.New("invalid date format"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
o.extend(duration, resourceID, "executions_id", namespace, user, peerID, groups)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Title ExtendForExecution
|
|
||||||
// @Description ExtendForExecution booking
|
|
||||||
// @Param namespace path string "targetted namespace"
|
|
||||||
// @Param resource_id path string "resource id"
|
|
||||||
// @Param end_date path string "the booking end date" format "2006-01-02T15:04:05"
|
|
||||||
// @Param is_draft query string false "draft wished"
|
|
||||||
// @Success 200 {object} models.object
|
|
||||||
// @router /extend/:resource_id/from_execution/:execution_id/to/:duration [post]
|
|
||||||
func (o *BookingController) ExtendForExecution() {
|
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
|
||||||
resourceID := o.Ctx.Input.Param(":resource_id")
|
|
||||||
executionID := o.Ctx.Input.Param(":execution_id")
|
|
||||||
duration, err := strconv.Atoi(o.Ctx.Input.Param(":duration"))
|
|
||||||
if err != nil {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"is_available": false,
|
|
||||||
},
|
|
||||||
"code": 400,
|
|
||||||
"error": errors.New("invalid date format"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
o.extend(duration, resourceID, "execution_id", executionID, user, peerID, groups)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *BookingController) extend(duration int, resourceID string, key string, namespace string, user string, peerID string, groups []string) {
|
|
||||||
/*
|
|
||||||
* This function is used to check if a booking is available for a specific datacenter.
|
|
||||||
* It takes the following parameters:
|
|
||||||
* - id: the id of the datacenter
|
|
||||||
* - start_date: the start date of the booking/search/execution/:id
|
|
||||||
* - end_date: the end date of the booking
|
|
||||||
*/
|
|
||||||
|
|
||||||
req := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil)
|
|
||||||
res := req.Search(&dbs.Filters{
|
|
||||||
Or: map[string][]dbs.Filter{
|
|
||||||
"expected_end_date": {
|
|
||||||
{Operator: dbs.GTE.String(), Value: time.Now()},
|
|
||||||
{Operator: dbs.EQUAL.String(), Value: bson.TypeNull},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
And: map[string][]dbs.Filter{
|
|
||||||
"resource_id": {{Operator: dbs.EQUAL.String(), Value: resourceID}},
|
|
||||||
key: {{Operator: dbs.EQUAL.String(), Value: namespace}},
|
|
||||||
"expected_start_date": {{Operator: dbs.GTE.String(), Value: time.Now()}},
|
|
||||||
},
|
|
||||||
}, "", false)
|
|
||||||
if res.Err == "" && len(res.Data) > 0 {
|
|
||||||
var id string
|
|
||||||
datas := []utils.DBObject{}
|
|
||||||
for _, b := range res.Data {
|
|
||||||
book := b.(*booking.Booking)
|
|
||||||
if book.ExpectedEndDate != nil {
|
|
||||||
bb := book.ExpectedEndDate.Add(time.Duration(duration) * time.Second)
|
|
||||||
if isAvailable, err := (&booking.Booking{}).Check(id, (*book.ExpectedEndDate).Add(1*time.Second), &bb, 1); isAvailable && err == nil {
|
|
||||||
book.ExpectedEndDate = &bb
|
|
||||||
result := req.UpdateOne(book.Serialize(book), book.GetID())
|
|
||||||
datas = append(datas, result.Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": datas,
|
|
||||||
"code": 200,
|
|
||||||
"error": "",
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
return
|
|
||||||
} else if res.Err == "" {
|
|
||||||
o.Data["json"] = map[string]interface{}{
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"is_available": false,
|
|
||||||
},
|
|
||||||
"code": 400,
|
|
||||||
"error": errors.New("can't find any booking to extend"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
o.Data["json"] = res
|
|
||||||
}
|
|
||||||
o.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *BookingController) createNamespace(ns string) error {
|
|
||||||
/*
|
|
||||||
* This function is used to create a namespace.
|
|
||||||
* It takes the following parameters:
|
|
||||||
* - ns: the namespace you want to create
|
|
||||||
*/
|
|
||||||
logger := oclib.GetLogger()
|
|
||||||
|
|
||||||
serv, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, err := serv.GetNamespace(o.Ctx.Request.Context(), ns)
|
|
||||||
if ok != nil && err == nil {
|
|
||||||
logger.Debug().Msg("A namespace with name " + ns + " already exists")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = serv.CreateNamespace(o.Ctx.Request.Context(), ns)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = serv.CreateServiceAccount(o.Ctx.Request.Context(), ns)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
role := "argo-role"
|
|
||||||
err = serv.CreateRole(o.Ctx.Request.Context(), ns, role,
|
|
||||||
[][]string{
|
|
||||||
{"coordination.k8s.io"},
|
|
||||||
{""},
|
|
||||||
{""}},
|
|
||||||
[][]string{
|
|
||||||
{"leases"},
|
|
||||||
{"secrets"},
|
|
||||||
{"pods"}},
|
|
||||||
[][]string{
|
|
||||||
{"get", "create", "update"},
|
|
||||||
{"get"},
|
|
||||||
{"patch"}})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return serv.CreateRoleBinding(o.Ctx.Request.Context(), ns, "argo-role-binding", role)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"oc-datacenter/infrastructure/monitor"
|
||||||
|
"time"
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
"cloud.o-forge.io/core/oc-lib/dbs"
|
"cloud.o-forge.io/core/oc-lib/dbs"
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Operations about workspace
|
// Operations about workspace
|
||||||
@@ -19,12 +24,12 @@ type DatacenterController struct {
|
|||||||
func (o *DatacenterController) GetAll() {
|
func (o *DatacenterController) GetAll() {
|
||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
storages := oclib.NewRequest(oclib.LibDataEnum(oclib.STORAGE_RESOURCE), user, peerID, groups, nil).Search(&dbs.Filters{
|
storages := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), user, peerID, groups, nil).Search(&dbs.Filters{
|
||||||
Or: map[string][]dbs.Filter{
|
Or: map[string][]dbs.Filter{
|
||||||
"abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
"abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
||||||
},
|
},
|
||||||
}, "", isDraft == "true")
|
}, "", isDraft == "true")
|
||||||
computes := oclib.NewRequest(oclib.LibDataEnum(oclib.COMPUTE_RESOURCE), user, peerID, groups, nil).Search(&dbs.Filters{
|
computes := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_DATACENTER), user, peerID, groups, nil).Search(&dbs.Filters{
|
||||||
Or: map[string][]dbs.Filter{
|
Or: map[string][]dbs.Filter{
|
||||||
"abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
"abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
||||||
},
|
},
|
||||||
@@ -47,14 +52,14 @@ func (o *DatacenterController) Get() {
|
|||||||
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
isDraft := o.Ctx.Input.Query("is_draft")
|
isDraft := o.Ctx.Input.Query("is_draft")
|
||||||
id := o.Ctx.Input.Param(":id")
|
id := o.Ctx.Input.Param(":id")
|
||||||
storages := oclib.NewRequest(oclib.LibDataEnum(oclib.STORAGE_RESOURCE), user, peerID, groups, nil).Search(&dbs.Filters{
|
storages := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), user, peerID, groups, nil).Search(&dbs.Filters{
|
||||||
Or: map[string][]dbs.Filter{
|
Or: map[string][]dbs.Filter{
|
||||||
"abstractinstanciatedresource.abstractresource.abstractobject.id": {{Operator: dbs.EQUAL.String(), Value: id}},
|
"abstractinstanciatedresource.abstractresource.abstractobject.id": {{Operator: dbs.EQUAL.String(), Value: id}},
|
||||||
"abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
"abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
||||||
},
|
},
|
||||||
}, "", isDraft == "true")
|
}, "", isDraft == "true")
|
||||||
if len(storages.Data) == 0 {
|
if len(storages.Data) == 0 {
|
||||||
computes := oclib.NewRequest(oclib.LibDataEnum(oclib.COMPUTE_RESOURCE), user, peerID, groups, nil).Search(&dbs.Filters{
|
computes := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_DATACENTER), user, peerID, groups, nil).Search(&dbs.Filters{
|
||||||
Or: map[string][]dbs.Filter{
|
Or: map[string][]dbs.Filter{
|
||||||
"abstractinstanciatedresource.abstractresource.abstractobject.id": {{Operator: dbs.EQUAL.String(), Value: id}},
|
"abstractinstanciatedresource.abstractresource.abstractobject.id": {{Operator: dbs.EQUAL.String(), Value: id}},
|
||||||
"abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
"abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}},
|
||||||
@@ -83,3 +88,30 @@ func (o *DatacenterController) Get() {
|
|||||||
}
|
}
|
||||||
o.ServeJSON()
|
o.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title Log
|
||||||
|
// @Description find booking by id
|
||||||
|
// @Param id path string true "the id you want to get"
|
||||||
|
// @Success 200 {booking} models.booking
|
||||||
|
// @router /:id [get]
|
||||||
|
func (o *DatacenterController) Log() {
|
||||||
|
// user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
|
||||||
|
id := o.Ctx.Input.Param(":id")
|
||||||
|
conn, err := upgrader.Upgrade(o.Ctx.ResponseWriter, o.Ctx.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
o.Ctx.WriteString("WebSocket upgrade failed: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
monitors, err := monitor.NewMonitorService()
|
||||||
|
if err != nil {
|
||||||
|
o.Ctx.WriteString("Monitor service unavailable: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := monitor.StreamRegistry.Register(id)
|
||||||
|
monitors.Stream(ctx, id, 1*time.Second, conn)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"oc-datacenter/infrastructure"
|
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/models/live"
|
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MinioController struct {
|
|
||||||
beego.Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// @Title CreateServiceAccounnt
|
|
||||||
// @Description Add a new ServiceAccount to a Minio server using its ID and an execution ID
|
|
||||||
// @Success 200
|
|
||||||
// @Param executions path string true "The executionsID of the execution"
|
|
||||||
// @Param minioId path string true "The ID of the Minio you want to reach"
|
|
||||||
// @router /serviceaccount/:minioId/:executions [post]
|
|
||||||
func (m *MinioController) CreateServiceAccount() {
|
|
||||||
_, peerID, _ := oclib.ExtractTokenInfo(*m.Ctx.Request)
|
|
||||||
// This part is solely for dev purposes and should be removed once test on
|
|
||||||
|
|
||||||
|
|
||||||
executionsId := m.Ctx.Input.Param(":executions")
|
|
||||||
minioId := m.Ctx.Input.Param(":minioId")
|
|
||||||
|
|
||||||
// retrieve the live storage with the minioId
|
|
||||||
s := oclib.NewRequest(oclib.LibDataEnum(oclib.STORAGE_RESOURCE), "", "", []string{}, nil).LoadOne(minioId)
|
|
||||||
if s.Err != "" {
|
|
||||||
m.Ctx.Output.SetStatus(400)
|
|
||||||
m.Data["json"] = map[string]interface{}{"error": " Could not load the storage resource with id " + minioId + ": " + s.Err}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
live := findLiveStorage(minioId, peerID)
|
|
||||||
if live == nil {
|
|
||||||
m.Ctx.Output.SetStatus(404)
|
|
||||||
m.Data["json"] = map[string]interface{}{"error":"could not find the Minio instance " + s.Err}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
url := live.Source
|
|
||||||
service := infrastructure.NewMinioService(url)
|
|
||||||
|
|
||||||
// call the method ctrating the svcacc
|
|
||||||
err := service.CreateClient()
|
|
||||||
if err != nil {
|
|
||||||
m.Ctx.Output.SetStatus(500)
|
|
||||||
m.Data["json"] = map[string]interface{}{"error":"could not create the client for " + minioId + " : " + err.Error()}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
access, secret, err := service.CreateCredentials(executionsId)
|
|
||||||
if err != nil {
|
|
||||||
m.Ctx.Output.SetStatus(500)
|
|
||||||
m.Data["json"] = map[string]interface{}{"error":"could not create the service account for " + minioId + " : " + err.Error()}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = service.CreateBucket(executionsId)
|
|
||||||
if err != nil {
|
|
||||||
m.Ctx.Output.SetStatus(500)
|
|
||||||
m.Data["json"] = map[string]interface{}{"error":"could not create the service account for " + minioId + " : " + err.Error()}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// test if the namespace exists
|
|
||||||
k, err := infrastructure.NewService()
|
|
||||||
if err != nil {
|
|
||||||
m.Ctx.Output.SetStatus(500)
|
|
||||||
m.Data["json"] = map[string]string{"error": err.Error()}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ns, err := k.GetNamespace(m.Ctx.Request.Context(), executionsId)
|
|
||||||
if ns == nil {
|
|
||||||
m.Ctx.Output.SetStatus(403)
|
|
||||||
m.Data["json"] = map[string]string{"error":"Could not find the namespace corresponding to executionsID " + executionsId}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
m.Ctx.Output.SetStatus(500)
|
|
||||||
m.Data["json"] = map[string]string{"error": "Error when trying to check if namespace " + executionsId + " exists : " + err.Error()}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// store the credentials in the namespace
|
|
||||||
err = k.CreateSecret(m.Ctx.Request.Context(), minioId, executionsId, access, secret)
|
|
||||||
if err != nil {
|
|
||||||
m.Ctx.Output.SetStatus(500)
|
|
||||||
m.Data["json"] = map[string]string{"error": "Error when storing Minio serviceAccount credentials in namespace " + executionsId + " exists : " + err.Error()}
|
|
||||||
m.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Data["json"] = map[string]string{"success": "created secret " + executionsId + "-secret-sa in namespace ns-" + executionsId}
|
|
||||||
m.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func findLiveStorage(storageId string, peerId string) *live.LiveStorage {
|
|
||||||
res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE),"",peerId,[]string{},nil).LoadAll(false)
|
|
||||||
if res.Err != "" {
|
|
||||||
l := oclib.GetLogger()
|
|
||||||
l.Error().Msg(res.Err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dbo := range res.Data {
|
|
||||||
r := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE),"","",[]string{},nil).LoadOne(dbo.GetID())
|
|
||||||
l := r.ToLiveStorage()
|
|
||||||
for _, id := range l.ResourcesID {
|
|
||||||
if id == storageId {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,10 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"oc-datacenter/infrastructure"
|
"oc-datacenter/conf"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"cloud.o-forge.io/core/oc-lib/tools"
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,7 +32,8 @@ func (o *SessionController) GetToken() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serv, err := infrastructure.NewService()
|
serv, err := tools.NewKubernetesService(conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
|
||||||
|
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// change code to 500
|
// change code to 500
|
||||||
o.Ctx.Output.SetStatus(500)
|
o.Ctx.Output.SetStatus(500)
|
||||||
|
|||||||
@@ -17,8 +17,11 @@ services:
|
|||||||
- "traefik.http.services.datacenter.loadbalancer.server.port=8080"
|
- "traefik.http.services.datacenter.loadbalancer.server.port=8080"
|
||||||
- "traefik.http.middlewares.datacenter-rewrite.replacepathregex.regex=^/datacenter(.*)"
|
- "traefik.http.middlewares.datacenter-rewrite.replacepathregex.regex=^/datacenter(.*)"
|
||||||
- "traefik.http.middlewares.datacenter-rewrite.replacepathregex.replacement=/oc$$1"
|
- "traefik.http.middlewares.datacenter-rewrite.replacepathregex.replacement=/oc$$1"
|
||||||
- "traefik.http.routers.datacenter.middlewares=datacenter-rewrite"
|
- "traefik.http.routers.datacenter.middlewares=datacenter-rewrite,auth-datacenter"
|
||||||
- "traefik.http.middlewares.datacenter.forwardauth.address=http://oc-auth:8080/oc/forward"
|
|
||||||
|
- "traefik.http.middlewares.auth-datacenter.forwardauth.address=http://oc-auth:8080/oc/forward"
|
||||||
|
- "traefik.http.middlewares.auth-datacenter.forwardauth.trustForwardHeader=true"
|
||||||
|
- "traefik.http.middlewares.auth-datacenter.forwardauth.authResponseHeaders=X-Auth-Request-User,X-Auth-Request-Email"
|
||||||
container_name: oc-datacenter
|
container_name: oc-datacenter
|
||||||
networks:
|
networks:
|
||||||
- oc
|
- oc
|
||||||
|
|||||||
62
go.mod
62
go.mod
@@ -1,22 +1,18 @@
|
|||||||
module oc-datacenter
|
module oc-datacenter
|
||||||
|
|
||||||
go 1.24.2
|
go 1.25.0
|
||||||
|
|
||||||
toolchain go1.24.4
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.o-forge.io/core/oc-lib v0.0.0-20250715125819-e735f78e58c6
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260319071818-28b5b7d39ffe
|
||||||
github.com/beego/beego/v2 v2.3.8
|
github.com/beego/beego/v2 v2.3.8
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||||
github.com/gorilla/websocket v1.5.3
|
|
||||||
github.com/minio/madmin-go/v4 v4.1.1
|
github.com/minio/madmin-go/v4 v4.1.1
|
||||||
github.com/minio/minio-go/v7 v7.0.94
|
github.com/minio/minio-go/v7 v7.0.94
|
||||||
github.com/necmettindev/randomstring v0.1.0
|
github.com/necmettindev/randomstring v0.1.0
|
||||||
go.mongodb.org/mongo-driver v1.17.4
|
go.mongodb.org/mongo-driver v1.17.4
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
k8s.io/api v0.35.1
|
||||||
k8s.io/api v0.32.3
|
k8s.io/apimachinery v0.35.1
|
||||||
k8s.io/apimachinery v0.32.3
|
k8s.io/client-go v0.35.1
|
||||||
k8s.io/client-go v0.32.3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -24,13 +20,14 @@ require (
|
|||||||
github.com/biter777/countries v1.7.5 // indirect
|
github.com/biter777/countries v1.7.5 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/go-ini/ini v1.67.0 // indirect
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
@@ -39,13 +36,10 @@ require (
|
|||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/gnostic-models v0.6.8 // indirect
|
github.com/google/gnostic-models v0.7.0 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/goraz/onion v0.1.3 // indirect
|
github.com/goraz/onion v0.1.3 // indirect
|
||||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||||
@@ -54,6 +48,7 @@ require (
|
|||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // 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 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
@@ -63,14 +58,13 @@ require (
|
|||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.43.0 // indirect
|
github.com/nats-io/nats.go v1.43.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
@@ -78,7 +72,6 @@ require (
|
|||||||
github.com/prometheus/procfs v0.17.0 // indirect
|
github.com/prometheus/procfs v0.17.0 // indirect
|
||||||
github.com/prometheus/prom2json v1.4.2 // indirect
|
github.com/prometheus/prom2json v1.4.2 // indirect
|
||||||
github.com/prometheus/prometheus v0.304.1 // indirect
|
github.com/prometheus/prometheus v0.304.1 // indirect
|
||||||
github.com/robfig/cron v1.2.0 // indirect
|
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/rs/zerolog v1.34.0 // indirect
|
github.com/rs/zerolog v1.34.0 // indirect
|
||||||
github.com/safchain/ethtool v0.6.1 // indirect
|
github.com/safchain/ethtool v0.6.1 // indirect
|
||||||
@@ -95,22 +88,25 @@ require (
|
|||||||
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-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
golang.org/x/crypto v0.40.0 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/net v0.42.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/crypto v0.44.0 // indirect
|
||||||
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/oauth2 v0.30.0 // indirect
|
golang.org/x/oauth2 v0.30.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
golang.org/x/term v0.33.0 // indirect
|
golang.org/x/term v0.37.0 // indirect
|
||||||
golang.org/x/text v0.27.0 // indirect
|
golang.org/x/text v0.31.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.8 // indirect
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
|
||||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
195
go.sum
195
go.sum
@@ -1,6 +1,8 @@
|
|||||||
cloud.o-forge.io/core/oc-lib v0.0.0-20250715125819-e735f78e58c6 h1:Gnkv59Ntl2RebC5tNauXuxyRXLfZ2XAJ0+ujMyFte5U=
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260319071818-28b5b7d39ffe h1:CHiWQAX7j/bMfbytCWGL2mUgSWYoDY4+bFQbCHEfypk=
|
||||||
cloud.o-forge.io/core/oc-lib v0.0.0-20250715125819-e735f78e58c6/go.mod h1:vHWauJsS6ryf7UDqq8hRXoYD5RsONxcFTxeZPOztEuI=
|
cloud.o-forge.io/core/oc-lib v0.0.0-20260319071818-28b5b7d39ffe/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
|
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 h1:wplhB1pF4TxR+2SS4PUej8eDoH4xGfxuHfS7wAk9VBc=
|
||||||
github.com/beego/beego/v2 v2.3.8/go.mod h1:8vl9+RrXqvodrl9C8yivX1e6le6deCK6RWeq8R7gTTg=
|
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=
|
||||||
@@ -16,24 +18,28 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
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.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
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.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
@@ -56,38 +62,33 @@ github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZ
|
|||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
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/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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
|
||||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
|
||||||
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=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/goraz/onion v0.1.3 h1:KhyvbDA2b70gcz/d5izfwTiOH8SmrvV43AsVzpng3n0=
|
github.com/goraz/onion v0.1.3 h1:KhyvbDA2b70gcz/d5izfwTiOH8SmrvV43AsVzpng3n0=
|
||||||
github.com/goraz/onion v0.1.3/go.mod h1:XEmz1XoBz+wxTgWB8NwuvRm4RAu3vKxvrmYtzK+XCuQ=
|
github.com/goraz/onion v0.1.3/go.mod h1:XEmz1XoBz+wxTgWB8NwuvRm4RAu3vKxvrmYtzK+XCuQ=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||||
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
||||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v1.0.2/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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
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=
|
||||||
@@ -95,8 +96,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
@@ -110,6 +109,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
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/go.mod h1:NYeJ9lvyBv9nbDk2IuGb8gFKEOkIv/W5YRIy1pAJB2Q=
|
||||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
|
||||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
@@ -132,6 +135,8 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
|||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
|
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
|
||||||
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
|
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
|
||||||
|
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=
|
||||||
@@ -140,10 +145,27 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||||||
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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
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 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug=
|
github.com/nats-io/nats.go v1.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug=
|
||||||
@@ -155,14 +177,13 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
|
|||||||
github.com/necmettindev/randomstring v0.1.0 h1:HeU/mfLCd/5E9At7xznbTeEw5YldGW92fvK8lWtvPwE=
|
github.com/necmettindev/randomstring v0.1.0 h1:HeU/mfLCd/5E9At7xznbTeEw5YldGW92fvK8lWtvPwE=
|
||||||
github.com/necmettindev/randomstring v0.1.0/go.mod h1:h2nX9Jl0TLImuMt++XfLStVr8N76BmmP5D5EhLq0KEQ=
|
github.com/necmettindev/randomstring v0.1.0/go.mod h1:h2nX9Jl0TLImuMt++XfLStVr8N76BmmP5D5EhLq0KEQ=
|
||||||
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.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
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/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
@@ -181,10 +202,8 @@ github.com/prometheus/prom2json v1.4.2 h1:PxCTM+Whqi/eykO1MKsEL0p/zMpxp9ybpsmdFa
|
|||||||
github.com/prometheus/prom2json v1.4.2/go.mod h1:zuvPm7u3epZSbXPWHny6G+o8ETgu6eAK3oPr6yFkRWE=
|
github.com/prometheus/prom2json v1.4.2/go.mod h1:zuvPm7u3epZSbXPWHny6G+o8ETgu6eAK3oPr6yFkRWE=
|
||||||
github.com/prometheus/prometheus v0.304.1 h1:e4kpJMb2Vh/PcR6LInake+ofcvFYHT+bCfmBvOkaZbY=
|
github.com/prometheus/prometheus v0.304.1 h1:e4kpJMb2Vh/PcR6LInake+ofcvFYHT+bCfmBvOkaZbY=
|
||||||
github.com/prometheus/prometheus v0.304.1/go.mod h1:ioGx2SGKTY+fLnJSQCdTHqARVldGNS8OlIe3kvp98so=
|
github.com/prometheus/prometheus v0.304.1/go.mod h1:ioGx2SGKTY+fLnJSQCdTHqARVldGNS8OlIe3kvp98so=
|
||||||
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
@@ -204,12 +223,16 @@ github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl
|
|||||||
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/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||||
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
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.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||||
@@ -226,48 +249,45 @@ github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6
|
|||||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
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=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
||||||
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
|
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
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-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/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.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
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 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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=
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -277,62 +297,59 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
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.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
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=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
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.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
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 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
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.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
|
k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q=
|
||||||
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
|
k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM=
|
||||||
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
|
k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU=
|
||||||
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||||
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
|
k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM=
|
||||||
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
|
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 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
|
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
|
||||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
|
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
||||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
||||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
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=
|
||||||
|
|||||||
350
infrastructure/admiralty.go
Normal file
350
infrastructure/admiralty.go
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
package infrastructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"oc-datacenter/conf"
|
||||||
|
"oc-datacenter/infrastructure/monitor"
|
||||||
|
"oc-datacenter/models"
|
||||||
|
|
||||||
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/tools"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// kubeconfigChannels holds channels waiting for kubeconfig delivery (keyed by executionID).
|
||||||
|
var kubeconfigChannels sync.Map
|
||||||
|
|
||||||
|
// kubeconfigEvent is the NATS payload used to transfer the kubeconfig from the source peer to the target peer.
|
||||||
|
type KubeconfigEvent struct {
|
||||||
|
DestPeerID string `json:"dest_peer_id"`
|
||||||
|
ExecutionsID string `json:"executions_id"`
|
||||||
|
Kubeconfig string `json:"kubeconfig"`
|
||||||
|
SourcePeerID string `json:"source_peer_id"`
|
||||||
|
// OriginID is the peer that initiated the provisioning request.
|
||||||
|
// The PB_CONSIDERS response is routed back to this peer.
|
||||||
|
OriginID string `json:"origin_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// admiraltyConsidersPayload is the PB_CONSIDERS payload emitted after admiralty provisioning.
|
||||||
|
type admiraltyConsidersPayload struct {
|
||||||
|
OriginID string `json:"origin_id"`
|
||||||
|
ExecutionsID string `json:"executions_id"`
|
||||||
|
Secret string `json:"secret,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitAdmiraltyConsiders publishes a PB_CONSIDERS back to OriginID with the result
|
||||||
|
// of the admiralty provisioning. secret is the base64-encoded kubeconfig; err is nil on success.
|
||||||
|
func emitAdmiraltyConsiders(executionsID, originID, secret string, provErr error) {
|
||||||
|
var errStr *string
|
||||||
|
if provErr != nil {
|
||||||
|
s := provErr.Error()
|
||||||
|
errStr = &s
|
||||||
|
}
|
||||||
|
payload, _ := json.Marshal(admiraltyConsidersPayload{
|
||||||
|
OriginID: originID,
|
||||||
|
ExecutionsID: executionsID,
|
||||||
|
Secret: secret,
|
||||||
|
Error: errStr,
|
||||||
|
})
|
||||||
|
b, _ := json.Marshal(&tools.PropalgationMessage{
|
||||||
|
DataType: tools.COMPUTE_RESOURCE.EnumIndex(),
|
||||||
|
Action: tools.PB_CONSIDERS,
|
||||||
|
Payload: payload,
|
||||||
|
})
|
||||||
|
go tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||||
|
FromApp: "oc-datacenter",
|
||||||
|
Datatype: -1,
|
||||||
|
Method: int(tools.PROPALGATION_EVENT),
|
||||||
|
Payload: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdmiraltySetter carries the execution context for an admiralty pairing.
|
||||||
|
type AdmiraltySetter struct {
|
||||||
|
ExecutionsID string // execution ID, used as the Kubernetes namespace
|
||||||
|
NodeName string // name of the virtual node created by Admiralty on the target cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdmiraltySetter(execIDS string) *AdmiraltySetter {
|
||||||
|
return &AdmiraltySetter{
|
||||||
|
ExecutionsID: execIDS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeAsSource is called on the peer that acts as the SOURCE cluster (compute provider).
|
||||||
|
// It creates the AdmiraltySource resource, generates a kubeconfig for the target peer,
|
||||||
|
// and publishes it on NATS so the target peer can complete its side of the setup.
|
||||||
|
func (s *AdmiraltySetter) InitializeAsSource(ctx context.Context, localPeerID string, destPeerID string, originID string) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
|
||||||
|
serv, err := tools.NewKubernetesService(conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
|
||||||
|
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("InitializeAsSource: failed to create service: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the AdmiraltySource resource on this cluster (inlined from CreateAdmiraltySource controller)
|
||||||
|
logger.Info().Msg("Creating AdmiraltySource ns-" + s.ExecutionsID)
|
||||||
|
_, err = serv.CreateAdmiraltySource(ctx, s.ExecutionsID)
|
||||||
|
if err != nil && !apierrors.IsAlreadyExists(err) {
|
||||||
|
logger.Error().Msg("InitializeAsSource: failed to create source: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a service-account token for the namespace (inlined from GetAdmiraltyKubeconfig controller)
|
||||||
|
token, err := serv.GenerateToken(ctx, s.ExecutionsID, 3600)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("InitializeAsSource: failed to generate token for ns-" + s.ExecutionsID + ": " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeconfig, err := buildHostKubeWithToken(token)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("InitializeAsSource: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(kubeconfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("InitializeAsSource: failed to marshal kubeconfig: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
encodedKubeconfig := base64.StdEncoding.EncodeToString(b)
|
||||||
|
kube := KubeconfigEvent{
|
||||||
|
ExecutionsID: s.ExecutionsID,
|
||||||
|
Kubeconfig: encodedKubeconfig,
|
||||||
|
SourcePeerID: localPeerID,
|
||||||
|
DestPeerID: destPeerID,
|
||||||
|
OriginID: originID,
|
||||||
|
}
|
||||||
|
if destPeerID == localPeerID {
|
||||||
|
s.InitializeAsTarget(ctx, kube)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Publish the kubeconfig on NATS so the target peer can proceed
|
||||||
|
payload, err := json.Marshal(kube)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("InitializeAsSource: failed to marshal kubeconfig event: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, err := json.Marshal(&tools.PropalgationMessage{
|
||||||
|
DataType: -1,
|
||||||
|
Action: tools.PB_ADMIRALTY_CONFIG,
|
||||||
|
Payload: payload,
|
||||||
|
}); err == nil {
|
||||||
|
go tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||||
|
FromApp: "oc-datacenter",
|
||||||
|
Datatype: -1,
|
||||||
|
User: "",
|
||||||
|
Method: int(tools.PROPALGATION_EVENT),
|
||||||
|
Payload: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
logger.Info().Msg("InitializeAsSource: kubeconfig published for ns-" + s.ExecutionsID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeAsTarget is called on the peer that acts as the TARGET cluster (scheduler).
|
||||||
|
// It waits for the kubeconfig published by the source peer via NATS, then creates
|
||||||
|
// the Secret, AdmiraltyTarget, and polls until the virtual node appears.
|
||||||
|
// kubeconfigCh must be obtained from RegisterKubeconfigWaiter before this goroutine starts.
|
||||||
|
func (s *AdmiraltySetter) InitializeAsTarget(ctx context.Context, kubeconfigObj KubeconfigEvent) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
defer kubeconfigChannels.Delete(s.ExecutionsID)
|
||||||
|
|
||||||
|
logger.Info().Msg("InitializeAsTarget: waiting for kubeconfig from source peer ns-" + s.ExecutionsID)
|
||||||
|
kubeconfigData := kubeconfigObj.Kubeconfig
|
||||||
|
|
||||||
|
serv, err := tools.NewKubernetesService(conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
|
||||||
|
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("InitializeAsTarget: failed to create service: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Create the namespace
|
||||||
|
logger.Info().Msg("InitializeAsTarget: creating Namespace " + s.ExecutionsID)
|
||||||
|
if err := serv.CreateNamespace(ctx, s.ExecutionsID); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||||
|
logger.Error().Msg("InitializeAsTarget: failed to create namespace: " + err.Error())
|
||||||
|
emitAdmiraltyConsiders(s.ExecutionsID, kubeconfigObj.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create the ServiceAccount sa-{executionID}
|
||||||
|
logger.Info().Msg("InitializeAsTarget: creating ServiceAccount sa-" + s.ExecutionsID)
|
||||||
|
if err := serv.CreateServiceAccount(ctx, s.ExecutionsID); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||||
|
logger.Error().Msg("InitializeAsTarget: failed to create service account: " + err.Error())
|
||||||
|
emitAdmiraltyConsiders(s.ExecutionsID, kubeconfigObj.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Create the Role
|
||||||
|
roleName := "role-" + s.ExecutionsID
|
||||||
|
logger.Info().Msg("InitializeAsTarget: creating Role " + roleName)
|
||||||
|
if err := serv.CreateRole(ctx, s.ExecutionsID, roleName,
|
||||||
|
[][]string{
|
||||||
|
{"coordination.k8s.io"},
|
||||||
|
{""},
|
||||||
|
{""}},
|
||||||
|
[][]string{
|
||||||
|
{"leases"},
|
||||||
|
{"secrets"},
|
||||||
|
{"pods"}},
|
||||||
|
[][]string{
|
||||||
|
{"get", "create", "update"},
|
||||||
|
{"get"},
|
||||||
|
{"patch"}},
|
||||||
|
); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||||
|
logger.Error().Msg("InitializeAsTarget: failed to create role: " + err.Error())
|
||||||
|
emitAdmiraltyConsiders(s.ExecutionsID, kubeconfigObj.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Create the RoleBinding
|
||||||
|
rbName := "rb-" + s.ExecutionsID
|
||||||
|
logger.Info().Msg("InitializeAsTarget: creating RoleBinding " + rbName)
|
||||||
|
if err := serv.CreateRoleBinding(ctx, s.ExecutionsID, rbName, roleName); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||||
|
logger.Error().Msg("InitializeAsTarget: failed to create role binding: " + err.Error())
|
||||||
|
emitAdmiraltyConsiders(s.ExecutionsID, kubeconfigObj.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Secret from the source peer's kubeconfig (inlined from CreateKubeSecret controller)
|
||||||
|
logger.Info().Msg("InitializeAsTarget: creating Secret ns-" + s.ExecutionsID)
|
||||||
|
if _, err := serv.CreateKubeconfigSecret(ctx, kubeconfigData, s.ExecutionsID, kubeconfigObj.SourcePeerID); err != nil {
|
||||||
|
logger.Error().Msg("InitializeAsTarget: failed to create kubeconfig secret: " + err.Error())
|
||||||
|
emitAdmiraltyConsiders(s.ExecutionsID, kubeconfigObj.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the AdmiraltyTarget resource (inlined from CreateAdmiraltyTarget controller)
|
||||||
|
logger.Info().Msg("InitializeAsTarget: creating AdmiraltyTarget ns-" + s.ExecutionsID)
|
||||||
|
resp, err := serv.CreateAdmiraltyTarget(ctx, s.ExecutionsID, kubeconfigObj.SourcePeerID)
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
logger.Error().Msg(fmt.Sprintf("InitializeAsTarget: failed to create admiralty target: %v", err))
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("CreateAdmiraltyTarget returned nil response")
|
||||||
|
}
|
||||||
|
emitAdmiraltyConsiders(s.ExecutionsID, kubeconfigObj.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll until the virtual node appears (inlined from GetNodeReady controller)
|
||||||
|
logger.Info().Msg("InitializeAsTarget: waiting for virtual node ns-" + s.ExecutionsID)
|
||||||
|
s.waitForNode(ctx, serv, kubeconfigObj.SourcePeerID)
|
||||||
|
emitAdmiraltyConsiders(s.ExecutionsID, kubeconfigObj.OriginID, kubeconfigData, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForNode polls GetOneNode until the Admiralty virtual node appears on this cluster.
|
||||||
|
func (s *AdmiraltySetter) waitForNode(ctx context.Context, serv *tools.KubernetesService, sourcePeerID string) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
for i := range 5 {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
node, err := serv.GetOneNode(ctx, s.ExecutionsID, sourcePeerID)
|
||||||
|
if err == nil && node != nil {
|
||||||
|
s.NodeName = node.Name
|
||||||
|
logger.Info().Msg("waitForNode: node ready: " + s.NodeName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i == 4 {
|
||||||
|
logger.Error().Msg("waitForNode: node never appeared for ns-" + s.ExecutionsID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Info().Msg("waitForNode: node not ready yet, retrying...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeardownAsTarget destroys all Admiralty resources created by InitializeAsTarget on the
|
||||||
|
// target (scheduler) cluster: the AdmiraltyTarget CRD, the ServiceAccount, the Role,
|
||||||
|
// the RoleBinding, and the namespace (namespace deletion cascades the rest).
|
||||||
|
func (s *AdmiraltySetter) TeardownAsTarget(ctx context.Context, originID string) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
serv, err := tools.NewKubernetesService(conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
|
||||||
|
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("TeardownAsTarget: failed to create k8s service: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := serv.DeleteNamespace(ctx, s.ExecutionsID, func() {
|
||||||
|
logger.Info().Msg("TeardownAsTarget: namespace " + s.ExecutionsID + " deleted")
|
||||||
|
defer monitor.StreamRegistry.Register(s.ExecutionsID)
|
||||||
|
}); err != nil {
|
||||||
|
logger.Error().Msg("TeardownAsTarget: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeardownAsSource destroys all Admiralty resources created by InitializeAsSource on the
|
||||||
|
// source (compute) cluster: the AdmiraltySource CRD, the ServiceAccount, and the namespace.
|
||||||
|
// The namespace deletion cascades the Role and RoleBinding.
|
||||||
|
func (s *AdmiraltySetter) TeardownAsSource(ctx context.Context) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
host := conf.GetConfig().KubeHost + ":" + conf.GetConfig().KubePort
|
||||||
|
ca := conf.GetConfig().KubeCA
|
||||||
|
cert := conf.GetConfig().KubeCert
|
||||||
|
data := conf.GetConfig().KubeData
|
||||||
|
|
||||||
|
// Delete the AdmiraltySource CRD via dynamic client
|
||||||
|
gvrSources := schema.GroupVersionResource{
|
||||||
|
Group: "multicluster.admiralty.io", Version: "v1alpha1", Resource: "sources",
|
||||||
|
}
|
||||||
|
if dyn, err := tools.NewDynamicClient(host, ca, cert, data); err != nil {
|
||||||
|
logger.Error().Msg("TeardownAsSource: failed to create dynamic client: " + err.Error())
|
||||||
|
} else if err := dyn.Resource(gvrSources).Namespace(s.ExecutionsID).Delete(
|
||||||
|
ctx, "source-"+s.ExecutionsID, metav1.DeleteOptions{},
|
||||||
|
); err != nil {
|
||||||
|
logger.Error().Msg("TeardownAsSource: failed to delete AdmiraltySource: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the namespace (cascades SA, Role, RoleBinding)
|
||||||
|
serv, err := tools.NewKubernetesService(host, ca, cert, data)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("TeardownAsSource: failed to create k8s service: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := serv.Set.CoreV1().Namespaces().Delete(ctx, s.ExecutionsID, metav1.DeleteOptions{}); err != nil {
|
||||||
|
logger.Error().Msg("TeardownAsSource: failed to delete namespace: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Info().Msg("TeardownAsSource: namespace " + s.ExecutionsID + " deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildHostKubeWithToken builds a kubeconfig pointing to this peer's cluster,
|
||||||
|
// authenticated with the provided service-account token.
|
||||||
|
func buildHostKubeWithToken(token string) (*models.KubeConfigValue, error) {
|
||||||
|
if len(token) == 0 {
|
||||||
|
return nil, fmt.Errorf("buildHostKubeWithToken: empty token")
|
||||||
|
}
|
||||||
|
encodedCA := base64.StdEncoding.EncodeToString([]byte(conf.GetConfig().KubeCA))
|
||||||
|
return &models.KubeConfigValue{
|
||||||
|
APIVersion: "v1",
|
||||||
|
CurrentContext: "default",
|
||||||
|
Kind: "Config",
|
||||||
|
Preferences: struct{}{},
|
||||||
|
Clusters: []models.KubeconfigNamedCluster{{
|
||||||
|
Name: "default",
|
||||||
|
Cluster: models.KubeconfigCluster{
|
||||||
|
Server: "https://" + conf.GetConfig().KubeHost + ":6443",
|
||||||
|
CertificateAuthorityData: encodedCA,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Contexts: []models.KubeconfigNamedContext{{
|
||||||
|
Name: "default",
|
||||||
|
Context: models.KubeconfigContext{Cluster: "default", User: "default"},
|
||||||
|
}},
|
||||||
|
Users: []models.KubeconfigUser{{
|
||||||
|
Name: "default",
|
||||||
|
User: models.KubeconfigUserKeyPair{Token: token},
|
||||||
|
}},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
244
infrastructure/booking_watchdog.go
Normal file
244
infrastructure/booking_watchdog.go
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
package infrastructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"oc-datacenter/infrastructure/minio"
|
||||||
|
|
||||||
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/dbs"
|
||||||
|
bookingmodel "cloud.o-forge.io/core/oc-lib/models/booking"
|
||||||
|
"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"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// processedBookings tracks booking IDs whose start-expiry has already been handled.
|
||||||
|
// Resets on restart; teardown methods are idempotent so duplicate runs are safe.
|
||||||
|
var processedBookings sync.Map
|
||||||
|
|
||||||
|
// processedEndBookings tracks booking IDs whose end-expiry (Admiralty source cleanup)
|
||||||
|
// has already been triggered in this process lifetime.
|
||||||
|
var processedEndBookings sync.Map
|
||||||
|
|
||||||
|
// closingStates is the set of terminal booking states after which infra must be torn down.
|
||||||
|
var closingStates = map[enum.BookingStatus]bool{
|
||||||
|
enum.FAILURE: true,
|
||||||
|
enum.SUCCESS: true,
|
||||||
|
enum.FORGOTTEN: true,
|
||||||
|
enum.CANCELLED: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchBookings starts a passive loop that ticks every minute, scans bookings whose
|
||||||
|
// ExpectedStartDate + 1 min has passed, transitions them to terminal states when needed,
|
||||||
|
// and tears down the associated Kubernetes / Minio infrastructure.
|
||||||
|
// Must be launched in a goroutine from main.
|
||||||
|
func WatchBookings() {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
logger.Info().Msg("BookingWatchdog: started")
|
||||||
|
ticker := time.NewTicker(time.Minute)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
if err := scanExpiredBookings(); err != nil {
|
||||||
|
logger.Error().Msg("BookingWatchdog: " + err.Error())
|
||||||
|
}
|
||||||
|
if err := scanEndedExec(); err != nil {
|
||||||
|
logger.Error().Msg("BookingWatchdog: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanExpiredBookings queries all bookings whose start deadline has passed and
|
||||||
|
// dispatches each one to processExpiredBooking.
|
||||||
|
func scanExpiredBookings() error {
|
||||||
|
myself, err := oclib.GetMySelf()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not resolve local peer: %w", err)
|
||||||
|
}
|
||||||
|
peerID := myself.GetID()
|
||||||
|
|
||||||
|
deadline := time.Now().UTC().Add(-time.Minute)
|
||||||
|
res := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), "", peerID, []string{}, nil).
|
||||||
|
Search(&dbs.Filters{
|
||||||
|
And: map[string][]dbs.Filter{
|
||||||
|
"expected_start_date": {{
|
||||||
|
Operator: dbs.LTE.String(),
|
||||||
|
Value: primitive.NewDateTimeFromTime(deadline),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}, "", false)
|
||||||
|
|
||||||
|
if res.Err != "" {
|
||||||
|
return fmt.Errorf("booking search failed: %s", res.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dbo := range res.Data {
|
||||||
|
b, ok := dbo.(*bookingmodel.Booking)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go processExpiredBooking(b, peerID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processExpiredBooking transitions the booking to a terminal state when applicable,
|
||||||
|
// then tears down infrastructure based on the resource type:
|
||||||
|
// - LIVE_DATACENTER / COMPUTE_RESOURCE → Admiralty (as target) + Minio (as target)
|
||||||
|
// - LIVE_STORAGE / STORAGE_RESOURCE → Minio (as source)
|
||||||
|
func processExpiredBooking(b *bookingmodel.Booking, peerID string) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Skip bookings already handled during this process lifetime.
|
||||||
|
if _, done := processedBookings.Load(b.GetID()); done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition non-terminal bookings.
|
||||||
|
if !closingStates[b.State] {
|
||||||
|
var newState enum.BookingStatus
|
||||||
|
switch b.State {
|
||||||
|
case enum.DRAFT, enum.DELAYED:
|
||||||
|
// DRAFT: never launched; DELAYED: was SCHEDULED but start never arrived.
|
||||||
|
newState = enum.FORGOTTEN
|
||||||
|
case enum.SCHEDULED:
|
||||||
|
// Passed its start date without ever being launched.
|
||||||
|
newState = enum.FAILURE
|
||||||
|
case enum.STARTED:
|
||||||
|
// A running booking is never auto-closed by the watchdog.
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
upd := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), "", peerID, []string{}, nil).
|
||||||
|
UpdateOne(map[string]any{"state": newState.EnumIndex()}, b.GetID())
|
||||||
|
if upd.Err != "" {
|
||||||
|
logger.Error().Msgf("BookingWatchdog: failed to update booking %s: %s", b.GetID(), upd.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.State = newState
|
||||||
|
logger.Info().Msgf("BookingWatchdog: booking %s (exec=%s, type=%s) → %s",
|
||||||
|
b.GetID(), b.ExecutionsID, b.ResourceType, b.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as handled before triggering async teardown (avoids double-trigger on next tick).
|
||||||
|
processedBookings.Store(b.GetID(), struct{}{})
|
||||||
|
|
||||||
|
// Tear down infrastructure according to resource type.
|
||||||
|
switch b.ResourceType {
|
||||||
|
case tools.LIVE_DATACENTER, tools.COMPUTE_RESOURCE:
|
||||||
|
logger.Info().Msgf("BookingWatchdog: tearing down compute infra exec=%s", b.ExecutionsID)
|
||||||
|
go NewAdmiraltySetter(b.ExecutionsID).TeardownAsSource(ctx) // i'm the compute units.
|
||||||
|
go teardownMinioForComputeBooking(ctx, b, peerID)
|
||||||
|
|
||||||
|
case tools.LIVE_STORAGE, tools.STORAGE_RESOURCE:
|
||||||
|
logger.Info().Msgf("BookingWatchdog: tearing down storage infra exec=%s", b.ExecutionsID)
|
||||||
|
go teardownMinioSourceBooking(ctx, b, peerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanEndedBookings queries LIVE_DATACENTER / COMPUTE_RESOURCE bookings whose
|
||||||
|
// ExpectedEndDate + 1 min has passed and triggers TeardownAsSource for Admiralty,
|
||||||
|
// cleaning up the compute-side namespace once the execution window is over.
|
||||||
|
func scanEndedExec() error {
|
||||||
|
myself, err := oclib.GetMySelf()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not resolve local peer: %w", err)
|
||||||
|
}
|
||||||
|
peerID := myself.GetID()
|
||||||
|
res := oclib.NewRequest(oclib.LibDataEnum(oclib.WORKFLOW_EXECUTION), "", peerID, []string{}, nil).
|
||||||
|
Search(&dbs.Filters{
|
||||||
|
And: map[string][]dbs.Filter{
|
||||||
|
// Only compute bookings require Admiralty source cleanup.
|
||||||
|
"state": {{
|
||||||
|
Operator: dbs.GT.String(),
|
||||||
|
Value: 2,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}, "", false)
|
||||||
|
|
||||||
|
if res.Err != "" {
|
||||||
|
return fmt.Errorf("ended-booking search failed: %s", res.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dbo := range res.Data {
|
||||||
|
b, ok := dbo.(*workflow_execution.WorkflowExecution)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go teardownAdmiraltyTarget(b)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// teardownAdmiraltySource triggers TeardownAsSource for the compute-side namespace
|
||||||
|
// of an execution whose expected end date has passed.
|
||||||
|
func teardownAdmiraltyTarget(b *workflow_execution.WorkflowExecution) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
|
||||||
|
// Each executionsID is processed at most once per process lifetime.
|
||||||
|
if _, done := processedEndBookings.Load(b.ExecutionsID); done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
processedEndBookings.Store(b.ExecutionsID, struct{}{})
|
||||||
|
|
||||||
|
logger.Info().Msgf("BookingWatchdog: tearing down Admiralty source exec=%s (booking=%s)",
|
||||||
|
b.ExecutionsID, b.GetID())
|
||||||
|
if p, err := oclib.GetMySelf(); err == nil {
|
||||||
|
NewAdmiraltySetter(b.ExecutionsID).TeardownAsTarget(context.Background(), p.GetID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// teardownMinioForComputeBooking finds the LIVE_STORAGE bookings belonging to the same
|
||||||
|
// execution and triggers Minio-as-target teardown for each (K8s secret + configmap).
|
||||||
|
// The Minio-as-source side is handled separately by the storage booking's own watchdog pass.
|
||||||
|
func teardownMinioForComputeBooking(ctx context.Context, computeBooking *bookingmodel.Booking, localPeerID string) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
|
||||||
|
res := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), "", localPeerID, []string{}, nil).
|
||||||
|
Search(&dbs.Filters{
|
||||||
|
And: map[string][]dbs.Filter{
|
||||||
|
"executions_id": {{Operator: dbs.EQUAL.String(), Value: computeBooking.ExecutionsID}},
|
||||||
|
"resource_type": {{Operator: dbs.EQUAL.String(), Value: tools.LIVE_STORAGE.EnumIndex()}},
|
||||||
|
},
|
||||||
|
}, "", false)
|
||||||
|
|
||||||
|
if res.Err != "" || len(res.Data) == 0 {
|
||||||
|
logger.Warn().Msgf("BookingWatchdog: no storage booking found for exec=%s", computeBooking.ExecutionsID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dbo := range res.Data {
|
||||||
|
sb, ok := dbo.(*bookingmodel.Booking)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := minio.MinioDeleteEvent{
|
||||||
|
ExecutionsID: computeBooking.ExecutionsID,
|
||||||
|
MinioID: sb.ResourceID,
|
||||||
|
SourcePeerID: sb.DestPeerID, // peer hosting Minio
|
||||||
|
DestPeerID: localPeerID, // this peer (compute/target)
|
||||||
|
OriginID: "",
|
||||||
|
}
|
||||||
|
minio.NewMinioSetter(computeBooking.ExecutionsID, sb.ResourceID).TeardownAsTarget(ctx, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// teardownMinioSourceBooking triggers Minio-as-source teardown for a storage booking:
|
||||||
|
// revokes the scoped service account and removes the execution bucket on this Minio host.
|
||||||
|
func teardownMinioSourceBooking(ctx context.Context, b *bookingmodel.Booking, localPeerID string) {
|
||||||
|
event := minio.MinioDeleteEvent{
|
||||||
|
ExecutionsID: b.ExecutionsID,
|
||||||
|
MinioID: b.ResourceID,
|
||||||
|
SourcePeerID: localPeerID, // this peer IS the Minio host
|
||||||
|
DestPeerID: b.DestPeerID,
|
||||||
|
OriginID: "",
|
||||||
|
}
|
||||||
|
minio.NewMinioSetter(b.ExecutionsID, b.ResourceID).TeardownAsSource(ctx, event)
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package infrastructure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"oc-datacenter/conf"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Infrastructure interface {
|
|
||||||
CreateNamespace(ctx context.Context, ns string) error
|
|
||||||
DeleteNamespace(ctx context.Context, ns string) error
|
|
||||||
GenerateToken(ctx context.Context, ns string, duration int) (string, error)
|
|
||||||
CreateServiceAccount(ctx context.Context, ns string) error
|
|
||||||
CreateRoleBinding(ctx context.Context, ns string, roleBinding string, role string) error
|
|
||||||
CreateRole(ctx context.Context, ns string, role string, groups [][]string, resources [][]string, verbs [][]string) error
|
|
||||||
GetTargets(ctx context.Context) ([]string, error)
|
|
||||||
CreateAdmiraltySource(context context.Context, executionId string) ([]byte, error)
|
|
||||||
CreateKubeconfigSecret(context context.Context, kubeconfig string, executionId string, peerId string) ([]byte, error)
|
|
||||||
GetKubeconfigSecret(context context.Context, executionId string, peerId string) ([]byte, error)
|
|
||||||
CreateAdmiraltyTarget(context context.Context, executionId string, peerId string) ([]byte, error)
|
|
||||||
GetOneNode(context context.Context, executionID string, peerId string) (*v1.Node, error)
|
|
||||||
GetNamespace(context context.Context, executionID string) (*v1.Namespace, error)
|
|
||||||
CreateSecret(context context.Context, minioId string, executionID string, access string, secret string) error
|
|
||||||
CheckHealth() error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _service = map[string]func() (Infrastructure, error){
|
|
||||||
"kubernetes": NewKubernetesService,
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServiceByType(t string) (Infrastructure, error) {
|
|
||||||
service, ok := _service[t]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("service not found")
|
|
||||||
}
|
|
||||||
return service()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewService() (Infrastructure, error) {
|
|
||||||
service, ok := _service[conf.GetConfig().Mode]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("service not found")
|
|
||||||
}
|
|
||||||
return service()
|
|
||||||
}
|
|
||||||
@@ -1,619 +0,0 @@
|
|||||||
package infrastructure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"oc-datacenter/conf"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDynamicClient() (*dynamic.DynamicClient, error) {
|
|
||||||
config := &rest.Config{
|
|
||||||
Host: conf.GetConfig().KubeHost + ":" + conf.GetConfig().KubePort,
|
|
||||||
TLSClientConfig: rest.TLSClientConfig{
|
|
||||||
CAData: []byte(conf.GetConfig().KubeCA),
|
|
||||||
CertData: []byte(conf.GetConfig().KubeCert),
|
|
||||||
KeyData: []byte(conf.GetConfig().KubeData),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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() (Infrastructure, error) {
|
|
||||||
config := &rest.Config{
|
|
||||||
Host: conf.GetConfig().KubeHost + ":" + conf.GetConfig().KubePort,
|
|
||||||
TLSClientConfig: rest.TLSClientConfig{
|
|
||||||
CAData: []byte(conf.GetConfig().KubeCA),
|
|
||||||
CertData: []byte(conf.GetConfig().KubeCert),
|
|
||||||
KeyData: []byte(conf.GetConfig().KubeData),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRemoteKubernetesService(url string, ca string, cert string, key string) (Infrastructure, 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,
|
|
||||||
}, 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) error {
|
|
||||||
targetGVR := schema.GroupVersionResource{
|
|
||||||
Group: "multicluster.admiralty.io",
|
|
||||||
Version: "v1alpha1",
|
|
||||||
Resource: "targets",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the Target
|
|
||||||
dyn, err := NewDynamicClient()
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
LockKill.Lock()
|
|
||||||
Kill = append(Kill, ns)
|
|
||||||
LockKill.Unlock()
|
|
||||||
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-" + oclib.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-" + oclib.GetConcatenatedName(peerId, executionId),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := dynamicClientApply(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(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-"+ oclib.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-"+oclib.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 := oclib.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(executionId string, resourceName string, resourceDefinition schema.GroupVersionResource, ctx context.Context, object map[string]interface{}) ([]byte, error) {
|
|
||||||
cli, err := NewDynamicClient()
|
|
||||||
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 := oclib.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 := oclib.GetLogger()
|
|
||||||
logger.Error().Msg("An error happened when creating the secret holding minio credentials in namespace " + executionID + " : " + err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package infrastructure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"oc-datacenter/conf"
|
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
|
||||||
"github.com/minio/madmin-go/v4"
|
|
||||||
"github.com/minio/minio-go/v7"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
||||||
|
|
||||||
"github.com/necmettindev/randomstring"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MinioService struct{
|
|
||||||
Url string
|
|
||||||
RootKey string
|
|
||||||
RootSecret string
|
|
||||||
MinioAdminClient *madmin.AdminClient
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatementEntry struct {
|
|
||||||
Effect string `json:"Effect"`
|
|
||||||
Action []string `json:"Action"`
|
|
||||||
Resource string `json:"Resource"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PolicyDocument struct {
|
|
||||||
Version string `json:"Version"`
|
|
||||||
Statement []StatementEntry `json:"Statement"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func NewMinioService(url string) *MinioService {
|
|
||||||
return &MinioService{
|
|
||||||
Url: url,
|
|
||||||
RootKey: conf.GetConfig().MinioRootKey,
|
|
||||||
RootSecret: conf.GetConfig().MinioRootSecret,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MinioService) CreateClient() error {
|
|
||||||
cred := credentials.NewStaticV4(m.RootKey,m.RootSecret,"")
|
|
||||||
cli, err := madmin.NewWithOptions(m.Url, &madmin.Options{Creds: cred, Secure: false}) // Maybe in the future we should use the secure option ?
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.MinioAdminClient = cli
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MinioService) CreateCredentials(executionId string) (string,string,error){
|
|
||||||
|
|
||||||
policy := PolicyDocument{
|
|
||||||
Version: "2012-10-17",
|
|
||||||
Statement: []StatementEntry{
|
|
||||||
{
|
|
||||||
Effect: "Allow",
|
|
||||||
Action: []string{"s3:GetObject", "s3:PutObject"},
|
|
||||||
Resource: "arn:aws:s3:::"+executionId+"/*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := json.Marshal(policy)
|
|
||||||
if err != nil {
|
|
||||||
return "","",err
|
|
||||||
}
|
|
||||||
|
|
||||||
randAccess, randSecret := getRandomCreds()
|
|
||||||
|
|
||||||
req := madmin.AddServiceAccountReq{
|
|
||||||
Policy: p,
|
|
||||||
TargetUser: m.RootKey,
|
|
||||||
AccessKey: randAccess,
|
|
||||||
SecretKey: randSecret,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := m.MinioAdminClient.AddServiceAccount(context.Background(), req)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return res.AccessKey, res.SecretKey, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRandomCreds() (string, string){
|
|
||||||
opts := randomstring.GenerationOptions{
|
|
||||||
Length: 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
a, _ := randomstring.GenerateString(opts)
|
|
||||||
|
|
||||||
opts.Length = 40
|
|
||||||
s, _ := randomstring.GenerateString(opts)
|
|
||||||
|
|
||||||
return a,s
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MinioService) CreateBucket(executionId string) error {
|
|
||||||
|
|
||||||
l := oclib.GetLogger()
|
|
||||||
cred := credentials.NewStaticV4(m.RootKey,m.RootSecret,"")
|
|
||||||
client, err := minio.New(m.Url, &minio.Options{
|
|
||||||
Creds: cred,
|
|
||||||
Secure: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
l.Error().Msg("Error when creating the minio client for the data plane")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.MakeBucket(context.Background(), executionId, minio.MakeBucketOptions{})
|
|
||||||
if err != nil {
|
|
||||||
l.Error().Msg("Error when creating the bucket for namespace " + executionId)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Info().Msg("Created the bucket " + executionId + " on " + m.Url + " minio")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
219
infrastructure/minio/minio.go
Normal file
219
infrastructure/minio/minio.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"oc-datacenter/conf"
|
||||||
|
|
||||||
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
|
"github.com/minio/madmin-go/v4"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
|
||||||
|
"github.com/necmettindev/randomstring"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MinioService struct {
|
||||||
|
Url string
|
||||||
|
RootKey string
|
||||||
|
RootSecret string
|
||||||
|
MinioAdminClient *madmin.AdminClient
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatementEntry struct {
|
||||||
|
Effect string `json:"Effect"`
|
||||||
|
Action []string `json:"Action"`
|
||||||
|
Resource string `json:"Resource"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PolicyDocument struct {
|
||||||
|
Version string `json:"Version"`
|
||||||
|
Statement []StatementEntry `json:"Statement"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMinioService(url string) *MinioService {
|
||||||
|
return &MinioService{
|
||||||
|
Url: url,
|
||||||
|
RootKey: conf.GetConfig().MinioRootKey,
|
||||||
|
RootSecret: conf.GetConfig().MinioRootSecret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MinioService) CreateClient() error {
|
||||||
|
cred := credentials.NewStaticV4(m.RootKey, m.RootSecret, "")
|
||||||
|
cli, err := madmin.NewWithOptions(m.Url, &madmin.Options{Creds: cred, Secure: false}) // Maybe in the future we should use the secure option ?
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.MinioAdminClient = cli
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MinioService) CreateCredentials(executionId string) (string, string, error) {
|
||||||
|
|
||||||
|
policy := PolicyDocument{
|
||||||
|
Version: "2012-10-17",
|
||||||
|
Statement: []StatementEntry{
|
||||||
|
{
|
||||||
|
Effect: "Allow",
|
||||||
|
Action: []string{"s3:GetObject", "s3:PutObject"},
|
||||||
|
Resource: "arn:aws:s3:::" + executionId + "/*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := json.Marshal(policy)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
randAccess, randSecret := getRandomCreds()
|
||||||
|
|
||||||
|
req := madmin.AddServiceAccountReq{
|
||||||
|
Policy: p,
|
||||||
|
TargetUser: m.RootKey,
|
||||||
|
AccessKey: randAccess,
|
||||||
|
SecretKey: randSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := m.MinioAdminClient.AddServiceAccount(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.AccessKey, res.SecretKey, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomCreds() (string, string) {
|
||||||
|
opts := randomstring.GenerationOptions{
|
||||||
|
Length: 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
a, _ := randomstring.GenerateString(opts)
|
||||||
|
|
||||||
|
opts.Length = 40
|
||||||
|
s, _ := randomstring.GenerateString(opts)
|
||||||
|
|
||||||
|
return a, s
|
||||||
|
|
||||||
|
}
|
||||||
|
func (m *MinioService) CreateMinioConfigMap(minioID string, executionId string, url string) error {
|
||||||
|
config, err := rest.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientset, err := kubernetes.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configMap := &v1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: minioID + "artifact-repository",
|
||||||
|
Namespace: executionId,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
minioID + "s3-local": fmt.Sprintf(`
|
||||||
|
s3:
|
||||||
|
bucket: %s
|
||||||
|
endpoint: %s
|
||||||
|
insecure: true
|
||||||
|
accessKeySecret:
|
||||||
|
name: %s-secret-s3
|
||||||
|
key: accesskey
|
||||||
|
secretKeySecret:
|
||||||
|
name: %s-secret-s3
|
||||||
|
key: secretkey
|
||||||
|
`, minioID+"-"+executionId, url, minioID, minioID),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := clientset.CoreV1().
|
||||||
|
ConfigMaps(executionId).
|
||||||
|
Get(context.Background(), minioID+"artifact-repository", metav1.GetOptions{})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Update
|
||||||
|
existing.Data = configMap.Data
|
||||||
|
_, err = clientset.CoreV1().
|
||||||
|
ConfigMaps(executionId).
|
||||||
|
Update(context.Background(), existing, metav1.UpdateOptions{})
|
||||||
|
} else {
|
||||||
|
// Create
|
||||||
|
_, err = clientset.CoreV1().
|
||||||
|
ConfigMaps(executionId).
|
||||||
|
Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MinioService) CreateBucket(minioID string, executionId string) error {
|
||||||
|
l := oclib.GetLogger()
|
||||||
|
cred := credentials.NewStaticV4(m.RootKey, m.RootSecret, "")
|
||||||
|
client, err := minio.New(m.Url, &minio.Options{
|
||||||
|
Creds: cred,
|
||||||
|
Secure: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
l.Error().Msg("Error when creating the minio client for the data plane")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.MakeBucket(context.Background(), minioID+"-"+executionId, minio.MakeBucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
l.Error().Msg("Error when creating the bucket for namespace " + executionId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Info().Msg("Created the bucket " + minioID + "-" + executionId + " on " + m.Url + " minio")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCredentials revokes a scoped Minio service account by its access key.
|
||||||
|
func (m *MinioService) DeleteCredentials(accessKey string) error {
|
||||||
|
if err := m.MinioAdminClient.DeleteServiceAccount(context.Background(), accessKey); err != nil {
|
||||||
|
return fmt.Errorf("DeleteCredentials: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucket removes the execution bucket from Minio.
|
||||||
|
func (m *MinioService) DeleteBucket(minioID, executionId string) error {
|
||||||
|
l := oclib.GetLogger()
|
||||||
|
cred := credentials.NewStaticV4(m.RootKey, m.RootSecret, "")
|
||||||
|
client, err := minio.New(m.Url, &minio.Options{Creds: cred, Secure: false})
|
||||||
|
if err != nil {
|
||||||
|
l.Error().Msg("Error when creating minio client for bucket deletion")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bucketName := minioID + "-" + executionId
|
||||||
|
if err := client.RemoveBucket(context.Background(), bucketName); err != nil {
|
||||||
|
l.Error().Msg("Error when deleting bucket " + bucketName)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Info().Msg("Deleted bucket " + bucketName + " on " + m.Url)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMinioConfigMap removes the artifact-repository ConfigMap from the execution namespace.
|
||||||
|
func (m *MinioService) DeleteMinioConfigMap(minioID, executionId string) error {
|
||||||
|
cfg, err := rest.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clientset, err := kubernetes.NewForConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return clientset.CoreV1().ConfigMaps(executionId).Delete(
|
||||||
|
context.Background(), minioID+"artifact-repository", metav1.DeleteOptions{},
|
||||||
|
)
|
||||||
|
}
|
||||||
297
infrastructure/minio/minio_setter.go
Normal file
297
infrastructure/minio/minio_setter.go
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"oc-datacenter/conf"
|
||||||
|
|
||||||
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/live"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/tools"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MinioCredentialEvent is the NATS payload used to transfer Minio credentials between peers.
|
||||||
|
//
|
||||||
|
// Two-phase protocol over PROPALGATION_EVENT (Action = PB_MINIO_CONFIG):
|
||||||
|
// - Phase 1 – role assignment (Access == ""):
|
||||||
|
// oc-discovery routes this to the SOURCE peer (Minio host) → InitializeAsSource.
|
||||||
|
// - Phase 2 – credential delivery (Access != ""):
|
||||||
|
// oc-discovery routes this to the TARGET peer (compute host) → InitializeAsTarget.
|
||||||
|
type MinioCredentialEvent struct {
|
||||||
|
ExecutionsID string `json:"executions_id"`
|
||||||
|
MinioID string `json:"minio_id"`
|
||||||
|
Access string `json:"access"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
SourcePeerID string `json:"source_peer_id"`
|
||||||
|
DestPeerID string `json:"dest_peer_id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
// OriginID is the peer that initiated the provisioning request.
|
||||||
|
// The PB_CONSIDERS response is routed back to this peer.
|
||||||
|
OriginID string `json:"origin_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// minioConsidersPayload is the PB_CONSIDERS payload emitted after minio provisioning.
|
||||||
|
type minioConsidersPayload struct {
|
||||||
|
OriginID string `json:"origin_id"`
|
||||||
|
ExecutionsID string `json:"executions_id"`
|
||||||
|
Secret string `json:"secret,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitConsiders publishes a PB_CONSIDERS back to OriginID with the result of
|
||||||
|
// the minio provisioning. secret is the provisioned credential; err is nil on success.
|
||||||
|
func emitConsiders(executionsID, originID, secret string, provErr error) {
|
||||||
|
var errStr *string
|
||||||
|
if provErr != nil {
|
||||||
|
s := provErr.Error()
|
||||||
|
errStr = &s
|
||||||
|
}
|
||||||
|
payload, _ := json.Marshal(minioConsidersPayload{
|
||||||
|
OriginID: originID,
|
||||||
|
ExecutionsID: executionsID,
|
||||||
|
Secret: secret,
|
||||||
|
Error: errStr,
|
||||||
|
})
|
||||||
|
b, _ := json.Marshal(&tools.PropalgationMessage{
|
||||||
|
DataType: tools.STORAGE_RESOURCE.EnumIndex(),
|
||||||
|
Action: tools.PB_CONSIDERS,
|
||||||
|
Payload: payload,
|
||||||
|
})
|
||||||
|
go tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||||
|
FromApp: "oc-datacenter",
|
||||||
|
Datatype: -1,
|
||||||
|
Method: int(tools.PROPALGATION_EVENT),
|
||||||
|
Payload: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinioSetter carries the execution context for a Minio credential provisioning.
|
||||||
|
type MinioSetter struct {
|
||||||
|
ExecutionsID string // used as both the bucket name and the K8s namespace suffix
|
||||||
|
MinioID string // ID of the Minio storage resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMinioSetter(execID, minioID string) *MinioSetter {
|
||||||
|
return &MinioSetter{ExecutionsID: execID, MinioID: minioID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeAsSource is called on the peer that hosts the Minio instance.
|
||||||
|
//
|
||||||
|
// It:
|
||||||
|
// 1. Looks up the live-storage endpoint URL for MinioID.
|
||||||
|
// 2. Creates a scoped service account (access + secret limited to the execution bucket).
|
||||||
|
// 3. Creates the execution bucket.
|
||||||
|
// 4. If source and dest are the same peer, calls InitializeAsTarget directly.
|
||||||
|
// Otherwise, publishes a MinioCredentialEvent via NATS (Phase 2) so that
|
||||||
|
// oc-discovery can route the credentials to the compute peer.
|
||||||
|
func (m *MinioSetter) InitializeAsSource(ctx context.Context, localPeerID, destPeerID, originID string) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
|
||||||
|
url, err := m.loadMinioURL(localPeerID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.InitializeAsSource: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
service := NewMinioService(url)
|
||||||
|
if err := service.CreateClient(); err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.InitializeAsSource: failed to create admin client: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
access, secret, err := service.CreateCredentials(m.ExecutionsID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.InitializeAsSource: failed to create service account: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := service.CreateBucket(m.MinioID, m.ExecutionsID); err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.InitializeAsSource: failed to create bucket: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Msg("MinioSetter.InitializeAsSource: bucket and service account ready for " + m.ExecutionsID)
|
||||||
|
|
||||||
|
event := MinioCredentialEvent{
|
||||||
|
ExecutionsID: m.ExecutionsID,
|
||||||
|
MinioID: m.MinioID,
|
||||||
|
Access: access,
|
||||||
|
Secret: secret,
|
||||||
|
SourcePeerID: localPeerID,
|
||||||
|
DestPeerID: destPeerID,
|
||||||
|
OriginID: originID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if destPeerID == localPeerID {
|
||||||
|
// Same peer: store the secret locally without going through NATS.
|
||||||
|
m.InitializeAsTarget(ctx, event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross-peer: publish credentials (Phase 2) so oc-discovery routes them to the compute peer.
|
||||||
|
payload, err := json.Marshal(event)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.InitializeAsSource: failed to marshal credential event: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, err := json.Marshal(&tools.PropalgationMessage{
|
||||||
|
DataType: -1,
|
||||||
|
Action: tools.PB_MINIO_CONFIG,
|
||||||
|
Payload: payload,
|
||||||
|
}); err == nil {
|
||||||
|
go tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||||
|
FromApp: "oc-datacenter",
|
||||||
|
Datatype: -1,
|
||||||
|
User: "",
|
||||||
|
Method: int(tools.PROPALGATION_EVENT),
|
||||||
|
Payload: b,
|
||||||
|
})
|
||||||
|
logger.Info().Msg("MinioSetter.InitializeAsSource: credentials published via NATS for " + m.ExecutionsID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeAsTarget is called on the peer that runs the compute workload.
|
||||||
|
//
|
||||||
|
// It stores the Minio credentials received from the source peer (via NATS or directly)
|
||||||
|
// as a Kubernetes secret inside the execution namespace, making them available to pods.
|
||||||
|
func (m *MinioSetter) InitializeAsTarget(ctx context.Context, event MinioCredentialEvent) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
|
||||||
|
k, err := tools.NewKubernetesService(
|
||||||
|
conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
|
||||||
|
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.InitializeAsTarget: failed to create k8s service: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := k.CreateSecret(ctx, event.MinioID, event.ExecutionsID, event.Access, event.Secret); err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.InitializeAsTarget: failed to create k8s secret: " + err.Error())
|
||||||
|
emitConsiders(event.ExecutionsID, event.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := NewMinioService(event.URL).CreateMinioConfigMap(event.MinioID, event.ExecutionsID, event.URL); err == nil {
|
||||||
|
logger.Error().Msg("MinioSetter.InitializeAsTarget: failed to create config map: " + err.Error())
|
||||||
|
emitConsiders(event.ExecutionsID, event.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Msg("MinioSetter.InitializeAsTarget: Minio credentials stored in namespace " + event.ExecutionsID)
|
||||||
|
emitConsiders(event.ExecutionsID, event.OriginID, event.Secret, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinioDeleteEvent is the NATS payload used to tear down Minio resources.
|
||||||
|
// It mirrors MinioCredentialEvent but carries the access key for revocation.
|
||||||
|
type MinioDeleteEvent struct {
|
||||||
|
ExecutionsID string `json:"executions_id"`
|
||||||
|
MinioID string `json:"minio_id"`
|
||||||
|
Access string `json:"access"` // service account access key to revoke on the Minio host
|
||||||
|
SourcePeerID string `json:"source_peer_id"`
|
||||||
|
DestPeerID string `json:"dest_peer_id"`
|
||||||
|
OriginID string `json:"origin_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeardownAsTarget is called on the peer that runs the compute workload.
|
||||||
|
// It reads the stored access key from the K8s secret, then removes both the secret
|
||||||
|
// and the artifact-repository ConfigMap from the execution namespace.
|
||||||
|
// For same-peer deployments it calls TeardownAsSource directly; otherwise it
|
||||||
|
// publishes a MinioDeleteEvent via NATS (PB_DELETE) so oc-discovery routes it to
|
||||||
|
// the Minio host peer.
|
||||||
|
func (m *MinioSetter) TeardownAsTarget(ctx context.Context, event MinioDeleteEvent) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
|
||||||
|
k, err := tools.NewKubernetesService(
|
||||||
|
conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
|
||||||
|
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.TeardownAsTarget: failed to create k8s service: " + err.Error())
|
||||||
|
emitConsiders(event.ExecutionsID, event.OriginID, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the access key from the K8s secret before deleting it.
|
||||||
|
accessKey := event.Access
|
||||||
|
if accessKey == "" {
|
||||||
|
if secret, err := k.Set.CoreV1().Secrets(event.ExecutionsID).Get(
|
||||||
|
ctx, event.MinioID+"-secret-s3", metav1.GetOptions{},
|
||||||
|
); err == nil {
|
||||||
|
accessKey = string(secret.Data["access-key"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete K8s credentials secret.
|
||||||
|
if err := k.Set.CoreV1().Secrets(event.ExecutionsID).Delete(
|
||||||
|
ctx, event.MinioID+"-secret-s3", metav1.DeleteOptions{},
|
||||||
|
); err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.TeardownAsTarget: failed to delete secret: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete artifact-repository ConfigMap.
|
||||||
|
if err := NewMinioService("").DeleteMinioConfigMap(event.MinioID, event.ExecutionsID); err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.TeardownAsTarget: failed to delete configmap: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Msg("MinioSetter.TeardownAsTarget: K8s resources removed for " + event.ExecutionsID)
|
||||||
|
|
||||||
|
// For same-peer deployments the source cleanup runs directly here so the
|
||||||
|
// caller (REMOVE_EXECUTION handler) doesn't have to distinguish roles.
|
||||||
|
if event.SourcePeerID == event.DestPeerID {
|
||||||
|
event.Access = accessKey
|
||||||
|
m.TeardownAsSource(ctx, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeardownAsSource is called on the peer that hosts the Minio instance.
|
||||||
|
// It revokes the scoped service account and removes the execution bucket.
|
||||||
|
func (m *MinioSetter) TeardownAsSource(ctx context.Context, event MinioDeleteEvent) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
|
||||||
|
url, err := m.loadMinioURL(event.SourcePeerID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.TeardownAsSource: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := NewMinioService(url)
|
||||||
|
if err := svc.CreateClient(); err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.TeardownAsSource: failed to create admin client: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Access != "" {
|
||||||
|
if err := svc.DeleteCredentials(event.Access); err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.TeardownAsSource: failed to delete service account: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.DeleteBucket(event.MinioID, event.ExecutionsID); err != nil {
|
||||||
|
logger.Error().Msg("MinioSetter.TeardownAsSource: failed to delete bucket: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Msg("MinioSetter.TeardownAsSource: Minio resources removed for " + event.ExecutionsID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadMinioURL searches through all live storages accessible by peerID to find
|
||||||
|
// the one that references MinioID, and returns its endpoint URL.
|
||||||
|
func (m *MinioSetter) loadMinioURL(peerID string) (string, error) {
|
||||||
|
res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false)
|
||||||
|
if res.Err != "" {
|
||||||
|
return "", fmt.Errorf("loadMinioURL: failed to load live storages: %s", res.Err)
|
||||||
|
}
|
||||||
|
for _, dbo := range res.Data {
|
||||||
|
l := dbo.(*live.LiveStorage)
|
||||||
|
if slices.Contains(l.ResourcesID, m.MinioID) {
|
||||||
|
return l.Source, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("loadMinioURL: no live storage found for minio ID %s", m.MinioID)
|
||||||
|
}
|
||||||
100
infrastructure/monitor/PROMETHEUS_ANALYSIS.md
Normal file
100
infrastructure/monitor/PROMETHEUS_ANALYSIS.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Analyse de `infrastructure/prometheus.go`
|
||||||
|
|
||||||
|
## Ce que fait le fichier
|
||||||
|
|
||||||
|
Ce fichier implémente un service de monitoring qui interroge une instance **Prometheus** pour collecter des métriques de conteneurs Kubernetes associés à une réservation (Booking).
|
||||||
|
|
||||||
|
### Structures de données
|
||||||
|
|
||||||
|
| Struct | Role |
|
||||||
|
|---|---|
|
||||||
|
| `MetricsSnapshot` | Snapshot de métriques associé à une origine (source). **Note : cette struct locale est déclarée mais jamais utilisée** — le code utilise en réalité `models.MetricsSnapshot` de oc-lib. |
|
||||||
|
| `Metric` | Paire nom/valeur d'une métrique. **Même remarque** — le code utilise `models.Metric`. |
|
||||||
|
| `PrometheusResponse` | Mapping de la réponse JSON de l'API Prometheus `/api/v1/query`. |
|
||||||
|
|
||||||
|
### Métriques collectées (`queriesMetrics`)
|
||||||
|
|
||||||
|
| # | Requête PromQL | Mesure |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | `rate(container_cpu_usage_seconds_total{namespace}[1m]) * 100` | Utilisation CPU (%) |
|
||||||
|
| 2 | `container_memory_usage_bytes{namespace}` | Mémoire utilisée (bytes) |
|
||||||
|
| 3 | `container_fs_usage_bytes / container_fs_limit_bytes * 100` | Utilisation disque (%) |
|
||||||
|
| 4 | `DCGM_FI_DEV_GPU_UTIL{namespace}` | Utilisation GPU (NVIDIA DCGM) |
|
||||||
|
| 5 | `rate(container_fs_reads_bytes_total[1m])` | Débit lecture disque (bytes/s) |
|
||||||
|
| 6 | `rate(container_fs_writes_bytes_total[1m])` | Débit écriture disque (bytes/s) |
|
||||||
|
| 7 | `rate(container_network_receive_bytes_total[1m])` | Bande passante réseau entrante (bytes/s) |
|
||||||
|
| 8 | `rate(container_network_transmit_bytes_total[1m])` | Bande passante réseau sortante (bytes/s) |
|
||||||
|
| 9 | `rate(http_requests_total[1m])` | Requêtes HTTP/s |
|
||||||
|
| 10 | `rate(http_requests_total{status=~"5.."}[1m]) / rate(http_requests_total[1m]) * 100` | Taux d'erreur HTTP 5xx (%) |
|
||||||
|
|
||||||
|
Métriques commentées (non actives) : `system_load_average`, `system_network_latency_ms`, `app_mean_time_to_repair_seconds`, `app_mean_time_between_failure_seconds`.
|
||||||
|
|
||||||
|
### Méthodes
|
||||||
|
|
||||||
|
#### `queryPrometheus(promURL, expr, namespace) Metric`
|
||||||
|
- Construit une requête GET vers `/api/v1/query` de Prometheus.
|
||||||
|
- Injecte le namespace dans l'expression PromQL via `fmt.Sprintf`.
|
||||||
|
- Parse la réponse JSON et extrait la première valeur du premier résultat.
|
||||||
|
- Retourne `-1` si aucun résultat.
|
||||||
|
|
||||||
|
#### `Call(book, user, peerID, groups) (Booking, map[string]MetricsSnapshot)`
|
||||||
|
- Charge la ressource de calcul (`ComputeResource`) liée au booking.
|
||||||
|
- Pour chaque instance de la ressource, cherche le `LiveDatacenter` correspondant.
|
||||||
|
- Lance en **goroutine** (parallèle) l'exécution de toutes les requêtes PromQL pour chaque datacenter ayant un `MonitorPath`.
|
||||||
|
- Attend toutes les goroutines (`sync.WaitGroup`), puis retourne les métriques groupées par instance.
|
||||||
|
|
||||||
|
#### `Stream(bookingID, interval, user, peerID, groups, websocket)`
|
||||||
|
- Boucle de monitoring en continu jusqu'à `ExpectedEndDate` du booking ou signal de kill.
|
||||||
|
- A chaque tick (`interval`), appelle `Call()` dans une goroutine.
|
||||||
|
- Envoie les métriques en temps réel via **WebSocket**.
|
||||||
|
- Accumule les métriques en mémoire et les persiste dans le booking tous les `max` (100) cycles.
|
||||||
|
- Supporte un mécanisme de kill via la variable globale `Kill`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problemes et points d'attention
|
||||||
|
|
||||||
|
### Bugs potentiels
|
||||||
|
|
||||||
|
1. **Race condition dans `Stream`** — Les variables `mets`, `bookIDS`, `book` sont partagées entre la boucle principale et les goroutines lancées à chaque tick, **sans synchronisation** (pas de mutex). Si `interval` est court, plusieurs goroutines peuvent écrire simultanément dans `mets` et `bookIDS`.
|
||||||
|
|
||||||
|
2. **Race condition sur `Kill`** — La variable globale `Kill` est lue dans la boucle sans verrouiller `LockKill`. Le mutex n'est utilisé que pour l'écriture.
|
||||||
|
|
||||||
|
3. **Structs locales inutilisées** — `MetricsSnapshot` et `Metric` (lignes 22-31) sont déclarées localement mais le code utilise `models.MetricsSnapshot` et `models.Metric`. Code mort à nettoyer.
|
||||||
|
|
||||||
|
4. **Requête PromQL avec double placeholder** — La requête filesystem (ligne 47) contient deux `%s` mais `queryPrometheus` ne fait qu'un seul `fmt.Sprintf(expr, namespace)`. Cela provoque un **`%!s(MISSING)`** dans la requête. Il faut passer le namespace deux fois ou réécrire la fonction.
|
||||||
|
|
||||||
|
5. **Pas de timeout HTTP** — `http.Get()` utilise le client par défaut sans timeout. Un Prometheus lent peut bloquer indéfiniment.
|
||||||
|
|
||||||
|
6. **Pas de gestion d'erreur sur `WriteJSON`** — Si le WebSocket est fermé côté client, l'écriture échoue silencieusement.
|
||||||
|
|
||||||
|
### Améliorations possibles
|
||||||
|
|
||||||
|
#### Fiabilité
|
||||||
|
- **Ajouter un `context.Context`** à `queryPrometheus` et `Call` pour supporter les timeouts et l'annulation.
|
||||||
|
- **Utiliser un `http.Client` avec timeout** au lieu de `http.Get`.
|
||||||
|
- **Protéger les accès concurrents** dans `Stream` avec un `sync.Mutex` sur `mets`/`bookIDS`.
|
||||||
|
- **Remplacer la variable globale `Kill`** par un `context.WithCancel` ou un channel, plus idiomatique en Go.
|
||||||
|
|
||||||
|
#### Métriques supplémentaires envisageables
|
||||||
|
- `container_cpu_cfs_throttled_seconds_total` — Throttling CPU (le container est bridé).
|
||||||
|
- `kube_pod_container_status_restarts_total` — Nombre de restarts (instabilité).
|
||||||
|
- `container_memory_working_set_bytes` — Mémoire réelle utilisée (exclut le cache, plus précis que `memory_usage_bytes`).
|
||||||
|
- `kube_pod_status_phase` — Phase du pod (Running, Pending, Failed...).
|
||||||
|
- `container_oom_events_total` ou `kube_pod_container_status_last_terminated_reason` — Détection des OOM kills.
|
||||||
|
- `kubelet_volume_stats_used_bytes` / `kubelet_volume_stats_capacity_bytes` — Utilisation des PVC.
|
||||||
|
- `DCGM_FI_DEV_MEM_COPY_UTIL` — Utilisation mémoire GPU.
|
||||||
|
- `DCGM_FI_DEV_GPU_TEMP` — Température GPU.
|
||||||
|
- `node_cpu_seconds_total` / `node_memory_MemAvailable_bytes` — Métriques au niveau du noeud (vue globale).
|
||||||
|
|
||||||
|
#### Architecture
|
||||||
|
- **Range queries** (`/api/v1/query_range`) — Actuellement seul l'instant query est utilisé. Pour le streaming sur une période, `query_range` permettrait de récupérer des séries temporelles complètes et de calculer des moyennes/percentiles.
|
||||||
|
- **Labels dans les résultats** — Actuellement seule la première série est lue (`Result[0]`). On perd l'information si plusieurs pods/containers matchent. Agréger ou renvoyer toutes les séries.
|
||||||
|
- **Noms de métriques lisibles** — Mapper les expressions PromQL vers des noms humains (`cpu_usage_percent`, `memory_bytes`, etc.) au lieu de stocker l'expression brute comme nom.
|
||||||
|
- **Health check Prometheus** — Ajouter une méthode pour vérifier que Prometheus est accessible (`/-/healthy`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé
|
||||||
|
|
||||||
|
Le fichier est **fonctionnel** pour un cas d'usage basique (collecte one-shot + streaming WebSocket), mais présente des **race conditions** dans `Stream`, un **bug sur la requête filesystem** (double `%s`), et du **code mort**. Les améliorations prioritaires sont la correction des accès concurrents et l'ajout de timeouts HTTP.
|
||||||
75
infrastructure/monitor/monitor.go
Normal file
75
infrastructure/monitor/monitor.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package monitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"oc-datacenter/conf"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/dbs"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/booking"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/common/models"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/live"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MonitorInterface interface {
|
||||||
|
Stream(ctx context.Context, bookingID string, interval time.Duration, ws *websocket.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _monitorService = map[string]func() MonitorInterface{
|
||||||
|
"prometheus": func() MonitorInterface { return NewPrometheusService() },
|
||||||
|
"vector": func() MonitorInterface { return NewVectorService() },
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMonitorService() (MonitorInterface, error) {
|
||||||
|
service, ok := _monitorService[conf.GetConfig().MonitorMode]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("monitor service not found")
|
||||||
|
}
|
||||||
|
return service(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Call(book *booking.Booking,
|
||||||
|
f func(*live.LiveDatacenter, *resources.ComputeResourceInstance,
|
||||||
|
map[string]models.MetricsSnapshot, *sync.WaitGroup, *sync.Mutex)) (*booking.Booking, map[string]models.MetricsSnapshot) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
metrics := map[string]models.MetricsSnapshot{}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
cUAccess := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.LIVE_DATACENTER), nil)
|
||||||
|
cRAccess := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.COMPUTE_RESOURCE), nil)
|
||||||
|
|
||||||
|
rr := cRAccess.LoadOne(book.ResourceID)
|
||||||
|
if rr.Err != "" {
|
||||||
|
logger.Err(fmt.Errorf("can't proceed because of unfound resource %s : %s", book.ResourceID, rr.Err))
|
||||||
|
return book, metrics
|
||||||
|
}
|
||||||
|
computeRes := rr.ToComputeResource()
|
||||||
|
for _, instance := range computeRes.Instances {
|
||||||
|
res := cUAccess.Search(&dbs.Filters{
|
||||||
|
And: map[string][]dbs.Filter{
|
||||||
|
"source": {{Operator: dbs.EQUAL.String(), Value: instance.Source}},
|
||||||
|
"abstractlive.resources_id": {{Operator: dbs.EQUAL.String(), Value: computeRes.GetID()}},
|
||||||
|
},
|
||||||
|
}, "", false)
|
||||||
|
if res.Err != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, r := range res.Data {
|
||||||
|
dc := r.(*live.LiveDatacenter)
|
||||||
|
if dc.MonitorPath == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go f(dc, instance, metrics, &wg, &mu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return book, metrics
|
||||||
|
}
|
||||||
194
infrastructure/monitor/prometheus.go
Normal file
194
infrastructure/monitor/prometheus.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package monitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/booking"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/common/models"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/live"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrometheusResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
ResultType string `json:"resultType"`
|
||||||
|
Result []struct {
|
||||||
|
Metric map[string]string `json:"metric"`
|
||||||
|
Value []interface{} `json:"value"` // [timestamp, value]
|
||||||
|
} `json:"result"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var queriesMetrics = []string{
|
||||||
|
"rate(container_cpu_usage_seconds_total{namespace=\"%s\"}[1m]) * 100",
|
||||||
|
"container_memory_usage_bytes{namespace=\"%s\"}",
|
||||||
|
"(container_fs_usage_bytes{namespace=\"%s\"}) / (container_fs_limit_bytes{namespace=\"%s\"}) * 100",
|
||||||
|
"DCGM_FI_DEV_GPU_UTIL{namespace=\"%s\"}",
|
||||||
|
"rate(container_fs_reads_bytes_total{namespace=\"%s\"}[1m])",
|
||||||
|
"rate(container_fs_writes_bytes_total{namespace=\"%s\"}[1m])",
|
||||||
|
"rate(container_network_receive_bytes_total{namespace=\"%s\"}[1m])",
|
||||||
|
"rate(container_network_transmit_bytes_total{namespace=\"%s\"}[1m])",
|
||||||
|
"rate(http_requests_total{namespace=\"%s\"}[1m])",
|
||||||
|
"(rate(http_requests_total{status=~\"5..\", namespace=\"%s\"}[1m]) / rate(http_requests_total{namespace=\"%s\"}[1m])) * 100",
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpClient = &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamRegistry manages cancellation of active monitoring streams by namespace.
|
||||||
|
var StreamRegistry = &streamRegistry{
|
||||||
|
streams: map[string]context.CancelFunc{},
|
||||||
|
}
|
||||||
|
|
||||||
|
type streamRegistry struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
streams map[string]context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *streamRegistry) Register(namespace string) context.Context {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if cancel, ok := r.streams[namespace]; ok {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
r.streams[namespace] = cancel
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *streamRegistry) Cancel(namespace string) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if cancel, ok := r.streams[namespace]; ok {
|
||||||
|
cancel()
|
||||||
|
delete(r.streams, namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrometheusService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrometheusService() *PrometheusService {
|
||||||
|
return &PrometheusService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrometheusService) queryPrometheus(ctx context.Context, promURL string, expr string, namespace string) models.Metric {
|
||||||
|
metric := models.Metric{Name: expr, Value: -1}
|
||||||
|
query := strings.ReplaceAll(expr, "%s", namespace)
|
||||||
|
reqURL := promURL + "/api/v1/query?query=" + url.QueryEscape(query)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
metric.Error = err
|
||||||
|
return metric
|
||||||
|
}
|
||||||
|
resp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
metric.Error = err
|
||||||
|
return metric
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
metric.Error = err
|
||||||
|
return metric
|
||||||
|
}
|
||||||
|
var result PrometheusResponse
|
||||||
|
if err = json.Unmarshal(body, &result); err != nil {
|
||||||
|
metric.Error = err
|
||||||
|
return metric
|
||||||
|
}
|
||||||
|
if len(result.Data.Result) > 0 && len(result.Data.Result[0].Value) == 2 {
|
||||||
|
metric.Value, metric.Error = strconv.ParseFloat(fmt.Sprintf("%s", result.Data.Result[0].Value[1]), 64)
|
||||||
|
}
|
||||||
|
return metric
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrometheusService) Stream(ctx context.Context, bookingID string, interval time.Duration, ws *websocket.Conn) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
max := 100
|
||||||
|
count := 0
|
||||||
|
mets := map[string][]models.MetricsSnapshot{}
|
||||||
|
bAccess := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.BOOKING), nil)
|
||||||
|
book := bAccess.LoadOne(bookingID)
|
||||||
|
if book.Err != "" {
|
||||||
|
logger.Err(fmt.Errorf("stop because of empty : %s", book.Err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive := func(e *booking.Booking) bool {
|
||||||
|
if e.ExpectedEndDate == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return time.Now().UTC().Before(*e.ExpectedEndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for isActive(book.Data.(*booking.Booking)) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
|
||||||
|
b, metrics := Call(book.Data.(*booking.Booking),
|
||||||
|
func(dc *live.LiveDatacenter, instance *resources.ComputeResourceInstance,
|
||||||
|
metrics map[string]models.MetricsSnapshot,
|
||||||
|
wg *sync.WaitGroup, mu *sync.Mutex) {
|
||||||
|
defer wg.Done()
|
||||||
|
for _, expr := range queriesMetrics {
|
||||||
|
if mm, ok := metrics[instance.Name]; !ok {
|
||||||
|
mu.Lock()
|
||||||
|
metrics[instance.Name] = models.MetricsSnapshot{
|
||||||
|
From: instance.Source,
|
||||||
|
Metrics: []models.Metric{p.queryPrometheus(ctx, dc.MonitorPath, expr, book.Data.(*booking.Booking).ExecutionsID)},
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
} else {
|
||||||
|
mu.Lock()
|
||||||
|
mm.Metrics = append(mm.Metrics, p.queryPrometheus(ctx, dc.MonitorPath, expr, book.Data.(*booking.Booking).ExecutionsID))
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
_ = b
|
||||||
|
count++
|
||||||
|
|
||||||
|
if ws != nil {
|
||||||
|
if err := ws.WriteJSON(metrics); err != nil {
|
||||||
|
logger.Err(fmt.Errorf("websocket write error: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if count < max {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bk := book.Data.(*booking.Booking)
|
||||||
|
if bk.ExecutionMetrics == nil {
|
||||||
|
bk.ExecutionMetrics = mets
|
||||||
|
} else {
|
||||||
|
for kk, vv := range mets {
|
||||||
|
bk.ExecutionMetrics[kk] = append(bk.ExecutionMetrics[kk], vv...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bk.GetAccessor(nil).UpdateOne(bk.Serialize(bk), bookingID)
|
||||||
|
mets = map[string][]models.MetricsSnapshot{}
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
153
infrastructure/monitor/vector.go
Normal file
153
infrastructure/monitor/vector.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package monitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/booking"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/common/models"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/live"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Structure métrique Vector ---
|
||||||
|
type VectorMetric struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
Labels map[string]string `json:"labels"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Service Vector ---
|
||||||
|
type VectorService struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
ExecutionMetrics map[string][]models.MetricsSnapshot // bookingID -> snapshots
|
||||||
|
sessions map[string]context.CancelFunc // optional: WS clients
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVectorService() *VectorService {
|
||||||
|
return &VectorService{
|
||||||
|
ExecutionMetrics: make(map[string][]models.MetricsSnapshot),
|
||||||
|
sessions: make(map[string]context.CancelFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Connexion à un flux Vector en WS ---
|
||||||
|
func (v *VectorService) ListenVector(ctx context.Context, b *booking.Booking, interval time.Duration, ws *websocket.Conn) error {
|
||||||
|
max := 100
|
||||||
|
count := 0
|
||||||
|
mets := map[string][]models.MetricsSnapshot{}
|
||||||
|
isActive := func() bool {
|
||||||
|
if b.ExpectedEndDate == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return time.Now().UTC().Before(*b.ExpectedEndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for isActive() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
|
||||||
|
bb, metrics := Call(b,
|
||||||
|
func(dc *live.LiveDatacenter, instance *resources.ComputeResourceInstance, metrics map[string]models.MetricsSnapshot,
|
||||||
|
wg *sync.WaitGroup, mu *sync.Mutex) {
|
||||||
|
c, _, err := websocket.DefaultDialer.Dial(dc.MonitorPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
_, msg, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("vector ws read error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var m models.Metric
|
||||||
|
if err := json.Unmarshal(msg, &m); err != nil {
|
||||||
|
log.Println("json unmarshal error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
if mm, ok := metrics[instance.Name]; !ok {
|
||||||
|
metrics[instance.Name] = models.MetricsSnapshot{
|
||||||
|
From: instance.Source,
|
||||||
|
Metrics: []models.Metric{m},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mm.Metrics = append(mm.Metrics, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Unlock()
|
||||||
|
})
|
||||||
|
_ = bb
|
||||||
|
for k, v := range metrics {
|
||||||
|
mets[k] = append(mets[k], v)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
|
if ws != nil {
|
||||||
|
if err := ws.WriteJSON(metrics); err != nil {
|
||||||
|
return fmt.Errorf("websocket write error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if count < max {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.ExecutionMetrics == nil {
|
||||||
|
b.ExecutionMetrics = mets
|
||||||
|
} else {
|
||||||
|
for kk, vv := range mets {
|
||||||
|
b.ExecutionMetrics[kk] = append(b.ExecutionMetrics[kk], vv...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.GetAccessor(nil).UpdateOne(b.Serialize(b), b.GetID())
|
||||||
|
mets = map[string][]models.MetricsSnapshot{}
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Permet d'ajouter un front WebSocket pour recevoir les metrics live ---
|
||||||
|
// --- Permet de récupérer le cache historique pour un booking ---
|
||||||
|
func (v *VectorService) GetCache(bookingID string) []models.MetricsSnapshot {
|
||||||
|
v.mu.Lock()
|
||||||
|
defer v.mu.Unlock()
|
||||||
|
snapshots := v.ExecutionMetrics[bookingID]
|
||||||
|
return snapshots
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Exemple d'intégration avec un booking ---
|
||||||
|
func (v *VectorService) Stream(ctx context.Context, bookingID string, interval time.Duration, ws *websocket.Conn) {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
go func() {
|
||||||
|
bAccess := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.BOOKING), nil)
|
||||||
|
book := bAccess.LoadOne(bookingID)
|
||||||
|
if book.Err != "" {
|
||||||
|
logger.Err(fmt.Errorf("stop because of empty : %s", book.Err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := book.ToBookings()
|
||||||
|
if b == nil {
|
||||||
|
logger.Err(fmt.Errorf("stop because of empty is not a booking"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := v.ListenVector(ctx, b, interval, ws); err != nil {
|
||||||
|
log.Printf("Vector listen error for booking %s: %v\n", b.GetID(), err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
25
infrastructure/namespace.go
Normal file
25
infrastructure/namespace.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package infrastructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"oc-datacenter/conf"
|
||||||
|
|
||||||
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
|
"cloud.o-forge.io/core/oc-lib/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Kubernetes namespace helper
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func CreateNamespace(ns string) error {
|
||||||
|
logger := oclib.GetLogger()
|
||||||
|
serv, err := tools.NewKubernetesService(
|
||||||
|
conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort, conf.GetConfig().KubeCA,
|
||||||
|
conf.GetConfig().KubeCert, conf.GetConfig().KubeData)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Msg("CreateNamespace: failed to init k8s service: " + err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return serv.ProvisionExecutionNamespace(context.Background(), ns)
|
||||||
|
}
|
||||||
167
infrastructure/nats.go
Normal file
167
infrastructure/nats.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package infrastructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"oc-datacenter/infrastructure/minio"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"cloud.o-forge.io/core/oc-lib/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// roleWaiters maps executionID → channel expecting the role-assignment message from OC discovery.
|
||||||
|
var roleWaiters sync.Map
|
||||||
|
|
||||||
|
// ArgoKubeEvent carries the peer-routing metadata for a resource provisioning event.
|
||||||
|
//
|
||||||
|
// When MinioID is non-empty the event concerns Minio credential provisioning;
|
||||||
|
// otherwise it concerns Admiralty kubeconfig provisioning.
|
||||||
|
type ArgoKubeEvent struct {
|
||||||
|
ExecutionsID string `json:"executions_id"`
|
||||||
|
DestPeerID string `json:"dest_peer_id"`
|
||||||
|
Type tools.DataType `json:"data_type"`
|
||||||
|
SourcePeerID string `json:"source_peer_id"`
|
||||||
|
MinioID string `json:"minio_id,omitempty"`
|
||||||
|
// OriginID is the peer that initiated the request; the PB_CONSIDERS
|
||||||
|
// response is routed back to this peer once provisioning completes.
|
||||||
|
OriginID string `json:"origin_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenNATS starts all NATS subscriptions for the infrastructure layer.
|
||||||
|
// Must be launched in a goroutine from main.
|
||||||
|
func ListenNATS() {
|
||||||
|
tools.NewNATSCaller().ListenNats(map[tools.NATSMethod]func(tools.NATSResponse){
|
||||||
|
// ─── ARGO_KUBE_EVENT ────────────────────────────────────────────────────────
|
||||||
|
// Triggered by oc-discovery to notify this peer of a provisioning task.
|
||||||
|
// Dispatches to Admiralty or Minio based on whether MinioID is set.
|
||||||
|
tools.ARGO_KUBE_EVENT: func(resp tools.NATSResponse) {
|
||||||
|
argo := &ArgoKubeEvent{}
|
||||||
|
if err := json.Unmarshal(resp.Payload, argo); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if argo.Type == tools.STORAGE_RESOURCE {
|
||||||
|
fmt.Println("DETECT STORAGE ARGO_KUBE_EVENT")
|
||||||
|
// ── Minio credential provisioning ──────────────────────────────
|
||||||
|
setter := minio.NewMinioSetter(argo.ExecutionsID, argo.MinioID)
|
||||||
|
if argo.SourcePeerID == argo.DestPeerID {
|
||||||
|
fmt.Println("CONFIG MYSELF")
|
||||||
|
err := CreateNamespace(argo.ExecutionsID)
|
||||||
|
fmt.Println("NS", err)
|
||||||
|
// Same peer: source creates credentials and immediately stores them.
|
||||||
|
go setter.InitializeAsSource(context.Background(), argo.SourcePeerID, argo.DestPeerID, argo.OriginID)
|
||||||
|
} else {
|
||||||
|
// Different peers: publish Phase-1 PB_MINIO_CONFIG (Access == "")
|
||||||
|
// so oc-discovery routes the role-assignment to the Minio host.
|
||||||
|
phase1 := minio.MinioCredentialEvent{
|
||||||
|
ExecutionsID: argo.ExecutionsID,
|
||||||
|
MinioID: argo.MinioID,
|
||||||
|
SourcePeerID: argo.SourcePeerID,
|
||||||
|
DestPeerID: argo.DestPeerID,
|
||||||
|
OriginID: argo.OriginID,
|
||||||
|
}
|
||||||
|
if b, err := json.Marshal(phase1); err == nil {
|
||||||
|
if b2, err := json.Marshal(&tools.PropalgationMessage{
|
||||||
|
Payload: b,
|
||||||
|
Action: tools.PB_MINIO_CONFIG,
|
||||||
|
}); err == nil {
|
||||||
|
fmt.Println("CONFIG THEM")
|
||||||
|
go tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||||
|
FromApp: "oc-datacenter",
|
||||||
|
Datatype: -1,
|
||||||
|
User: resp.User,
|
||||||
|
Method: int(tools.PROPALGATION_EVENT),
|
||||||
|
Payload: b2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("DETECT COMPUTE ARGO_KUBE_EVENT")
|
||||||
|
// ── Admiralty kubeconfig provisioning (existing behaviour) ──────
|
||||||
|
if argo.SourcePeerID == argo.DestPeerID {
|
||||||
|
fmt.Println("CONFIG MYSELF")
|
||||||
|
err := CreateNamespace(argo.ExecutionsID)
|
||||||
|
fmt.Println("NS", err)
|
||||||
|
go NewAdmiraltySetter(argo.ExecutionsID).InitializeAsSource(
|
||||||
|
context.Background(), argo.SourcePeerID, argo.DestPeerID, argo.OriginID)
|
||||||
|
} else if b, err := json.Marshal(argo); err == nil {
|
||||||
|
if b2, err := json.Marshal(&tools.PropalgationMessage{
|
||||||
|
Payload: b,
|
||||||
|
Action: tools.PB_ADMIRALTY_CONFIG,
|
||||||
|
}); err == nil {
|
||||||
|
fmt.Println("CONFIG THEM")
|
||||||
|
go tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||||
|
FromApp: "oc-datacenter",
|
||||||
|
Datatype: -1,
|
||||||
|
User: resp.User,
|
||||||
|
Method: int(tools.PROPALGATION_EVENT),
|
||||||
|
Payload: b2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── ADMIRALTY_CONFIG_EVENT ─────────────────────────────────────────────────
|
||||||
|
// Forwarded by oc-discovery after receiving via libp2p ProtocolAdmiraltyConfigResource.
|
||||||
|
// Payload is a KubeconfigEvent (phase discriminated by Kubeconfig presence).
|
||||||
|
tools.ADMIRALTY_CONFIG_EVENT: func(resp tools.NATSResponse) {
|
||||||
|
|
||||||
|
kubeconfigEvent := KubeconfigEvent{}
|
||||||
|
if err := json.Unmarshal(resp.Payload, &kubeconfigEvent); err == nil {
|
||||||
|
if kubeconfigEvent.Kubeconfig != "" {
|
||||||
|
// Phase 2: kubeconfig present → this peer is the TARGET (scheduler).
|
||||||
|
NewAdmiraltySetter(kubeconfigEvent.ExecutionsID).InitializeAsTarget(
|
||||||
|
context.Background(), kubeconfigEvent)
|
||||||
|
} else {
|
||||||
|
err := CreateNamespace(kubeconfigEvent.ExecutionsID)
|
||||||
|
fmt.Println("NS", err)
|
||||||
|
// Phase 1: no kubeconfig → this peer is the SOURCE (compute).
|
||||||
|
NewAdmiraltySetter(kubeconfigEvent.ExecutionsID).InitializeAsSource(
|
||||||
|
context.Background(), kubeconfigEvent.SourcePeerID, kubeconfigEvent.DestPeerID, kubeconfigEvent.OriginID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── MINIO_CONFIG_EVENT ──────────────────────────────────────────────────────
|
||||||
|
// Forwarded by oc-discovery after receiving via libp2p ProtocolMinioConfigResource.
|
||||||
|
// Payload is a MinioCredentialEvent (phase discriminated by Access presence).
|
||||||
|
tools.MINIO_CONFIG_EVENT: func(resp tools.NATSResponse) {
|
||||||
|
minioEvent := minio.MinioCredentialEvent{}
|
||||||
|
if err := json.Unmarshal(resp.Payload, &minioEvent); err == nil {
|
||||||
|
if minioEvent.Access != "" {
|
||||||
|
// Phase 2: credentials present → this peer is the TARGET (compute).
|
||||||
|
minio.NewMinioSetter(minioEvent.ExecutionsID, minioEvent.MinioID).InitializeAsTarget(
|
||||||
|
context.Background(), minioEvent)
|
||||||
|
} else {
|
||||||
|
err := CreateNamespace(minioEvent.ExecutionsID)
|
||||||
|
fmt.Println("NS", err)
|
||||||
|
// Phase 1: no credentials → this peer is the SOURCE (Minio host).
|
||||||
|
minio.NewMinioSetter(minioEvent.ExecutionsID, minioEvent.MinioID).InitializeAsSource(
|
||||||
|
context.Background(), minioEvent.SourcePeerID, minioEvent.DestPeerID, minioEvent.OriginID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── REMOVE_RESOURCE ────────────────────────────────────────────────────────
|
||||||
|
// Routed by oc-discovery via ProtocolDeleteResource for datacenter teardown.
|
||||||
|
// Only STORAGE_RESOURCE and COMPUTE_RESOURCE deletions are handled here.
|
||||||
|
tools.REMOVE_RESOURCE: func(resp tools.NATSResponse) {
|
||||||
|
switch resp.Datatype {
|
||||||
|
case tools.STORAGE_RESOURCE:
|
||||||
|
deleteEvent := minio.MinioDeleteEvent{}
|
||||||
|
if err := json.Unmarshal(resp.Payload, &deleteEvent); err == nil && deleteEvent.ExecutionsID != "" {
|
||||||
|
go minio.NewMinioSetter(deleteEvent.ExecutionsID, deleteEvent.MinioID).
|
||||||
|
TeardownAsSource(context.Background(), deleteEvent)
|
||||||
|
}
|
||||||
|
case tools.COMPUTE_RESOURCE:
|
||||||
|
argo := &ArgoKubeEvent{}
|
||||||
|
if err := json.Unmarshal(resp.Payload, argo); err == nil && argo.ExecutionsID != "" {
|
||||||
|
go NewAdmiraltySetter(argo.ExecutionsID).TeardownAsSource(context.Background())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
package infrastructure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/dbs"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/models/booking"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/models/common/models"
|
|
||||||
"cloud.o-forge.io/core/oc-lib/models/live"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MetricsSnapshot struct {
|
|
||||||
From string `json:"origin"`
|
|
||||||
Metrics []Metric `json:"metrics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Metric struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Value float64 `json:"value"`
|
|
||||||
Error error `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrometheusResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Data struct {
|
|
||||||
ResultType string `json:"resultType"`
|
|
||||||
Result []struct {
|
|
||||||
Metric map[string]string `json:"metric"`
|
|
||||||
Value []interface{} `json:"value"` // [timestamp, value]
|
|
||||||
} `json:"result"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var queriesMetrics = []string{
|
|
||||||
"rate(container_cpu_usage_seconds_total{namespace=\"%s\"}[1m]) * 100",
|
|
||||||
"container_memory_usage_bytes{namespace=\"%s\"}",
|
|
||||||
"(container_fs_usage_bytes{namespace=\"%s\"}) / (container_fs_limit_bytes{namespace=\"%s\"}) * 100",
|
|
||||||
"DCGM_FI_DEV_GPU_UTIL{namespace=\"%s\"}",
|
|
||||||
// "system_load_average",
|
|
||||||
"rate(container_fs_reads_bytes_total{namespace=\"%s\"}[1m])",
|
|
||||||
"rate(container_fs_writes_bytes_total{namespace=\"%s\"}[1m])",
|
|
||||||
"rate(container_network_receive_bytes_total{namespace=\"%s\"}[1m])",
|
|
||||||
"rate(container_network_transmit_bytes_total{namespace=\"%s\"}[1m])",
|
|
||||||
// "system_network_latency_ms",
|
|
||||||
"rate(http_requests_total{namespace=\"%s\"}[1m])",
|
|
||||||
"(rate(http_requests_total{status=~\"5..\", namespace=\"%s\"}[1m]) / rate(http_requests_total{namespace=\"%s\"}[1m])) * 100",
|
|
||||||
// "app_mean_time_to_repair_seconds",
|
|
||||||
// "app_mean_time_between_failure_seconds",
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrometheusService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrometheusService() *PrometheusService {
|
|
||||||
return &PrometheusService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrometheusService) queryPrometheus(promURL string, expr string, namespace string) models.Metric {
|
|
||||||
metric := models.Metric{Name: expr, Value: -1}
|
|
||||||
resp, err := http.Get(promURL + "/api/v1/query?query=" + url.QueryEscape(fmt.Sprintf(expr, namespace)))
|
|
||||||
if err != nil {
|
|
||||||
metric.Error = err
|
|
||||||
} else {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if body, err := io.ReadAll(resp.Body); err == nil {
|
|
||||||
var result PrometheusResponse
|
|
||||||
if err = json.Unmarshal(body, &result); err == nil && len(result.Data.Result) > 0 && len(result.Data.Result[0].Value) == 2 {
|
|
||||||
metric.Value, metric.Error = strconv.ParseFloat(fmt.Sprintf("%s", result.Data.Result[0].Value[1]), 64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrometheusService) Call(book *booking.Booking, user string, peerID string, groups []string) (*booking.Booking, map[string]models.MetricsSnapshot) {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
metrics := map[string]models.MetricsSnapshot{}
|
|
||||||
// get all booking... from executions_id == namespace typed datacenter.
|
|
||||||
|
|
||||||
cUAccess := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_DATACENTER), user, peerID, groups, nil)
|
|
||||||
cRAccess := oclib.NewRequest(oclib.LibDataEnum(oclib.COMPUTE_RESOURCE), user, peerID, groups, nil)
|
|
||||||
|
|
||||||
rr := cRAccess.LoadOne(book.ResourceID)
|
|
||||||
if rr.Err != "" {
|
|
||||||
fmt.Errorf("can't proceed because of unfound resource %s : %s", book.ResourceID, rr.Err)
|
|
||||||
return book, metrics
|
|
||||||
}
|
|
||||||
computeRes := rr.ToComputeResource()
|
|
||||||
for _, instance := range computeRes.Instances {
|
|
||||||
res := cUAccess.Search(&dbs.Filters{
|
|
||||||
And: map[string][]dbs.Filter{
|
|
||||||
"source": {{Operator: dbs.EQUAL.String(), Value: instance.Source}},
|
|
||||||
"abstractlive.resources_id": {{Operator: dbs.EQUAL.String(), Value: computeRes.GetID()}},
|
|
||||||
},
|
|
||||||
}, "", false)
|
|
||||||
if res.Err != "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, r := range res.Data {
|
|
||||||
// TODO watch out ... to not exec on an absent datacenter...
|
|
||||||
if r.(*live.LiveDatacenter).MonitorPath == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
snapshot := models.MetricsSnapshot{From: instance.Source, Metrics: []models.Metric{}}
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for _, expr := range queriesMetrics {
|
|
||||||
snapshot.Metrics = append(snapshot.Metrics,
|
|
||||||
p.queryPrometheus(r.(*live.LiveDatacenter).MonitorPath, expr, book.ExecutionsID))
|
|
||||||
}
|
|
||||||
metrics[instance.Name] = snapshot
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
return book, metrics
|
|
||||||
}
|
|
||||||
|
|
||||||
var LockKill = &sync.Mutex{}
|
|
||||||
|
|
||||||
// TODO kill procedure
|
|
||||||
func (p *PrometheusService) Stream(bookingID string, interval time.Duration, user string, peerID string, groups []string, websocket *websocket.Conn) {
|
|
||||||
|
|
||||||
max := 100
|
|
||||||
bookIDS := []string{}
|
|
||||||
mets := map[string][]models.MetricsSnapshot{}
|
|
||||||
bAccess := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil)
|
|
||||||
book := bAccess.LoadOne(bookingID)
|
|
||||||
if book.Err != "" {
|
|
||||||
fmt.Errorf("stop because of empty : %s", book.Err)
|
|
||||||
}
|
|
||||||
f := func(e *booking.Booking) bool {
|
|
||||||
if e.ExpectedEndDate == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return time.Now().Before(*e.ExpectedEndDate)
|
|
||||||
}
|
|
||||||
for f(book.Data.(*booking.Booking)) {
|
|
||||||
if slices.Contains(Kill, book.Data.(*booking.Booking).ExecutionsID) {
|
|
||||||
newKill := []string{}
|
|
||||||
for _, k := range Kill {
|
|
||||||
if k != book.Data.(*booking.Booking).ExecutionsID {
|
|
||||||
newKill = append(newKill, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LockKill.Lock()
|
|
||||||
Kill = newKill
|
|
||||||
LockKill.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
book, metrics := p.Call(book.Data.(*booking.Booking), user, peerID, groups)
|
|
||||||
for k, v := range metrics {
|
|
||||||
if me, ok := mets[k]; !ok {
|
|
||||||
mets[k] = []models.MetricsSnapshot{v}
|
|
||||||
} else {
|
|
||||||
me = append(me, v)
|
|
||||||
mets[k] = me
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bookIDS = append(bookIDS, bookingID)
|
|
||||||
if websocket != nil {
|
|
||||||
(*websocket).WriteJSON(metrics)
|
|
||||||
}
|
|
||||||
if len(bookIDS) != max {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if book.ExecutionMetrics == nil {
|
|
||||||
book.ExecutionMetrics = mets
|
|
||||||
} else {
|
|
||||||
for kk, vv := range mets {
|
|
||||||
if em, ok := book.ExecutionMetrics[kk]; !ok {
|
|
||||||
book.ExecutionMetrics[kk] = vv
|
|
||||||
} else {
|
|
||||||
em = append(em, vv...)
|
|
||||||
book.ExecutionMetrics[kk] = em
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
book.GetAccessor(nil).UpdateOne(book, bookingID)
|
|
||||||
bookIDS = []string{}
|
|
||||||
|
|
||||||
}()
|
|
||||||
time.Sleep(interval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var Kill = []string{}
|
|
||||||
|
|
||||||
// should add a datacenter... under juridiction... of opencloud...
|
|
||||||
41
main.go
41
main.go
@@ -3,27 +3,23 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"oc-datacenter/conf"
|
"oc-datacenter/conf"
|
||||||
|
"oc-datacenter/infrastructure"
|
||||||
_ "oc-datacenter/routers"
|
_ "oc-datacenter/routers"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
oclib "cloud.o-forge.io/core/oc-lib"
|
oclib "cloud.o-forge.io/core/oc-lib"
|
||||||
"cloud.o-forge.io/core/oc-lib/tools"
|
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
"github.com/beego/beego/v2/server/web/filter/cors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const appname = "oc-datacenter"
|
const appname = "oc-datacenter"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Init the oc-lib
|
|
||||||
oclib.Init(appname)
|
|
||||||
|
|
||||||
// Load the right config file
|
// Load the right config file
|
||||||
o := oclib.GetConfLoader()
|
o := oclib.GetConfLoader(appname)
|
||||||
conf.GetConfig().Mode = o.GetStringDefault("MODE", "kubernetes")
|
conf.GetConfig().Mode = o.GetStringDefault("MODE", "kubernetes")
|
||||||
conf.GetConfig().KubeHost = o.GetStringDefault("KUBERNETES_SERVICE_HOST", os.Getenv("KUBERNETES_SERVICE_HOST"))
|
conf.GetConfig().KubeHost = o.GetStringDefault("KUBERNETES_SERVICE_HOST", os.Getenv("KUBERNETES_SERVICE_HOST"))
|
||||||
conf.GetConfig().KubePort = o.GetStringDefault("KUBERNETES_SERVICE_PORT", "6443")
|
conf.GetConfig().KubePort = o.GetStringDefault("KUBERNETES_SERVICE_PORT", "6443")
|
||||||
|
|
||||||
sDec, err := base64.StdEncoding.DecodeString(o.GetStringDefault("KUBE_CA", ""))
|
sDec, err := base64.StdEncoding.DecodeString(o.GetStringDefault("KUBE_CA", ""))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conf.GetConfig().KubeCA = string(sDec)
|
conf.GetConfig().KubeCA = string(sDec)
|
||||||
@@ -37,32 +33,13 @@ func main() {
|
|||||||
conf.GetConfig().KubeData = string(sDec)
|
conf.GetConfig().KubeData = string(sDec)
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.GetConfig().MinioRootKey = o.GetStringDefault("MINIO_ADMIN_ACCESS","")
|
conf.GetConfig().MonitorMode = o.GetStringDefault("MONITOR_MODE", "prometheus")
|
||||||
conf.GetConfig().MinioRootSecret = o.GetStringDefault("MINIO_ADMIN_SECRET","")
|
conf.GetConfig().MinioRootKey = o.GetStringDefault("MINIO_ADMIN_ACCESS", "")
|
||||||
|
conf.GetConfig().MinioRootSecret = o.GetStringDefault("MINIO_ADMIN_SECRET", "")
|
||||||
|
oclib.InitAPI(appname)
|
||||||
|
|
||||||
|
go infrastructure.ListenNATS()
|
||||||
|
go infrastructure.WatchBookings()
|
||||||
|
|
||||||
// feed the library with the loaded config
|
|
||||||
oclib.SetConfig(
|
|
||||||
o.GetStringDefault("MONGO_URL", "mongodb://127.0.0.1:27017"),
|
|
||||||
o.GetStringDefault("MONGO_DATABASE", "DC_myDC"),
|
|
||||||
o.GetStringDefault("NATS_URL", "nats://localhost:4222"),
|
|
||||||
o.GetStringDefault("LOKI_URL", ""),
|
|
||||||
o.GetStringDefault("LOG_LEVEL", "info"),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Beego init
|
|
||||||
beego.BConfig.AppName = appname
|
|
||||||
beego.BConfig.Listen.HTTPPort = o.GetIntDefault("port", 8080)
|
|
||||||
beego.BConfig.WebConfig.DirectoryIndex = true
|
|
||||||
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
|
|
||||||
api := &tools.API{}
|
|
||||||
api.Discovered(beego.BeeApp.Handlers.GetAllControllerInfo())
|
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
|
|
||||||
AllowAllOrigins: true,
|
|
||||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
||||||
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
|
|
||||||
ExposeHeaders: []string{"Content-Length", "Content-Type"},
|
|
||||||
AllowCredentials: true,
|
|
||||||
}))
|
|
||||||
beego.Run()
|
beego.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,6 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
|
||||||
beego.ControllerComments{
|
|
||||||
Method: "GetAdmiraltyKubeconfig",
|
|
||||||
Router: `/kubeconfig/:execution`,
|
|
||||||
AllowHTTPMethods: []string{"get"},
|
|
||||||
MethodParams: param.Make(),
|
|
||||||
Filters: nil,
|
|
||||||
Params: nil})
|
|
||||||
|
|
||||||
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
|
||||||
beego.ControllerComments{
|
|
||||||
Method: "GetNodeReady",
|
|
||||||
Router: `/node/:execution/:peer`,
|
|
||||||
AllowHTTPMethods: []string{"get"},
|
|
||||||
MethodParams: param.Make(),
|
|
||||||
Filters: nil,
|
|
||||||
Params: nil})
|
|
||||||
|
|
||||||
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
||||||
beego.ControllerComments{
|
beego.ControllerComments{
|
||||||
Method: "GetKubeSecret",
|
Method: "GetKubeSecret",
|
||||||
@@ -34,33 +16,6 @@ func init() {
|
|||||||
Filters: nil,
|
Filters: nil,
|
||||||
Params: nil})
|
Params: nil})
|
||||||
|
|
||||||
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
|
||||||
beego.ControllerComments{
|
|
||||||
Method: "CreateKubeSecret",
|
|
||||||
Router: `/secret/:execution/:peer`,
|
|
||||||
AllowHTTPMethods: []string{"post"},
|
|
||||||
MethodParams: param.Make(),
|
|
||||||
Filters: nil,
|
|
||||||
Params: nil})
|
|
||||||
|
|
||||||
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
|
||||||
beego.ControllerComments{
|
|
||||||
Method: "CreateAdmiraltySource",
|
|
||||||
Router: `/source/:execution`,
|
|
||||||
AllowHTTPMethods: []string{"post"},
|
|
||||||
MethodParams: param.Make(),
|
|
||||||
Filters: nil,
|
|
||||||
Params: nil})
|
|
||||||
|
|
||||||
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
|
||||||
beego.ControllerComments{
|
|
||||||
Method: "CreateAdmiraltyTarget",
|
|
||||||
Router: `/target/:execution/:peer`,
|
|
||||||
AllowHTTPMethods: []string{"post"},
|
|
||||||
MethodParams: param.Make(),
|
|
||||||
Filters: nil,
|
|
||||||
Params: nil})
|
|
||||||
|
|
||||||
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"],
|
||||||
beego.ControllerComments{
|
beego.ControllerComments{
|
||||||
Method: "GetAllTargets",
|
Method: "GetAllTargets",
|
||||||
@@ -214,6 +169,15 @@ func init() {
|
|||||||
Filters: nil,
|
Filters: nil,
|
||||||
Params: nil})
|
Params: nil})
|
||||||
|
|
||||||
|
beego.GlobalControllerRouter["oc-datacenter/controllers:VectorController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:VectorController"],
|
||||||
|
beego.ControllerComments{
|
||||||
|
Method: "Receive",
|
||||||
|
Router: `/`,
|
||||||
|
AllowHTTPMethods: []string{"post"},
|
||||||
|
MethodParams: param.Make(),
|
||||||
|
Filters: nil,
|
||||||
|
Params: nil})
|
||||||
|
|
||||||
beego.GlobalControllerRouter["oc-datacenter/controllers:VersionController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:VersionController"],
|
beego.GlobalControllerRouter["oc-datacenter/controllers:VersionController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:VersionController"],
|
||||||
beego.ControllerComments{
|
beego.ControllerComments{
|
||||||
Method: "GetAll",
|
Method: "GetAll",
|
||||||
|
|||||||
@@ -24,26 +24,11 @@ func init() {
|
|||||||
&controllers.SessionController{},
|
&controllers.SessionController{},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
beego.NSNamespace("/booking",
|
|
||||||
beego.NSInclude(
|
|
||||||
&controllers.BookingController{},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
beego.NSNamespace("/version",
|
beego.NSNamespace("/version",
|
||||||
beego.NSInclude(
|
beego.NSInclude(
|
||||||
&controllers.VersionController{},
|
&controllers.VersionController{},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
beego.NSNamespace("/admiralty",
|
|
||||||
beego.NSInclude(
|
|
||||||
&controllers.AdmiraltyController{},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
beego.NSNamespace("/minio",
|
|
||||||
beego.NSInclude(
|
|
||||||
&controllers.MinioController{},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
beego.AddNamespace(ns)
|
beego.AddNamespace(ns)
|
||||||
|
|||||||
BIN
swagger/favicon-16x16.png
Normal file
BIN
swagger/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 665 B |
BIN
swagger/favicon-32x32.png
Normal file
BIN
swagger/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 628 B |
60
swagger/index.html
Normal file
60
swagger/index.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<!-- HTML for static distribution bundle build -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Swagger UI</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||||
|
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||||
|
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||||
|
<style>
|
||||||
|
html
|
||||||
|
{
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: -moz-scrollbars-vertical;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after
|
||||||
|
{
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
{
|
||||||
|
margin:0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
|
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||||
|
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
// Begin Swagger UI call region
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
url: "https://petstore.swagger.io/v2/swagger.json",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
layout: "StandaloneLayout"
|
||||||
|
});
|
||||||
|
// End Swagger UI call region
|
||||||
|
|
||||||
|
window.ui = ui;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
79
swagger/oauth2-redirect.html
Normal file
79
swagger/oauth2-redirect.html
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<title>Swagger UI: OAuth2 Redirect</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
function run () {
|
||||||
|
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||||
|
var sentState = oauth2.state;
|
||||||
|
var redirectUrl = oauth2.redirectUrl;
|
||||||
|
var isValid, qp, arr;
|
||||||
|
|
||||||
|
if (/code|token|error/.test(window.location.hash)) {
|
||||||
|
qp = window.location.hash.substring(1);
|
||||||
|
} else {
|
||||||
|
qp = location.search.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr = qp.split("&");
|
||||||
|
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||||
|
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||||
|
function (key, value) {
|
||||||
|
return key === "" ? value : decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
) : {};
|
||||||
|
|
||||||
|
isValid = qp.state === sentState;
|
||||||
|
|
||||||
|
if ((
|
||||||
|
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||||
|
) && !oauth2.auth.code) {
|
||||||
|
if (!isValid) {
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "warning",
|
||||||
|
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qp.code) {
|
||||||
|
delete oauth2.state;
|
||||||
|
oauth2.auth.code = qp.code;
|
||||||
|
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||||
|
} else {
|
||||||
|
let oauthErrorMsg;
|
||||||
|
if (qp.error) {
|
||||||
|
oauthErrorMsg = "["+qp.error+"]: " +
|
||||||
|
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||||
|
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "error",
|
||||||
|
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState !== 'loading') {
|
||||||
|
run();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
swagger/swagger-ui-bundle.js
Normal file
3
swagger/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
swagger/swagger-ui-bundle.js.map
Normal file
1
swagger/swagger-ui-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
2
swagger/swagger-ui-es-bundle-core.js
Normal file
2
swagger/swagger-ui-es-bundle-core.js
Normal file
File diff suppressed because one or more lines are too long
3
swagger/swagger-ui-es-bundle.js
Normal file
3
swagger/swagger-ui-es-bundle.js
Normal file
File diff suppressed because one or more lines are too long
3
swagger/swagger-ui-standalone-preset.js
Normal file
3
swagger/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
1
swagger/swagger-ui-standalone-preset.js.map
Normal file
1
swagger/swagger-ui-standalone-preset.js.map
Normal file
File diff suppressed because one or more lines are too long
4
swagger/swagger-ui.css
Normal file
4
swagger/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
1
swagger/swagger-ui.css.map
Normal file
1
swagger/swagger-ui.css.map
Normal file
File diff suppressed because one or more lines are too long
2
swagger/swagger-ui.js
Normal file
2
swagger/swagger-ui.js
Normal file
File diff suppressed because one or more lines are too long
1
swagger/swagger-ui.js.map
Normal file
1
swagger/swagger-ui.js.map
Normal file
File diff suppressed because one or more lines are too long
555
swagger/swagger.json
Normal file
555
swagger/swagger.json
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "oc-datacenter",
|
||||||
|
"description": "Monitor owned datacenter activity\n",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"termsOfService": "http://cloud.o-forge.io/",
|
||||||
|
"contact": {
|
||||||
|
"email": "admin@o-cloud.io"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "AGPL",
|
||||||
|
"url": "https://www.gnu.org/licenses/agpl-3.0.html"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"basePath": "/oc/",
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"oc-datacenter/controllersDatacenterController"
|
||||||
|
],
|
||||||
|
"description": "find booking by id\n\u003cbr\u003e",
|
||||||
|
"operationId": "DatacenterController.GetAll",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "is_draft",
|
||||||
|
"description": "draft wished",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{booking} models.booking"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admiralty/kubeconfig/{execution}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"admiralty"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "execution",
|
||||||
|
"description": "execution id of the workflow",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admiralty/node/{execution}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"admiralty"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "execution",
|
||||||
|
"description": "execution id of the workflow",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admiralty/secret/{execution}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"admiralty"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "execution",
|
||||||
|
"description": "execution id of the workflow",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"admiralty"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "execution",
|
||||||
|
"description": "execution id of the workflow",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "kubeconfig",
|
||||||
|
"description": "Kubeconfig to use when creating secret",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controllers.RemoteKubeconfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admiralty/source/{execution}": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"admiralty"
|
||||||
|
],
|
||||||
|
"description": "Create an Admiralty Source on remote cluster\n\u003cbr\u003e",
|
||||||
|
"operationId": "AdmiraltyController.CreateSource",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "execution",
|
||||||
|
"description": "execution id of the workflow",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admiralty/target/{execution}": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"admiralty"
|
||||||
|
],
|
||||||
|
"description": "Create an Admiralty Target in the namespace associated to the executionID\n\u003cbr\u003e",
|
||||||
|
"operationId": "AdmiraltyController.CreateAdmiraltyTarget",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "execution",
|
||||||
|
"description": "execution id of the workflow",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admiralty/targets": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"admiralty"
|
||||||
|
],
|
||||||
|
"description": "find all Admiralty Target\n\u003cbr\u003e",
|
||||||
|
"operationId": "AdmiraltyController.GetAllTargets",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admiralty/targets/{execution}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"admiralty"
|
||||||
|
],
|
||||||
|
"description": "find one Admiralty Target\n\u003cbr\u003e",
|
||||||
|
"operationId": "AdmiraltyController.GetOneTarget",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"description": "the name of the target to get",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/booking/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"booking"
|
||||||
|
],
|
||||||
|
"description": "find booking by id\n\u003cbr\u003e",
|
||||||
|
"operationId": "BookingController.GetAll",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "is_draft",
|
||||||
|
"description": "draft wished",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{booking} models.booking"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"booking"
|
||||||
|
],
|
||||||
|
"description": "create booking\n\u003cbr\u003e",
|
||||||
|
"operationId": "BookingController.Post.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "booking",
|
||||||
|
"description": "the booking you want to post",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "is_draft",
|
||||||
|
"description": "draft wished",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/booking/check/{id}/{start_date}/{end_date}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"booking"
|
||||||
|
],
|
||||||
|
"description": "check booking\n\u003cbr\u003e",
|
||||||
|
"operationId": "BookingController.Check",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"description": "id of the datacenter",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "start_date",
|
||||||
|
"description": "2006-01-02T15:04:05",
|
||||||
|
"type": "string",
|
||||||
|
"default": "the booking start date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "end_date",
|
||||||
|
"description": "2006-01-02T15:04:05",
|
||||||
|
"type": "string",
|
||||||
|
"default": "the booking end date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "is_draft",
|
||||||
|
"description": "draft wished",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/booking/search/execution/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"booking"
|
||||||
|
],
|
||||||
|
"description": "search bookings by execution\n\u003cbr\u003e",
|
||||||
|
"operationId": "BookingController.Search",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"description": "id execution",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "is_draft",
|
||||||
|
"description": "draft wished",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{workspace} models.workspace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/booking/search/{start_date}/{end_date}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"booking"
|
||||||
|
],
|
||||||
|
"description": "search bookings\n\u003cbr\u003e",
|
||||||
|
"operationId": "BookingController.Search",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "start_date",
|
||||||
|
"description": "the word search you want to get",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "end_date",
|
||||||
|
"description": "the word search you want to get",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "is_draft",
|
||||||
|
"description": "draft wished",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{workspace} models.workspace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/booking/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"booking"
|
||||||
|
],
|
||||||
|
"description": "find booking by id\n\u003cbr\u003e",
|
||||||
|
"operationId": "BookingController.Get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"description": "the id you want to get",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{booking} models.booking"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"booking"
|
||||||
|
],
|
||||||
|
"description": "create computes\n\u003cbr\u003e",
|
||||||
|
"operationId": "BookingController.Update",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"description": "the compute id you want to get",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "body",
|
||||||
|
"description": "The compute content",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.compute"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{compute} models.compute"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/session/token/{id}/{duration}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"session"
|
||||||
|
],
|
||||||
|
"description": "find booking by id\n\u003cbr\u003e",
|
||||||
|
"operationId": "SessionController.GetToken",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"description": "id of the datacenter",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "duration",
|
||||||
|
"description": "duration of the token",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{booking} models.booking"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/version/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"version"
|
||||||
|
],
|
||||||
|
"description": "get version\n\u003cbr\u003e",
|
||||||
|
"operationId": "VersionController.GetAll",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/version/status": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"version"
|
||||||
|
],
|
||||||
|
"description": "get status\n\u003cbr\u003e",
|
||||||
|
"operationId": "VersionController.Status",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"oc-datacenter/controllersDatacenterController"
|
||||||
|
],
|
||||||
|
"description": "find booking by id\n\u003cbr\u003e",
|
||||||
|
"operationId": "DatacenterController.Get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"description": "the id you want to get",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "is_draft",
|
||||||
|
"description": "draft wished",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{booking} models.booking"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"controllers.RemoteKubeconfig": {
|
||||||
|
"title": "RemoteKubeconfig",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Data": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.compute": {
|
||||||
|
"title": "compute",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"models.object": {
|
||||||
|
"title": "object",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "oc-datacenter/controllersDatacenterController",
|
||||||
|
"description": "Operations about workspace\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "booking",
|
||||||
|
"description": "Operations about workspace\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "version",
|
||||||
|
"description": "VersionController operations for Version\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "admiralty",
|
||||||
|
"description": "Operations about the admiralty objects of the datacenter\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
396
swagger/swagger.yml
Normal file
396
swagger/swagger.yml
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
swagger: "2.0"
|
||||||
|
info:
|
||||||
|
title: oc-datacenter
|
||||||
|
description: |
|
||||||
|
Monitor owned datacenter activity
|
||||||
|
version: 1.0.0
|
||||||
|
termsOfService: http://cloud.o-forge.io/
|
||||||
|
contact:
|
||||||
|
email: admin@o-cloud.io
|
||||||
|
license:
|
||||||
|
name: AGPL
|
||||||
|
url: https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
basePath: /oc/
|
||||||
|
paths:
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- oc-datacenter/controllersDatacenterController
|
||||||
|
description: |-
|
||||||
|
find booking by id
|
||||||
|
<br>
|
||||||
|
operationId: DatacenterController.GetAll
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: is_draft
|
||||||
|
description: draft wished
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{booking} models.booking'
|
||||||
|
/{id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- oc-datacenter/controllersDatacenterController
|
||||||
|
description: |-
|
||||||
|
find booking by id
|
||||||
|
<br>
|
||||||
|
operationId: DatacenterController.Get
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
description: the id you want to get
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: is_draft
|
||||||
|
description: draft wished
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{booking} models.booking'
|
||||||
|
/admiralty/kubeconfig/{execution}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admiralty
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: execution
|
||||||
|
description: execution id of the workflow
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
/admiralty/node/{execution}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admiralty
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: execution
|
||||||
|
description: execution id of the workflow
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
/admiralty/secret/{execution}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admiralty
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: execution
|
||||||
|
description: execution id of the workflow
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- admiralty
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: execution
|
||||||
|
description: execution id of the workflow
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: body
|
||||||
|
name: kubeconfig
|
||||||
|
description: Kubeconfig to use when creating secret
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.RemoteKubeconfig'
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: ""
|
||||||
|
/admiralty/source/{execution}:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- admiralty
|
||||||
|
description: |-
|
||||||
|
Create an Admiralty Source on remote cluster
|
||||||
|
<br>
|
||||||
|
operationId: AdmiraltyController.CreateSource
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: execution
|
||||||
|
description: execution id of the workflow
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: ""
|
||||||
|
/admiralty/target/{execution}:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- admiralty
|
||||||
|
description: |-
|
||||||
|
Create an Admiralty Target in the namespace associated to the executionID
|
||||||
|
<br>
|
||||||
|
operationId: AdmiraltyController.CreateAdmiraltyTarget
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: execution
|
||||||
|
description: execution id of the workflow
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: ""
|
||||||
|
/admiralty/targets:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admiralty
|
||||||
|
description: |-
|
||||||
|
find all Admiralty Target
|
||||||
|
<br>
|
||||||
|
operationId: AdmiraltyController.GetAllTargets
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
/admiralty/targets/{execution}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admiralty
|
||||||
|
description: |-
|
||||||
|
find one Admiralty Target
|
||||||
|
<br>
|
||||||
|
operationId: AdmiraltyController.GetOneTarget
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
description: the name of the target to get
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
/booking/:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- booking
|
||||||
|
description: |-
|
||||||
|
find booking by id
|
||||||
|
<br>
|
||||||
|
operationId: BookingController.GetAll
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: is_draft
|
||||||
|
description: draft wished
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{booking} models.booking'
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- booking
|
||||||
|
description: |-
|
||||||
|
create booking
|
||||||
|
<br>
|
||||||
|
operationId: BookingController.Post.
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: booking
|
||||||
|
description: the booking you want to post
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: is_draft
|
||||||
|
description: draft wished
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.object'
|
||||||
|
/booking/{id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- booking
|
||||||
|
description: |-
|
||||||
|
find booking by id
|
||||||
|
<br>
|
||||||
|
operationId: BookingController.Get
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
description: the id you want to get
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{booking} models.booking'
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- booking
|
||||||
|
description: |-
|
||||||
|
create computes
|
||||||
|
<br>
|
||||||
|
operationId: BookingController.Update
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
description: the compute id you want to get
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The compute content
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.compute'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{compute} models.compute'
|
||||||
|
/booking/check/{id}/{start_date}/{end_date}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- booking
|
||||||
|
description: |-
|
||||||
|
check booking
|
||||||
|
<br>
|
||||||
|
operationId: BookingController.Check
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
description: id of the datacenter
|
||||||
|
type: string
|
||||||
|
- in: path
|
||||||
|
name: start_date
|
||||||
|
description: 2006-01-02T15:04:05
|
||||||
|
type: string
|
||||||
|
default: the booking start date
|
||||||
|
- in: path
|
||||||
|
name: end_date
|
||||||
|
description: 2006-01-02T15:04:05
|
||||||
|
type: string
|
||||||
|
default: the booking end date
|
||||||
|
- in: query
|
||||||
|
name: is_draft
|
||||||
|
description: draft wished
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.object'
|
||||||
|
/booking/search/{start_date}/{end_date}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- booking
|
||||||
|
description: |-
|
||||||
|
search bookings
|
||||||
|
<br>
|
||||||
|
operationId: BookingController.Search
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: start_date
|
||||||
|
description: the word search you want to get
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: path
|
||||||
|
name: end_date
|
||||||
|
description: the word search you want to get
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: is_draft
|
||||||
|
description: draft wished
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{workspace} models.workspace'
|
||||||
|
/booking/search/execution/{id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- booking
|
||||||
|
description: |-
|
||||||
|
search bookings by execution
|
||||||
|
<br>
|
||||||
|
operationId: BookingController.Search
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
description: id execution
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: is_draft
|
||||||
|
description: draft wished
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{workspace} models.workspace'
|
||||||
|
/session/token/{id}/{duration}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- session
|
||||||
|
description: |-
|
||||||
|
find booking by id
|
||||||
|
<br>
|
||||||
|
operationId: SessionController.GetToken
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
description: id of the datacenter
|
||||||
|
type: string
|
||||||
|
- in: path
|
||||||
|
name: duration
|
||||||
|
description: duration of the token
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{booking} models.booking'
|
||||||
|
/version/:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- version
|
||||||
|
description: |-
|
||||||
|
get version
|
||||||
|
<br>
|
||||||
|
operationId: VersionController.GetAll
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
/version/status:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- version
|
||||||
|
description: |-
|
||||||
|
get status
|
||||||
|
<br>
|
||||||
|
operationId: VersionController.Status
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
definitions:
|
||||||
|
controllers.RemoteKubeconfig:
|
||||||
|
title: RemoteKubeconfig
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
Data:
|
||||||
|
type: string
|
||||||
|
models.compute:
|
||||||
|
title: compute
|
||||||
|
type: object
|
||||||
|
models.object:
|
||||||
|
title: object
|
||||||
|
type: object
|
||||||
|
tags:
|
||||||
|
- name: oc-datacenter/controllersDatacenterController
|
||||||
|
description: |
|
||||||
|
Operations about workspace
|
||||||
|
- name: booking
|
||||||
|
description: |
|
||||||
|
Operations about workspace
|
||||||
|
- name: version
|
||||||
|
description: |
|
||||||
|
VersionController operations for Version
|
||||||
|
- name: admiralty
|
||||||
|
description: |
|
||||||
|
Operations about the admiralty objects of the datacenter
|
||||||
Reference in New Issue
Block a user