github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/swift/swift.go (about)

     1  package swift
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"net/http"
     7  	"net/url"
     8  	"strconv"
     9  
    10  	"github.com/cozy/cozy-stack/model/instance"
    11  	"github.com/cozy/cozy-stack/pkg/config/config"
    12  	"github.com/cozy/cozy-stack/web/middlewares"
    13  	"github.com/labstack/echo/v4"
    14  	"github.com/ncw/swift/v2"
    15  )
    16  
    17  // ListLayouts returns the number of instances for each Swift layouts.
    18  func ListLayouts(c echo.Context) error {
    19  	type layout struct {
    20  		Counter int      `json:"counter"`
    21  		Domains []string `json:"domains,omitempty"`
    22  	}
    23  	var layoutUnknown, layoutV3a, layoutV3b layout
    24  
    25  	flagShowDomains := false
    26  	flagParam := c.QueryParam("show_domains")
    27  	if converted, err := strconv.ParseBool(flagParam); err == nil {
    28  		flagShowDomains = converted
    29  	}
    30  
    31  	instances, err := instance.List()
    32  	if err != nil {
    33  		return err
    34  	}
    35  	for _, inst := range instances {
    36  		switch inst.SwiftLayout {
    37  		case 2:
    38  			switch inst.DBPrefix() {
    39  			case inst.Domain:
    40  				layoutV3a.Counter++
    41  				if flagShowDomains {
    42  					layoutV3a.Domains = append(layoutV3a.Domains, inst.Domain)
    43  				}
    44  			case inst.Prefix:
    45  				layoutV3b.Counter++
    46  				if flagShowDomains {
    47  					layoutV3b.Domains = append(layoutV3b.Domains, inst.Domain)
    48  				}
    49  			default:
    50  				layoutUnknown.Counter++
    51  				if flagShowDomains {
    52  					layoutUnknown.Domains = append(layoutUnknown.Domains, inst.Domain)
    53  				}
    54  			}
    55  		default:
    56  			layoutUnknown.Counter++
    57  			if flagShowDomains {
    58  				layoutUnknown.Domains = append(layoutUnknown.Domains, inst.Domain)
    59  			}
    60  		}
    61  	}
    62  
    63  	output := make(map[string]interface{})
    64  	output["unknown"] = layoutUnknown
    65  	output["v3a"] = layoutV3a
    66  	output["v3b"] = layoutV3b
    67  	output["total"] = layoutUnknown.Counter + layoutV3a.Counter + layoutV3b.Counter
    68  
    69  	return c.JSON(http.StatusOK, output)
    70  }
    71  
    72  // GetObject retrieves a Swift object from an instance
    73  func GetObject(c echo.Context) error {
    74  	i := middlewares.GetInstance(c)
    75  	object := c.Param("object")
    76  	unescaped, err := url.PathUnescape(object)
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	buf := new(bytes.Buffer)
    82  	sc := config.GetSwiftConnection()
    83  	ctx := c.Request().Context()
    84  	_, err = sc.ObjectGet(ctx, swiftContainer(i), unescaped, buf, false, nil)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	return c.Blob(http.StatusOK, echo.MIMEOctetStream, buf.Bytes())
    90  }
    91  
    92  // PutObject puts an object into Swift
    93  func PutObject(c echo.Context) error {
    94  	i := middlewares.GetInstance(c)
    95  
    96  	contentType := c.Request().Header.Get(echo.HeaderContentType)
    97  	objectName := c.Param("object")
    98  	unescaped, err := url.PathUnescape(objectName)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	sc := config.GetSwiftConnection()
   104  	ctx := c.Request().Context()
   105  	f, err := sc.ObjectCreate(ctx, swiftContainer(i), unescaped, true, "", contentType, nil)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	defer f.Close()
   110  
   111  	_, err = io.Copy(f, c.Request().Body)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	return c.JSON(http.StatusOK, nil)
   117  }
   118  
   119  // DeleteObject removes an object from Swift
   120  func DeleteObject(c echo.Context) error {
   121  	i := middlewares.GetInstance(c)
   122  	objectName := c.Param("object")
   123  	unescaped, err := url.PathUnescape(objectName)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	sc := config.GetSwiftConnection()
   129  	ctx := c.Request().Context()
   130  	err = sc.ObjectDelete(ctx, swiftContainer(i), unescaped)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	return c.JSON(http.StatusOK, nil)
   135  }
   136  
   137  // ListObjects list objects of an instance
   138  func ListObjects(c echo.Context) error {
   139  	i := middlewares.GetInstance(c)
   140  	sc := config.GetSwiftConnection()
   141  	container := swiftContainer(i)
   142  	ctx := c.Request().Context()
   143  	opts := &swift.ObjectsOpts{Limit: 10_000}
   144  
   145  	names, err := sc.ObjectNamesAll(ctx, container, opts)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	out := struct {
   151  		ObjectNameList []string `json:"objects_names"`
   152  	}{
   153  		names,
   154  	}
   155  	return c.JSON(http.StatusOK, out)
   156  }
   157  
   158  // Routes sets the routing for the swift service
   159  func Routes(router *echo.Group) {
   160  	router.GET("/layouts", ListLayouts, checkSwift)
   161  	router.GET("/vfs/:object", GetObject, checkSwift, middlewares.NeedInstance)
   162  	router.PUT("/vfs/:object", PutObject, checkSwift, middlewares.NeedInstance)
   163  	router.DELETE("/vfs/:object", DeleteObject, checkSwift, middlewares.NeedInstance)
   164  	router.GET("/vfs", ListObjects, checkSwift, middlewares.NeedInstance)
   165  }
   166  
   167  // checkSwift middleware ensures that the VFS relies on Swift
   168  func checkSwift(next echo.HandlerFunc) echo.HandlerFunc {
   169  	return func(c echo.Context) error {
   170  		if config.FsURL().Scheme != config.SchemeSwift &&
   171  			config.FsURL().Scheme != config.SchemeSwiftSecure {
   172  			return c.JSON(http.StatusBadRequest, "the configured filesystem does not rely on OpenStack Swift")
   173  		}
   174  		return next(c)
   175  	}
   176  }
   177  
   178  // swiftContainer returns the container name for an instance
   179  func swiftContainer(i *instance.Instance) string {
   180  	return "cozy-v3-" + i.DBPrefix()
   181  }