package main
import (
"errors"
"fmt"
"os/exec"
"strings"
"slices"
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
"github.com/go-ldap/ldap/v3"
)
const (
FIELD_AEROGRAMME_CRYPTOROOT = "aero_cryptoroot"
FIELD_AEROGRAMME_BUCKET_ID = "aero_bucket_id"
FIELD_AEROGRAMME_BUCKET_NAME = "aero_bucket"
LOCAL_ALIAS_NAME = "aerogramme"
)
var (
ErrPimBuilderDirty = fmt.Errorf("builder is dirty.")
ErrPimBucketLocalAliasNotFound = fmt.Errorf("local alias does not exist in garage or points to the wrong bucket.")
ErrPimBucketIdEmpty = fmt.Errorf("missing bucket ID in LDAP.")
ErrPimBucketNameEmpty = fmt.Errorf("missing bucket local garage alias in LDAP.")
ErrPimBucketInfoNotFetched = fmt.Errorf("bucket info has not been fetched.")
ErrPimCryptoRootEmpty = fmt.Errorf("missing cryptoroot in LDAP.")
ErrPimCantCreateBucket = fmt.Errorf("unable to create PIM bucket.")
)
type PimBuilder struct {
user *LoggedUser
cryptoroot string
bucketId string
bucketName string
bucketInfo *garage.BucketInfo
dirty bool
errors []error
}
func NewPimBuilder(user *LoggedUser) *PimBuilder {
return &PimBuilder {
user: user,
cryptoroot: user.Entry.GetAttributeValue(FIELD_AEROGRAMME_CRYPTOROOT),
bucketId: user.Entry.GetAttributeValue(FIELD_AEROGRAMME_BUCKET_ID),
bucketName: user.Entry.GetAttributeValue(FIELD_AEROGRAMME_BUCKET_NAME),
bucketInfo: nil,
dirty: false,
errors: make([]error, 0),
}
}
func (pm *PimBuilder) CheckCryptoRoot() *PimBuilder {
if pm.cryptoroot == "" {
cmd := exec.Command("./aerogramme", "tools", "crypto-root", "new-clear-text")
var out strings.Builder
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
pm.errors = append(pm.errors, err)
return pm
}
pm.cryptoroot = out.String()
pm.dirty = true
}
return pm
}
func (pm *PimBuilder) CheckBucket() *PimBuilder {
keyInfo, err := pm.user.S3KeyInfo()
if err != nil {
pm.errors = append(pm.errors, err)
return pm
}
if pm.bucketId == "" {
candidateName := LOCAL_ALIAS_NAME
var bInfo *garage.BucketInfo
var err error
err = nil
for _, ext := range []string{"", "-1", "-2", "-3", "-4", "-5"} {
candidateName = LOCAL_ALIAS_NAME + ext
bInfo, err = grgCreateLocalBucket(candidateName, *keyInfo.AccessKeyId)
if err == nil {
break
}
}
if err != nil {
pm.errors = append(pm.errors, ErrPimCantCreateBucket)
return pm
}
qr := pm.user.Quota.DefaultPimQuota()
ur := garage.NewUpdateBucketRequest()
ur.SetQuotas(*qr)
bInfo, err = grgUpdateBucket(*bInfo.Id, ur)
if err != nil {
pm.errors = append(pm.errors, err)
return pm
}
pm.bucketId = *bInfo.Id
pm.bucketName = candidateName
pm.bucketInfo = bInfo
pm.dirty = true
} else {
binfo, err := grgGetBucket(pm.bucketId)
if err != nil {
pm.errors = append(pm.errors, err)
return pm
}
pm.bucketInfo = binfo
//@TODO find my key, check that pm.bucketName exists in bucketLocalAliases
nameFound := false
for _, k := range binfo.Keys {
if *k.AccessKeyId != *keyInfo.AccessKeyId {
// not my key
continue
}
if slices.Contains(k.BucketLocalAliases, pm.bucketName) {
nameFound = true
break
}
}
if !nameFound {
pm.errors = append(pm.errors, ErrPimBucketLocalAliasNotFound)
return pm
}
}
return pm
}
func (pm *PimBuilder) LdapUpdate() *PimBuilder {
if len(pm.errors) > 0 {
return pm
}
modify_request := ldap.NewModifyRequest(pm.user.Login.Info.DN(), nil)
modify_request.Replace(FIELD_AEROGRAMME_CRYPTOROOT, []string{pm.cryptoroot})
modify_request.Replace(FIELD_AEROGRAMME_BUCKET_NAME, []string{pm.bucketName})
modify_request.Replace(FIELD_AEROGRAMME_BUCKET_ID, []string{pm.bucketId})
err := pm.user.Login.conn.Modify(modify_request)
if err != nil {
pm.errors = append(pm.errors, err)
return pm
}
pm.dirty = false
return pm
}
func (pm *PimBuilder) Build() (*PimController, error) {
// checks
if pm.dirty {
pm.errors = append(pm.errors, ErrPimBuilderDirty)
}
if pm.bucketId == "" {
pm.errors = append(pm.errors, ErrPimBucketIdEmpty)
}
if pm.bucketName == "" {
pm.errors = append(pm.errors, ErrPimBucketNameEmpty)
}
if pm.bucketInfo == nil {
pm.errors = append(pm.errors, ErrPimBucketInfoNotFetched)
}
if pm.cryptoroot == "" {
pm.errors = append(pm.errors, ErrPimCryptoRootEmpty)
}
if len(pm.errors) > 0 {
err := errors.New("PIM Builder failed")
for _, iterErr := range pm.errors {
err = errors.Join(err, iterErr)
}
return nil, err
}
// quotas
q := pm.bucketInfo.GetQuotas()
size := NewQuotaStat(*pm.bucketInfo.Bytes, (&q).GetMaxSize(), true)
objects := NewQuotaStat(*pm.bucketInfo.Objects, (&q).GetMaxObjects(), false)
// final object
pim_ctl := &PimController {
BucketId: pm.bucketId,
BucketName: pm.bucketName,
Size: size,
Files: objects,
user: pm.user,
bucketInfo: pm.bucketInfo,
cryptoroot: pm.cryptoroot,
}
return pim_ctl, nil
}
// --- Controller ---
type PimController struct {
BucketId string `json:"bucket_id"`
BucketName string `json:"bucket_name"`
Size QuotaStat `json:"quota_size"`
Files QuotaStat `json:"quota_files"`
user *LoggedUser
bucketInfo *garage.BucketInfo
cryptoroot string
}
//@FIXME Implement quota bursting