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{}, ) }