aboutsummaryrefslogblamecommitdiff
path: root/website.go
blob: cdfae8916367fd31320a5fa5d435fbfca7bd6351 (plain) (tree)
1
2
3
4
5
6
7
8
9
10



             
                                                                     

                 


     







                                                                                                                                                                     
                                                                                                                                     

                                                                                                                                                                         

 
                       




                                           
 
 













                                                                 
                                                         





                                                                      


                                          











                                                                         
                                              










                                                                         
                                                               

 
                             


                                                                          

 
                                                                  

                                                     
                                              
         
                                
                                
                                                        
                       















                                                                          














                                                                                             

                                                                               





                                             
                                               




                                                          











                                                                               
                                         
































                                                                         







                                                   
















                                                         
         








                                                      
                         




                                                             







                                                                            






                                      


                          
                                         
                                    
 
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")
	ErrCantChangeVhost              = fmt.Errorf("Can't change the vhost to the desired value. Maybe it's already used by someone else or an internal error occured")
	ErrCantRemoveOldVhost           = fmt.Errorf("The new vhost is bound to the bucket but the old one can't be removed, this is an internal error")
)

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 {
	AllowedWebsites      *QuotaStat   `json:"quota_website_count"`
	BurstBucketQuotaSize string       `json:"burst_bucket_quota_size"`
	Websites             []*WebsiteId `json:"vhosts"`
}

func (w *WebsiteController) Describe() (*WebsiteDescribe, error) {
	r := make([]*WebsiteId, 0, len(w.PrettyList))
	for _, k := range w.PrettyList {
		r = append(r, w.WebsiteIdx[k])
	}
	return &WebsiteDescribe{
		&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 "update bucket" function
	binfo, err = grgUpdateBucket(website.Internal, ur)
	if err != nil {
		return nil, ErrCantConfigureBucket
	}

	// Update the alias if the vhost field is set and different
	if patch.Vhost != nil && *patch.Vhost != "" && *patch.Vhost != pretty {
		binfo, err = grgAddGlobalAlias(website.Internal, *patch.Vhost)
		if err != nil {
			return nil, ErrCantChangeVhost
		}
		binfo, err = grgDelGlobalAlias(website.Internal, pretty)
		if err != nil {
			return nil, ErrCantRemoveOldVhost
		}
	}

	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:"vhost"`
	AccessKeyId     string     `json:"access_key_id"`
	SecretAccessKey string     `json:"secret_access_key"`
	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, 
		"not yet implemented",
		"not yet implemented",
		size, 
		objects,
	}
}

type WebsitePatch struct {
	Size  *int64  `json:"quota_size"`
	Vhost *string `json:"vhost"`
}