Added routes and methods to create admiralty resources : secrets, target sources

This commit is contained in:
pb
2025-02-25 13:03:52 +01:00
parent d26f0d6b1b
commit f60474681b
3 changed files with 474 additions and 9 deletions

View File

@@ -14,6 +14,10 @@ type Infrastructure interface {
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(executionId string) ([]byte, error)
CreateKubeconfigSecret(kubeconfig string, executionId string) ([]byte, error)
GetKubeconfigSecret(executionId string) ([]byte, error)
CreateAdmiraltyTarget(executionId string)([]byte,error)
}
var _service = map[string]func() (Infrastructure, error){
@@ -27,3 +31,4 @@ func NewService() (Infrastructure, error) {
}
return service()
}

View File

@@ -1,15 +1,19 @@
package infrastructure
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"html/template"
"oc-datacenter/conf"
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/client-go/kubernetes"
"k8s.io/client-go/rest"
@@ -43,6 +47,34 @@ func NewKubernetesService() (Infrastructure, error) {
}, 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
namespace := &v1.Namespace{
@@ -158,19 +190,16 @@ func (k *KubernetesService) GetToken(ctx context.Context, ns string, duration in
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 := k.Set.RESTClient().
Get().
AbsPath("/apis/multicluster.admiralty.io/v1alpha1/targets").
DoRaw(ctx) // from https://stackoverflow.com/questions/60764908/how-to-access-kubernetes-crd-using-client-go
resp, err := getCDRapiKube(*k.Set, ctx,"/apis/multicluster.admiralty.io/v1alpha1/targets")
if err != nil {
fmt.Println("TODO : handle the error generated when contacting kube API")
fmt.Println("Error from k8s API : ", err)
return nil,err
}
fmt.Println(string(resp))
var targetDict map[string]interface{}
err = json.Unmarshal(resp,&targetDict)
@@ -201,8 +230,215 @@ func (k *KubernetesService) GetTargets(ctx context.Context) ([]string,error){
listTargets = append(listTargets, metadata.Name)
}
// parse targets to retrieve the info we need
// fmt.Println(targetDict)
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(executionId string)([]byte,error){
exists, err := k.GetKubeconfigSecret(executionId)
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 ns-",executionId)
return nil, nil // Maybe we could create a wrapper for errors and add more info to have
}
var targetManifest string
var tpl bytes.Buffer
tmpl, err := template.New("target").
Parse("{\"apiVersion\": \"multicluster.admiralty.io/v1alpha1\", \"kind\": \"Target\", \"metadata\": {\"name\": \"target-{{.ExecutionId}}\"}, \"spec\": { \"kubeconfigSecret\" :{\"name\": \"kube-secret-{{.ExecutionId}}\"}} }")
if err != nil {
fmt.Println("Error creating the template for the target Manifest")
return nil, err
}
err = tmpl.Execute(&tpl, map[string]string{"ExecutionId":executionId})
targetManifest = tpl.String()
resp, err := postCDRapiKube(
*k.Set,
context.TODO(),
"/apis/multicluster.admiralty.io/v1alpha1/namespaces/ns-"+ executionId +"/targets",
[]byte(targetManifest),
map[string]string{"fieldManager":"kubectl-client-side-apply"},
map[string]string{"fieldValidation":"Strict"},
)
if err != nil {
fmt.Println("Error trying to create a Source on remote cluster : ", err , " : ", resp)
return nil, err
}
return resp, 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(executionId string) ([]byte, error) {
var sourceManifest string
var tpl bytes.Buffer
tmpl, err := template.New("source").
Parse("{\"apiVersion\": \"multicluster.admiralty.io/v1alpha1\", \"kind\": \"Source\", \"metadata\": {\"name\": \"source-{{.ExecutionId}}\"}, \"spec\": {\"serviceAccountName\": \"sa-{{.ExecutionId}}\"} }")
if err != nil {
fmt.Println("Error creating the template for the source Manifest")
return nil, err
}
err = tmpl.Execute(&tpl, map[string]string{"ExecutionId":executionId})
sourceManifest = tpl.String()
resp, err := postCDRapiKube(
*k.Set,
context.TODO(),
"/apis/multicluster.admiralty.io/v1alpha1/namespaces/ns-"+ executionId +"/sources",
[]byte(sourceManifest),
map[string]string{"fieldManager":"kubectl-client-side-apply"},
map[string]string{"fieldValidation":"Strict"},
)
// We can add more info to the log with the content of resp if not nil
if err != nil {
fmt.Println("Error trying to create a Source on remote cluster : ", err , " : ", resp)
return nil, err
}
return resp, 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(kubeconfig string, executionId 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
}
secretManifest := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-secret-" + executionId,
Namespace: "ns-" + executionId,
},
Data: map[string][]byte{
"config": config,
},
}
exists, err := k.GetKubeconfigSecret(executionId)
if err != nil {
fmt.Println("Error verifying if kube secret exists in ns-", executionId)
return nil, err
}
if exists != nil {
fmt.Println("kube-secret already exists in ns-", 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("ns-"+executionId).
Create(context.TODO(),secretManifest,metav1.CreateOptions{})
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(executionId string) ([]byte, error) {
resp, err := k.Set.CoreV1().
Secrets("ns-"+executionId).
Get(context.TODO(),"kube-secret-"+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 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 postCDRapiKube(client kubernetes.Clientset, ctx context.Context, path string, body []byte, params ...map[string]string) ([]byte, error){
req := client.RESTClient().
Post().
AbsPath(path).
Body(body)
for _, param := range params {
for k,v := range param {
req = req.Param(k,v)
}
}
resp, err := req.DoRaw(ctx)
if err != nil {
fmt.Println("Error from k8s API when posting " + string(body) + " to " + path + " : " , err)
return nil,err
}
return resp, nil
}