diff options
author | Quentin <quentin@dufour.io> | 2023-09-26 06:44:36 +0000 |
---|---|---|
committer | Quentin <quentin@dufour.io> | 2023-09-26 06:44:36 +0000 |
commit | 49d8e81fbea0d4703a33e87a807927169a8060ac (patch) | |
tree | d0b655454d5e13ed2238060fee27fc0d951d64c8 /website.go | |
parent | 1e75c21b65021da0c3c5a8be9be12114a2327464 (diff) | |
parent | 706ff58a6f6608719feda15075d50f978df39c5b (diff) | |
download | guichet-49d8e81fbea0d4703a33e87a807927169a8060ac.tar.gz guichet-49d8e81fbea0d4703a33e87a807927169a8060ac.zip |
Merge pull request 'An API for Guichet' (#23) from api into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/guichet/pulls/23
Diffstat (limited to 'website.go')
-rw-r--r-- | website.go | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/website.go b/website.go new file mode 100644 index 0000000..ba432c5 --- /dev/null +++ b/website.go @@ -0,0 +1,236 @@ +package main + +import ( + "fmt" + garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" + "sort" + "strings" +) + +var ( + ErrWebsiteNotFound = fmt.Errorf("Website not found") + ErrFetchBucketInfo = fmt.Errorf("Failed to fetch bucket information") + ErrWebsiteQuotaReached = fmt.Errorf("Can't create additional websites, quota reached") + ErrEmptyBucketName = fmt.Errorf("You can't create a website with an empty name") + ErrCantCreateBucket = fmt.Errorf("Can't create this bucket. Maybe another bucket already exists with this name or you have an invalid character") + ErrCantAllowKey = fmt.Errorf("Can't allow given key on the target bucket") + ErrCantConfigureBucket = fmt.Errorf("Unable to configure the bucket (activating website, adding quotas, etc.)") + ErrBucketDeleteNotEmpty = fmt.Errorf("You must remove all the files before deleting a bucket") + ErrBucketDeleteUnfinishedUpload = fmt.Errorf("You must remove all the unfinished multipart uploads before deleting a bucket") +) + +type WebsiteId struct { + Pretty string `json:"name"` + Internal string `json:"-"` + Alt []string `json:"alt_name"` + Expanded bool `json:"expanded"` + Url string `json:"domain"` +} + +func NewWebsiteId(id string, aliases []string) *WebsiteId { + pretty := id + var alt []string + if len(aliases) > 0 { + pretty = aliases[0] + alt = aliases[1:] + } + expanded := strings.Contains(pretty, ".") + + url := pretty + if !expanded { + url = fmt.Sprintf("%s.web.deuxfleurs.fr", pretty) + } + + return &WebsiteId{pretty, id, alt, expanded, url} +} +func NewWebsiteIdFromBucketInfo(binfo *garage.BucketInfo) *WebsiteId { + return NewWebsiteId(*binfo.Id, binfo.GlobalAliases) +} + +type WebsiteController struct { + User *LoggedUser + WebsiteIdx map[string]*WebsiteId + PrettyList []string + WebsiteCount QuotaStat +} + +func NewWebsiteController(user *LoggedUser) (*WebsiteController, error) { + idx := map[string]*WebsiteId{} + var wlist []string + + keyInfo, err := user.S3KeyInfo() + if err != nil { + return nil, err + } + + for _, bckt := range keyInfo.Buckets { + if len(bckt.GlobalAliases) > 0 { + wid := NewWebsiteId(*bckt.Id, bckt.GlobalAliases) + idx[wid.Pretty] = wid + wlist = append(wlist, wid.Pretty) + } + } + sort.Strings(wlist) + + maxW := user.Quota.WebsiteCount + quota := NewQuotaStat(int64(len(wlist)), maxW, true) + + return &WebsiteController{user, idx, wlist, quota}, nil +} + +type WebsiteDescribe struct { + AccessKeyId string `json:"access_key_id"` + SecretAccessKey string `json:"secret_access_key"` + AllowedWebsites *QuotaStat `json:"quota_website_count"` + BurstBucketQuotaSize string `json:"burst_bucket_quota_size"` + Websites []*WebsiteId `json:"vhosts"` +} + +func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { + s3key, err := w.User.S3KeyInfo() + if err != nil { + return nil, err + } + + r := make([]*WebsiteId, 0, len(w.PrettyList)) + for _, k := range w.PrettyList { + r = append(r, w.WebsiteIdx[k]) + } + return &WebsiteDescribe{ + *s3key.AccessKeyId, + *s3key.SecretAccessKey, + &w.WebsiteCount, + w.User.Quota.WebsiteSizeBurstedPretty(), + r}, nil +} + +func (w *WebsiteController) Inspect(pretty string) (*WebsiteView, error) { + website, ok := w.WebsiteIdx[pretty] + if !ok { + return nil, ErrWebsiteNotFound + } + + binfo, err := grgGetBucket(website.Internal) + if err != nil { + return nil, ErrFetchBucketInfo + } + + return NewWebsiteView(binfo), nil +} + +func (w *WebsiteController) Patch(pretty string, patch *WebsitePatch) (*WebsiteView, error) { + website, ok := w.WebsiteIdx[pretty] + if !ok { + return nil, ErrWebsiteNotFound + } + + binfo, err := grgGetBucket(website.Internal) + if err != nil { + return nil, ErrFetchBucketInfo + } + + // Patch the max size + urQuota := garage.NewUpdateBucketRequestQuotas() + urQuota.SetMaxSize(w.User.Quota.WebsiteSizeAdjust(binfo.Quotas.GetMaxSize())) + urQuota.SetMaxObjects(w.User.Quota.WebsiteObjectAdjust(binfo.Quotas.GetMaxObjects())) + if patch.Size != nil { + urQuota.SetMaxSize(w.User.Quota.WebsiteSizeAdjust(*patch.Size)) + } + + // Build the update + ur := garage.NewUpdateBucketRequest() + ur.SetQuotas(*urQuota) + + // Call garage + binfo, err = grgUpdateBucket(website.Internal, ur) + if err != nil { + return nil, ErrCantConfigureBucket + } + + return NewWebsiteView(binfo), nil +} + +func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { + if pretty == "" { + return nil, ErrEmptyBucketName + } + + if w.WebsiteCount.IsFull() { + return nil, ErrWebsiteQuotaReached + } + + binfo, err := grgCreateBucket(pretty) + if err != nil { + return nil, ErrCantCreateBucket + } + + s3key, err := w.User.S3KeyInfo() + if err != nil { + return nil, err + } + + binfo, err = grgAllowKeyOnBucket(*binfo.Id, *s3key.AccessKeyId) + if err != nil { + return nil, ErrCantAllowKey + } + + qr := w.User.Quota.DefaultWebsiteQuota() + wr := allowWebsiteDefault() + + ur := garage.NewUpdateBucketRequest() + ur.SetWebsiteAccess(*wr) + ur.SetQuotas(*qr) + + binfo, err = grgUpdateBucket(*binfo.Id, ur) + if err != nil { + return nil, ErrCantConfigureBucket + } + + return NewWebsiteView(binfo), nil +} + +func (w *WebsiteController) Delete(pretty string) error { + if pretty == "" { + return ErrEmptyBucketName + } + + website, ok := w.WebsiteIdx[pretty] + if !ok { + return ErrWebsiteNotFound + } + + binfo, err := grgGetBucket(website.Internal) + if err != nil { + return ErrFetchBucketInfo + } + + if *binfo.Objects > int64(0) { + return ErrBucketDeleteNotEmpty + } + + if *binfo.UnfinishedUploads > int32(0) { + return ErrBucketDeleteUnfinishedUpload + } + + err = grgDeleteBucket(website.Internal) + return err +} + +type WebsiteView struct { + Name *WebsiteId `json:"identified_as"` + Size QuotaStat `json:"quota_size"` + Files QuotaStat `json:"quota_files"` +} + +func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { + q := binfo.GetQuotas() + + wid := NewWebsiteIdFromBucketInfo(binfo) + size := NewQuotaStat(*binfo.Bytes, (&q).GetMaxSize(), true) + objects := NewQuotaStat(*binfo.Objects, (&q).GetMaxObjects(), false) + return &WebsiteView{wid, size, objects} +} + +type WebsitePatch struct { + Size *int64 `json:"quota_size"` +} |