Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(API): [KDL6-276] update CR on configmap change #1137

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/.golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ issues:
- gochecknoglobals
- gosec
- mnd
- funlen # it's not common to fragment test functions
- dupl # tests are repetitive by nature
# https://github.com/go-critic/go-critic/issues/926
- linters:
- gocritic
Expand Down
2 changes: 1 addition & 1 deletion app/api/entity/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func checkTolerationSeconds(toleration map[string]interface{}) error {

if ok {
switch seconds.(type) {
case int, int32, int64:
case int, int32, int64, float64:
default:
return wrapErrWithValue(ErrCapabilitiesInvalidSeconds, fmt.Sprintf("%v", seconds))
}
Expand Down
3 changes: 2 additions & 1 deletion app/api/infrastructure/k8s/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import "errors"

var errCRDNoMetadata = errors.New("CRD does not have a 'metadata' field")
var errCRDNoSpec = errors.New("CRD does not have a 'spec' field")
var errCDRNoSpecUsername = errors.New("CRD does not have a 'spec.username' field")
var errCRDNoSpecMlflow = errors.New("CRD does not have a 'spec.mlflow' field")
var errCRDNoSpecMlflowEnv = errors.New("CRD does not have a 'spec.mlflow.env' field")
var errCRDNoSpecFilebrowser = errors.New("CRD does not have a 'spec.filebrowser' field")
Expand All @@ -13,3 +12,5 @@ var errCRDNoSpecVscodeRuntime = errors.New("CRD does not have a 'spec.vscodeRunt
var errCRDNoSpecVscodeRuntimeEnv = errors.New("CRD does not have a 'spec.vscodeRuntime.env' field")
var errCRDNoSpecVscodeRuntimeImage = errors.New("CRD does not have a 'spec.vscodeRuntime.image' field")
var errCRDNoSpecPodLabels = errors.New("CRD does not have a 'spec.podLabels' field")
var errCRDCantEncodeInputData = errors.New("can't encode input data for CR")
var errCRDCantDecodeInputData = errors.New("can't decode input data from CR")
4 changes: 2 additions & 2 deletions app/api/infrastructure/k8s/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ type ClientInterface interface {
CreateSecret(ctx context.Context, name string, values, labels map[string]string) error
UpdateSecret(ctx context.Context, name string, values, labels map[string]string) error
GetSecret(ctx context.Context, name string) (map[string][]byte, error)
CreateKDLUserToolsCR(ctx context.Context, username string, data UserToolsData) error
CreateKDLUserToolsCR(ctx context.Context, data UserToolsData) error
DeleteUserToolsCR(ctx context.Context, username string) error
UpdateKDLUserToolsCR(ctx context.Context, resourceName string, data UserToolsData, crd *map[string]interface{}) error
UpdateKDLUserToolsCR(ctx context.Context, resourceName string, crd *map[string]interface{}) error
ListKDLUserToolsCR(ctx context.Context) ([]unstructured.Unstructured, error)
GetKDLUserToolsCR(ctx context.Context, username string) (*unstructured.Unstructured, error)
IsUserToolPODRunning(ctx context.Context, username string) (bool, error)
Expand Down
83 changes: 69 additions & 14 deletions app/api/infrastructure/k8s/kdlproject.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package k8s

import (
"context"
"encoding/json"

"github.com/konstellation-io/kdl-server/app/api/entity"
"github.com/konstellation-io/kdl-server/app/api/pkg/kdlutil"
Expand All @@ -15,6 +16,42 @@ type ProjectData struct {
MinioAccessKey entity.MinioAccessKey
}

func (k *Client) ProjectDataToMap(data ProjectData) (map[string]string, error) {
minioAccessKeyJSON, err := json.Marshal(data.MinioAccessKey)
if err != nil {
return nil, err
}

return map[string]string{
"projectId": data.ProjectID,
"minioAccessKey": string(minioAccessKeyJSON),
}, nil
}

func (k *Client) MapToProjectData(data map[string]interface{}) (ProjectData, error) {
minioAccessKeyJSON, ok := data["minioAccessKey"].(string)
if !ok {
return ProjectData{}, errCRDCantDecodeInputData
}

var minioAccessKey entity.MinioAccessKey

err := json.Unmarshal([]byte(minioAccessKeyJSON), &minioAccessKey)
if err != nil {
return ProjectData{}, err
}

projectID, ok := data["projectId"].(string)
if !ok {
return ProjectData{}, errCRDCantDecodeInputData
}

return ProjectData{
ProjectID: projectID,
MinioAccessKey: minioAccessKey,
}, nil
}

func (k *Client) GetConfigMapTemplateNameKDLProject() string {
return k.cfg.ReleaseName + "-server-project-template"
}
Expand All @@ -28,6 +65,12 @@ func (k *Client) updateKDLProjectTemplate(data ProjectData, crd *map[string]inte
return nil, errCRDNoSpec
}

inputData, err := k.ProjectDataToMap(data)
if err != nil {
return nil, errCRDCantEncodeInputData
}

spec["inputData"] = inputData
spec["projectId"] = data.ProjectID

// update spec.mlflow.env in the CRD object
Expand Down Expand Up @@ -148,40 +191,52 @@ func (k *Client) GetKDLProjectCR(ctx context.Context, name string) (*unstructure
}

func (k *Client) UpdateKDLProjectsCR(ctx context.Context, projectID string, crd *map[string]interface{}) error {
// update the CRD object with correct values
data := ProjectData{
ProjectID: projectID,
// FUTURE: add minio credentials here
}
k.logger.Info("Updating kdl project", "projectName", projectID)

crdUpdated, err := k.updateKDLProjectTemplate(data, crd)
// Get existing CR
existingKDLProject, err := k.GetKDLProjectCR(ctx, projectID)
if err != nil {
return err
}

// to update current CRD object, we need to get the existing CRD object and update the spec field
specValue, ok := (*crdUpdated)["spec"]
// Recover the input data from the existing CR
existingSpec, _, err := unstructured.NestedMap(existingKDLProject.Object, "spec")
if err != nil {
return errCRDCantDecodeInputData
}

inputDataMap, ok := existingSpec["inputData"].(map[string]interface{})
if !ok {
return errCRDNoSpec
return errCRDCantDecodeInputData
}

existingKDLProject, err := k.GetKDLProjectCR(ctx, data.ProjectID)
inputData, err := k.MapToProjectData(inputDataMap)
if err != nil {
return errCRDCantDecodeInputData
}

// Re-apply the input data with the updated template
crdUpdated, err := k.updateKDLProjectTemplate(inputData, crd)
if err != nil {
return err
}

// to update current CRD object, we need to get the existing CRD object and update the spec field
specValue, ok := (*crdUpdated)["spec"]
if !ok {
return errCRDNoSpec
}

existingKDLProject.Object["spec"] = specValue

// CRD object is now updated and ready to be created
k.logger.Info("Updating kdl project", "projectName", data.ProjectID)

_, err = k.kdlProjectRes.Namespace(k.cfg.Kubernetes.Namespace).Update(ctx, existingKDLProject, metav1.UpdateOptions{})
if err != nil {
k.logger.Error(err, "Error updating KDL Project CR in k8s", "projectName", data.ProjectID)
k.logger.Error(err, "Error updating KDL Project CR in k8s", "projectName", projectID)
return err
}

k.logger.Info("Updated kdl project", "projectName", data.ProjectID)
k.logger.Info("Updated kdl project", "projectName", projectID)

return nil
}
14 changes: 14 additions & 0 deletions app/api/infrastructure/k8s/kdlproject_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ func (s *testSuite) TestCreateKDLProjectCR_and_DeleteKDLProjectCR() {
s.Require().Equal(projectMinioAccessKey, filebrowserEnv["AWS_S3_ACCESS_KEY_ID"])
s.Require().Equal(projectMinioSecretKey, filebrowserEnv["AWS_S3_SECRET_ACCESS_KEY"])

// Check the input data itself is stored as well
minioAccessKeyJSON := "{\"AccessKey\":\"project-test-project-id\",\"SecretKey\":\"testproject123\"}"
inputData, _ := spec["inputData"].(map[string]interface{})
inputProjectID, _ := inputData["projectId"].(string)
s.Require().Equal(projectID, inputProjectID)
s.Require().Equal(minioAccessKeyJSON, inputData["minioAccessKey"])

// Delete the CR
err = s.Client.DeleteKDLProjectCR(context.Background(), projectID)
s.Require().NoError(err)
Expand Down Expand Up @@ -265,6 +272,13 @@ func (s *testSuite) TestUpdateKDLProjectsCR() {
// Update the CR
crd := map[string]interface{}{
"spec": map[string]interface{}{
"inputData": map[string]interface{}{
"projectId": "my-demo-projectId",
"minioAccessKey": map[string]interface{}{
"AccessKey": "my-demo-accessKey",
"SecretKey": "my-demo-secretKey",
},
},
"projectId": "my-demo-projectId",
"mlflow": map[string]interface{}{
"env": map[string]interface{}{
Expand Down
Loading
Loading