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
2025-03-10 12:23:07 +01:00
HandleControllerErrors ( c . Controller , 500 , & err , nil )
// c.Ctx.Output.SetStatus(500)
// c.ServeJSON()
// c.Data["json"] = map[string]string{"error": err.Error()}
2025-02-20 12:46:24 +01:00
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
2025-03-10 16:27:16 +01:00
// @router /targets/:execution [get]
2025-02-20 12:46:24 +01:00
func ( c * AdmiraltyController ) GetOneTarget ( ) {
2025-03-10 17:57:14 +01:00
id := c . Ctx . Input . Param ( ":execution" )
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
}
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 execution path string true "execution id of the workflow"
2025-03-12 10:53:48 +01:00
// @Success 201
2025-03-10 16:27:16 +01:00
// @router /source/:execution [post]
2025-02-25 13:03:52 +01:00
func ( c * AdmiraltyController ) CreateSource ( ) {
execution := c . Ctx . Input . Param ( ":execution" )
2025-03-10 17:57:14 +01:00
fmt . Println ( "execution :: " , execution )
fmt . Println ( "input :: " , c . Ctx . Input )
2025-03-10 12:23:07 +01:00
serv , err := infrastructure . NewKubernetesService ( )
2025-02-25 13:03:52 +01:00
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 )
2025-03-12 11:07:45 +01:00
c . Ctx . Output . SetStatus ( 201 )
2025-02-25 13:03:52 +01:00
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"
// @Success 201
2025-03-10 16:27:16 +01:00
// @router /target/:execution [post]
2025-02-25 13:03:52 +01:00
func ( c * AdmiraltyController ) CreateAdmiraltyTarget ( ) {
var data map [ string ] interface { }
2025-03-10 12:23:07 +01:00
2025-02-25 13:03:52 +01:00
execution := c . Ctx . Input . Param ( ":execution" )
2025-03-10 12:23:07 +01:00
2025-02-25 13:03:52 +01:00
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
}
2025-03-12 11:07:45 +01:00
c . Ctx . Output . SetStatus ( 201 )
2025-02-25 13:03:52 +01:00
c . Data [ "json" ] = data
c . ServeJSON ( )
}
// @Title GetKubeSecret
// @Description Retrieve the secret created from a Kubeconfig that will be associated to an Admiralty Target
2025-03-10 12:23:07 +01:00
2025-02-25 13:03:52 +01:00
// @Param execution path string true "execution id of the workflow"
// @Success 200
2025-03-10 16:27:16 +01:00
// @router /secret/:execution [get]
2025-02-25 13:03:52 +01:00
func ( c * AdmiraltyController ) GetKubeSecret ( ) {
var data map [ string ] interface { }
2025-03-10 12:23:07 +01:00
2025-02-25 13:03:52 +01:00
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
}
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
2025-03-10 12:23:07 +01:00
2025-02-25 13:03:52 +01:00
// @Param execution path string true "execution id of the workflow"
2025-03-13 16:39:50 +01:00
// @Param kubeconfig body controllers.RemoteKubeconfig true "Kubeconfig to use when creating secret"
2025-03-12 11:07:45 +01:00
// @Success 201
2025-03-10 16:27:16 +01:00
// @router /secret/:execution [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
}
2025-03-10 12:23:07 +01:00
2025-02-25 13:03:52 +01:00
execution := c . Ctx . Input . Param ( ":execution" )
2025-03-10 12:23:07 +01:00
2025-02-25 13:03:52 +01:00
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 )
2025-03-12 11:07:45 +01:00
c . Ctx . Output . SetStatus ( 201 )
2025-02-25 13:03:52 +01:00
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 execution path string true "execution id of the workflow"
// @Success 200
2025-03-10 16:27:16 +01:00
// @router /node/:execution [get]
2025-02-27 17:00:36 +01:00
func ( c * AdmiraltyController ) GetNodeReady ( ) {
var secret v1 . Secret
2025-03-10 12:23:07 +01:00
2025-02-27 17:00:36 +01:00
execution := c . Ctx . Input . Param ( ":execution" )
2025-03-10 12:23:07 +01:00
2025-02-27 17:00:36 +01:00
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 { }
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
}
2025-03-10 12:23:07 +01:00
token , err := retrieveTokenFromKonfig ( editedKubeconfig )
if err != nil {
2025-03-13 16:39:50 +01:00
fmt . Println ( "Error while trying to retrieve token for kubeconfing" )
fmt . Println ( err )
HandleControllerErrors ( c . Controller , 500 , & err , nil )
2025-03-10 12:23:07 +01:00
}
2025-02-27 17:00:36 +01:00
// Decode token
2025-03-10 12:23:07 +01:00
isExpired , err := isTokenExpired ( token )
2025-02-27 17:00:36 +01:00
if err != nil {
2025-03-10 12:23:07 +01:00
fmt . Println ( "Error veryfing token's expiration" )
c . Ctx . Output . SetStatus ( 500 )
c . Data [ "json" ] = err
2025-02-27 17:00:36 +01:00
c . ServeJSON ( )
}
2025-03-10 12:23:07 +01:00
if * isExpired {
2025-02-27 17:00:36 +01:00
c . Data [ "json" ] = map [ string ] string {
"token" : "token in the secret is expired and must be regenerated" ,
}
2025-03-10 12:23:07 +01:00
c . Ctx . Output . SetStatus ( 410 )
2025-02-27 17:00:36 +01:00
c . ServeJSON ( )
}
c . Data [ "json" ] = map [ string ] bool { "ok" : true }
c . ServeJSON ( )
2025-02-28 14:07:45 +01:00
}
2025-03-10 12:23:07 +01:00
func retrieveTokenFromKonfig ( editedKubeconfig map [ string ] interface { } ) ( string , error ) {
var kubeUsers [ ] KubeUser
2025-03-13 16:39:50 +01:00
b , err := yaml . Marshal ( editedKubeconfig [ "users" ] )
2025-03-10 12:23:07 +01:00
if err != nil {
2025-03-13 16:39:50 +01:00
fmt . Println ( "Error while retrieving the users attribute from the Kubeconfig" )
fmt . Println ( err )
return "" , err
2025-03-10 12:23:07 +01:00
}
err = yaml . Unmarshal ( b , & kubeUsers )
if err != nil {
2025-03-13 16:39:50 +01:00
fmt . Println ( "Error while unmarshalling users attribute from kubeconfig" )
fmt . Println ( err )
return "" , nil
2025-03-10 12:23:07 +01:00
}
2025-03-13 16:39:50 +01:00
fmt . Println ( kubeUsers )
2025-03-10 12:23:07 +01:00
token := kubeUsers [ 0 ] . User . Token
return token , nil
}
func isTokenExpired ( token string ) ( * bool , error ) {
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
}
fmt . Println ( "Expiration date : " + expiration . UTC ( ) . Format ( "2006-01-02T15:04:05" ) )
expired := expiration . Unix ( ) < time . Now ( ) . Unix ( )
return & expired , nil
}
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
2025-03-10 12:23:07 +01:00
2025-02-28 14:07:45 +01:00
// @Param execution path string true "execution id of the workflow"
// @Success 200
2025-03-10 16:27:16 +01:00
// @router /kubeconfig/:execution [get]
2025-02-28 14:07:45 +01:00
func ( c * AdmiraltyController ) GetAdmiraltyKubeconfig ( ) {
2025-03-10 12:23:07 +01:00
2025-02-28 14:07:45 +01:00
execution := c . Ctx . Input . Param ( ":execution" )
2025-03-10 12:23:07 +01:00
2025-02-28 14:07:45 +01:00
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 ( )
}
func NewHostKubeWithToken ( token string ) ( * models . KubeConfigValue , error ) {
if len ( token ) == 0 {
2025-03-10 12:23:07 +01:00
return nil , fmt . Errorf ( "you didn't provide a token to be inserted in the Kubeconfig" )
2025-02-28 14:07:45 +01:00
}
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
}