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 }