2025-02-19 12:28:48 +01:00
package controllers
import (
2025-02-28 14:07:45 +01:00
"encoding/base64"
2025-02-25 13:03:52 +01:00
"encoding/json"
"fmt"
2025-02-28 14:07:45 +01:00
"oc-datacenter/conf"
2025-02-20 12:46:24 +01:00
"oc-datacenter/infrastructure"
2025-02-28 14:07:45 +01:00
"oc-datacenter/models"
2025-02-20 12:46:24 +01:00
"slices"
2025-02-27 17:00:36 +01:00
"time"
2025-02-20 12:46:24 +01:00
2025-02-19 12:28:48 +01:00
beego "github.com/beego/beego/v2/server/web"
2025-02-27 17:00:36 +01:00
jwt "github.com/golang-jwt/jwt/v5"
2025-02-28 17:57:13 +01:00
"gopkg.in/yaml.v2"
2025-02-27 17:00:36 +01:00
v1 "k8s.io/api/core/v1"
2025-02-19 12:28:48 +01:00
)
2025-02-25 13:03:52 +01:00
type KubeInfo struct {
Url * string
KubeCA * string
KubeCert * string
KubeKey * string
}
2025-02-28 14:07:45 +01:00
type RemoteKubeconfig struct {
2025-02-25 13:03:52 +01:00
Data * string
}
2025-02-27 17:00:36 +01:00
type KubeUser struct {
Name string
User struct {
Token string
}
}
2025-02-28 14:07:45 +01:00
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" `
}
2025-03-05 16:50:57 +01:00
2025-02-19 12:28:48 +01:00
2025-02-19 12:28:48 +01:00
// Operations about the admiralty objects of the datacenter
type AdmiraltyController struct {
beego . Controller
}
// @Title GetAllTargets
// @Description find all Admiralty Target
2025-02-20 12:46:24 +01:00
// @Success 200
// @router /targets [get]
2025-02-19 12:28:48 +01:00
func ( c * AdmiraltyController ) GetAllTargets ( ) {
2025-02-20 12:46:24 +01:00
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
}
2025-02-19 12:28:48 +01:00
2025-02-20 12:46:24 +01:00
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/:id [get]
func ( c * AdmiraltyController ) GetOneTarget ( ) {
id := c . Ctx . Input . Param ( ":id" )
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 ( ) )
id = "target-" + id
found := slices . Contains ( res , id )
if ! found {
c . Ctx . Output . SetStatus ( 404 )
c . ServeJSON ( )
}
c . Data [ "json" ] = id
c . ServeJSON ( )
2025-02-25 13:03:52 +01:00
}
// @Title CreateSource
// @Description Create an Admiralty Source on remote cluster
// @Param dc_id path string true "which dc to contact"
// @Param execution path string true "execution id of the workflow"
2025-02-27 17:00:36 +01:00
// @Param kubeconfigInfo body controllers.KubeInfo true "url and serviceAccount to use with the source formatted as json object"
2025-02-25 13:03:52 +01:00
// @Success 200
2025-03-03 11:40:53 +01:00
// @router /source/:id [post]
2025-02-25 13:03:52 +01:00
func ( c * AdmiraltyController ) CreateSource ( ) {
var data KubeInfo
json . Unmarshal ( c . Ctx . Input . CopyBody ( 10000000 ) , & data )
if data . Url == nil || data . KubeCA == nil || data . KubeCert == nil || data . KubeKey == nil {
c . Ctx . Output . SetStatus ( 500 )
c . ServeJSON ( )
missingData := fmt . Sprint ( data )
c . Data [ "json" ] = map [ string ] string { "error" : "Missing something in " + missingData }
c . ServeJSON ( )
}
fmt . Println ( "" )
fmt . Println ( "URL : %v" , data . Url )
fmt . Println ( "" )
fmt . Println ( "CA : %v" , data . KubeCA )
fmt . Println ( "" )
fmt . Println ( "Key : " , data . KubeKey )
dc_id := c . Ctx . Input . Param ( ":dc_id" )
execution := c . Ctx . Input . Param ( ":execution" )
_ = dc_id
serv , err := infrastructure . NewRemoteKubernetesService (
* data . Url ,
* data . KubeCA ,
* data . KubeCert ,
* data . KubeKey ,
)
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
}
2025-02-27 17:00:36 +01:00
res , err := serv . CreateAdmiraltySource ( c . Ctx . Request . Context ( ) , execution )
2025-02-25 13:03:52 +01:00
if err != nil {
// 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 )
c . Data [ "json" ] = respData
c . ServeJSON ( )
}
// @Title CreateAdmiraltyTarget
// @Description Create an Admiralty Target in the namespace associated to the executionID
// @Param dc_id path string true "which dc to contact"
// @Param execution path string true "execution id of the workflow"
// @Success 201
2025-03-03 11:40:53 +01:00
// @router /target/:id [post]
2025-02-25 13:03:52 +01:00
func ( c * AdmiraltyController ) CreateAdmiraltyTarget ( ) {
var data map [ string ] interface { }
dc_id := c . Ctx . Input . Param ( ":dc_id" )
execution := c . Ctx . Input . Param ( ":execution" )
_ = dc_id
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
}
2025-02-27 17:00:36 +01:00
resp , err := serv . CreateAdmiraltyTarget ( c . Ctx . Request . Context ( ) , execution )
2025-02-25 13:03:52 +01:00
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 . Data [ "json" ] = data
c . ServeJSON ( )
}
// @Title GetKubeSecret
// @Description Retrieve the secret created from a Kubeconfig that will be associated to an Admiralty Target
// @Param dc_id path string true "which dc to contact"
// @Param execution path string true "execution id of the workflow"
// @Success 200
2025-03-03 11:40:53 +01:00
// @router /secret/:id [get]
2025-02-25 13:03:52 +01:00
func ( c * AdmiraltyController ) GetKubeSecret ( ) {
var data map [ string ] interface { }
dc_id := c . Ctx . Input . Param ( ":dc_id" )
execution := c . Ctx . Input . Param ( ":execution" )
_ = dc_id
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
}
2025-02-27 17:00:36 +01:00
resp , err := serv . GetKubeconfigSecret ( c . Ctx . Request . Context ( ) , execution )
2025-02-25 13:03:52 +01:00
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 dc_id path string true "which dc to contact"
// @Param execution path string true "execution id of the workflow"
// @Param kubeconfig body controllers.Kubeconfig true "Kubeconfig to use when creating secret"
// @Success 200
2025-03-03 11:40:53 +01:00
// @router /secret/:id [post]
2025-02-25 13:03:52 +01:00
func ( c * AdmiraltyController ) CreateKubeSecret ( ) {
2025-02-28 14:07:45 +01:00
var kubeconfig RemoteKubeconfig
2025-02-25 13:03:52 +01:00
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
}
dc_id := c . Ctx . Input . Param ( ":dc_id" )
execution := c . Ctx . Input . Param ( ":execution" )
_ = dc_id
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
}
2025-02-27 17:00:36 +01:00
resp , err := serv . CreateKubeconfigSecret ( c . Ctx . Request . Context ( ) , * kubeconfig . Data , execution )
2025-02-25 13:03:52 +01:00
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 . Data [ "json" ] = respData
c . ServeJSON ( )
2025-02-27 17:00:36 +01:00
}
// @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 dc_id path string true "which dc to contact"
// @Param execution path string true "execution id of the workflow"
// @Success 200
// @Success 203
2025-03-03 11:40:53 +01:00
// @router /node/:id [get]
2025-02-27 17:00:36 +01:00
func ( c * AdmiraltyController ) GetNodeReady ( ) {
var secret v1 . Secret
dc_id := c . Ctx . Input . Param ( ":dc_id" )
execution := c . Ctx . Input . Param ( ":execution" )
_ = dc_id
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 )
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 {
"error" : "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 )
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 { }
var kubeUsers [ ] KubeUser
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
}
b , err := json . Marshal ( editedKubeconfig [ "users" ] )
err = yaml . Unmarshal ( b , & kubeUsers )
token := kubeUsers [ 0 ] . User . Token
// Decode token
t , _ , err := new ( jwt . Parser ) . ParseUnverified ( token , jwt . MapClaims { } )
if err != nil {
fmt . Println ( "couldn't decode token" )
c . Data [ "json" ] = false
c . ServeJSON ( )
}
expiration , err := t . Claims . GetExpirationTime ( )
fmt . Println ( "Expiration date : " + expiration . UTC ( ) . Format ( "2006-01-02T15:04:05" ) )
if expiration . Add ( 1 * time . Hour ) . Unix ( ) < time . Now ( ) . Unix ( ) {
c . Data [ "json" ] = map [ string ] string {
"token" : "token in the secret is expired and must be regenerated" ,
}
c . ServeJSON ( )
}
c . Data [ "json" ] = map [ string ] bool { "ok" : true }
c . ServeJSON ( )
2025-02-28 14:07:45 +01:00
}
// @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 dc_id path string true "which dc to contact"
// @Param execution path string true "execution id of the workflow"
// @Success 200
2025-03-03 11:40:53 +01:00
// @router /kubeconfig/:id [get]
2025-02-28 14:07:45 +01:00
func ( c * AdmiraltyController ) GetAdmiraltyKubeconfig ( ) {
dc_id := c . Ctx . Input . Param ( ":dc_id" )
execution := c . Ctx . Input . Param ( ":execution" )
_ = dc_id
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
}
2025-02-28 17:57:13 +01:00
b , err := yaml . 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 ,
}
2025-02-28 14:07:45 +01:00
c . ServeJSON ( )
return
}
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 : conf . GetConfig ( ) . KubeHost ,
CertificateAuthorityData : encodedCA ,
} ,
} ,
} ,
Contexts : [ ] models . KubeconfigNamedContext {
{
Name : "default" ,
Context : models . KubeconfigContext {
Cluster : "default" ,
User : "default" ,
} ,
} ,
} ,
Users : [ ] models . KubeconfigUser {
models . KubeconfigUser {
Name : "default" ,
User : models . KubeconfigUserKeyPair {
Token : token ,
} ,
} ,
} ,
}
return & hostKube , nil
2025-03-05 16:59:45 +01:00
}