Skip to content

Commit 1ef9284

Browse files
committed
MINOR: storage: implement general storage handlers
1 parent d650817 commit 1ef9284

6 files changed

Lines changed: 247 additions & 3 deletions

File tree

cmd/dataplaneapi/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ func startServer(cfg *configuration.Configuration) (reload configuration.AtomicB
157157
if storageDir != "" {
158158
cfg.HAProxy.MapsDir = path.Join(storageDir, string(storage.MapsType))
159159
cfg.HAProxy.SSLCertsDir = path.Join(storageDir, string(storage.SSLType))
160+
cfg.HAProxy.GeneralStorageDir = path.Join(storageDir, string(storage.GeneralType))
160161
cfg.HAProxy.SpoeDir = path.Join(storageDir, string(storage.SpoeType))
161162
cfg.HAProxy.SpoeTransactionDir = path.Join(storageDir, string(storage.SpoeTransactionsType))
162163
cfg.HAProxy.BackupsDir = path.Join(storageDir, string(storage.BackupsType))

configuration/configuration.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type HAProxyConfiguration struct {
5151
NodeIDFile string `long:"fid" description:"Path to file that will dataplaneapi use to write its id (not a pid) that was given to him after joining a cluster" group:"haproxy"`
5252
MapsDir string `short:"p" long:"maps-dir" description:"Path to directory of map files managed by dataplane" default:"/etc/haproxy/maps" group:"resources"`
5353
SSLCertsDir string `long:"ssl-certs-dir" description:"Path to SSL certificates directory" default:"/etc/haproxy/ssl" group:"resources"`
54+
GeneralStorageDir string `long:"general-storage-dir" description:"Path to general storage directory" default:"/etc/haproxy/general" group:"resources"`
5455
UpdateMapFiles bool `long:"update-map-files" description:"Flag used for syncing map files with runtime maps values" group:"resources"`
5556
UpdateMapFilesPeriod int64 `long:"update-map-files-period" description:"Elapsed time in seconds between two maps syncing operations" default:"10" group:"resources"`
5657
ClusterTLSCertDir string `long:"cluster-tls-dir" description:"Path where cluster tls certificates will be stored. Defaults to same directory as dataplane configuration file" group:"cluster"`

configuration/configuration_storage.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configuration/examples/example-dataplaneapi.hcl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ dataplaneapi {
1010
}
1111

1212
resources {
13-
maps_dir = "/etc/haproxy/maps"
14-
ssl_certs_dir = "/etc/haproxy/ssl"
15-
spoe_dir = "/etc/haproxy/spoe"
13+
maps_dir = "/etc/haproxy/maps"
14+
ssl_certs_dir = "/etc/haproxy/ssl"
15+
general_storage_dir = "/etc/haproxy/general"
16+
spoe_dir = "/etc/haproxy/spoe"
1617
}
1718
}
1819

configure_data_plane.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,13 @@ func configureAPI(api *operations.DataPlaneAPI) http.Handler {
659659
api.StorageReplaceStorageSSLCertificateHandler = &handlers.StorageReplaceStorageSSLCertificateHandlerImpl{Client: client, ReloadAgent: ra}
660660
api.StorageCreateStorageSSLCertificateHandler = &handlers.StorageCreateStorageSSLCertificateHandlerImpl{Client: client, ReloadAgent: ra}
661661

662+
// general file storage handlers
663+
api.StorageCreateStorageGeneralFileHandler = &handlers.StorageCreateStorageGeneralFileHandlerImpl{Client: client}
664+
api.StorageGetAllStorageGeneralFilesHandler = &handlers.StorageGetAllStorageGeneralFilesHandlerImpl{Client: client}
665+
api.StorageGetOneStorageGeneralFileHandler = &handlers.StorageGetOneStorageGeneralFileHandlerImpl{Client: client}
666+
api.StorageDeleteStorageGeneralFileHandler = &handlers.StorageDeleteStorageGeneralFileHandlerImpl{Client: client}
667+
api.StorageReplaceStorageGeneralFileHandler = &handlers.StorageReplaceStorageGeneralFileHandlerImpl{Client: client, ReloadAgent: ra}
668+
662669
// setup OpenAPI v3 specification handler
663670
api.SpecificationOpenapiv3GetOpenapiv3SpecificationHandler = specification_openapiv3.GetOpenapiv3SpecificationHandlerFunc(func(params specification_openapiv3.GetOpenapiv3SpecificationParams, principal interface{}) middleware.Responder {
664671
v2 := openapi2.Swagger{}
@@ -841,6 +848,16 @@ func configureNativeClient(cyx context.Context, haproxyOptions dataplaneapi_conf
841848
log.Fatalf("error trying to use empty string for managed map directory")
842849
}
843850

851+
if haproxyOptions.GeneralStorageDir != "" {
852+
generalStorage, err := storage.New(haproxyOptions.GeneralStorageDir, storage.GeneralType)
853+
if err != nil {
854+
log.Fatalf("error initializing General storage: %v", err)
855+
}
856+
opt = append(opt, options.GeneralStorage(generalStorage))
857+
} else {
858+
log.Fatalf("error trying to use empty string for managed general files directory")
859+
}
860+
844861
if haproxyOptions.SpoeDir != "" {
845862
prms := spoe.Params{
846863
SpoeDir: haproxyOptions.SpoeDir,

handlers/general_storage.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Copyright 2019 HAProxy Technologies
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
package handlers
17+
18+
import (
19+
"bufio"
20+
"fmt"
21+
"os"
22+
"path/filepath"
23+
"strings"
24+
25+
"github.com/go-openapi/runtime"
26+
"github.com/go-openapi/runtime/middleware"
27+
client_native "github.com/haproxytech/client-native/v3"
28+
models "github.com/haproxytech/client-native/v3/models"
29+
30+
"github.com/haproxytech/dataplaneapi/haproxy"
31+
"github.com/haproxytech/dataplaneapi/misc"
32+
"github.com/haproxytech/dataplaneapi/operations/storage"
33+
)
34+
35+
// StorageCreateStorageGeneralFileHandlerImpl implementation of the StorageCreateStorageGeneralFileHandler interface using client-native client
36+
type StorageCreateStorageGeneralFileHandlerImpl struct {
37+
Client client_native.HAProxyClient
38+
}
39+
40+
func (h *StorageCreateStorageGeneralFileHandlerImpl) Handle(params storage.CreateStorageGeneralFileParams, principal interface{}) middleware.Responder {
41+
file, ok := params.FileUpload.(*runtime.File)
42+
if !ok {
43+
return storage.NewCreateStorageGeneralFileBadRequest()
44+
}
45+
46+
gs, err := h.Client.GeneralStorage()
47+
if err != nil {
48+
e := misc.HandleError(err)
49+
return storage.NewCreateStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
50+
}
51+
52+
filename, err := gs.Create(file.Header.Filename, params.FileUpload)
53+
if err != nil {
54+
status := misc.GetHTTPStatusFromErr(err)
55+
return storage.NewCreateStorageGeneralFileDefault(status).WithPayload(misc.SetError(status, err.Error()))
56+
}
57+
58+
me := &models.GeneralFile{
59+
Description: "managed general use file",
60+
File: filename,
61+
StorageName: filepath.Base(filename),
62+
}
63+
64+
return storage.NewCreateStorageGeneralFileCreated().WithPayload(me)
65+
}
66+
67+
// StorageGetAllStorageGeneralFilesHandlerImpl implementation of the StorageGetAllStorageGeneralFilesHandler interface
68+
type StorageGetAllStorageGeneralFilesHandlerImpl struct {
69+
Client client_native.HAProxyClient
70+
}
71+
72+
// Handle executing the request and returning a response
73+
func (h *StorageGetAllStorageGeneralFilesHandlerImpl) Handle(params storage.GetAllStorageGeneralFilesParams, principal interface{}) middleware.Responder {
74+
75+
gs, err := h.Client.GeneralStorage()
76+
if err != nil {
77+
e := misc.HandleError(err)
78+
return storage.NewGetAllStorageGeneralFilesDefault(int(*e.Code)).WithPayload(e)
79+
}
80+
81+
filenames, err := gs.GetAll()
82+
if err != nil {
83+
e := misc.HandleError(err)
84+
return storage.NewGetAllStorageGeneralFilesDefault(int(*e.Code)).WithPayload(e)
85+
}
86+
87+
retFiles := models.GeneralFiles{}
88+
for _, f := range filenames {
89+
retFiles = append(retFiles, &models.GeneralFile{
90+
Description: "managed general use file",
91+
File: f,
92+
ID: "",
93+
StorageName: filepath.Base(f),
94+
})
95+
}
96+
97+
return storage.NewGetAllStorageGeneralFilesOK().WithPayload(retFiles)
98+
}
99+
100+
// StorageGetOneStorageGeneralFileHandlerImpl implementation of the StorageGetOneStorageGeneralFileHandler interface
101+
type StorageGetOneStorageGeneralFileHandlerImpl struct {
102+
Client client_native.HAProxyClient
103+
}
104+
105+
func (h *StorageGetOneStorageGeneralFileHandlerImpl) Handle(params storage.GetOneStorageGeneralFileParams, principal interface{}) middleware.Responder {
106+
gs, err := h.Client.GeneralStorage()
107+
if err != nil {
108+
e := misc.HandleError(err)
109+
return storage.NewGetOneStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
110+
}
111+
112+
filename, err := gs.Get(params.Name)
113+
if err != nil {
114+
e := misc.HandleError(err)
115+
return storage.NewGetOneStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
116+
}
117+
if filename == "" {
118+
return storage.NewGetOneStorageGeneralFileNotFound()
119+
}
120+
f, err := os.Open(filename)
121+
if err != nil {
122+
e := misc.HandleError(err)
123+
return storage.NewGetOneStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
124+
}
125+
return storage.NewGetOneStorageGeneralFileOK().WithPayload(f)
126+
}
127+
128+
// StorageDeleteStorageGeneralFileHandlerImpl implementation of the StorageDeleteStorageGeneralFileHandler interface
129+
type StorageDeleteStorageGeneralFileHandlerImpl struct {
130+
Client client_native.HAProxyClient
131+
}
132+
133+
func (h *StorageDeleteStorageGeneralFileHandlerImpl) Handle(params storage.DeleteStorageGeneralFileParams, principal interface{}) middleware.Responder {
134+
configuration, err := h.Client.Configuration()
135+
if err != nil {
136+
e := misc.HandleError(err)
137+
return storage.NewCreateStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
138+
}
139+
140+
gs, err := h.Client.GeneralStorage()
141+
if err != nil {
142+
e := misc.HandleError(err)
143+
return storage.NewCreateStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
144+
}
145+
146+
runningConf := strings.NewReader(configuration.Parser().String())
147+
148+
filename, err := gs.Get(params.Name)
149+
if err != nil {
150+
e := misc.HandleError(err)
151+
return storage.NewDeleteStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
152+
}
153+
154+
// this is far from perfect but should provide a basic level of protection
155+
scanner := bufio.NewScanner(runningConf)
156+
157+
lineNr := 0
158+
159+
for scanner.Scan() {
160+
line := strings.TrimSpace(scanner.Text())
161+
if strings.Contains(line, filename) && !strings.HasPrefix(line, "#") {
162+
errCode := misc.ErrHTTPConflict
163+
errMsg := fmt.Sprintf("rejecting attempt to delete file %s referenced in haproxy conf at line %d: %s", filename, lineNr-1, line)
164+
e := &models.Error{Code: &errCode, Message: &errMsg}
165+
return storage.NewDeleteStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
166+
}
167+
lineNr++
168+
}
169+
170+
err = gs.Delete(params.Name)
171+
if err != nil {
172+
e := misc.HandleError(err)
173+
return storage.NewDeleteStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
174+
}
175+
return storage.NewDeleteStorageGeneralFileNoContent()
176+
}
177+
178+
// StorageReplaceStorageGeneralFileHandlerImpl implementation of the StorageReplaceStorageGeneralFileHandler interface
179+
type StorageReplaceStorageGeneralFileHandlerImpl struct {
180+
Client client_native.HAProxyClient
181+
ReloadAgent haproxy.IReloadAgent
182+
}
183+
184+
func (h *StorageReplaceStorageGeneralFileHandlerImpl) Handle(params storage.ReplaceStorageGeneralFileParams, principal interface{}) middleware.Responder {
185+
gs, err := h.Client.GeneralStorage()
186+
if err != nil {
187+
e := misc.HandleError(err)
188+
return storage.NewReplaceStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
189+
}
190+
191+
_, err = gs.Replace(params.Name, params.Data)
192+
if err != nil {
193+
e := misc.HandleError(err)
194+
return storage.NewReplaceStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
195+
}
196+
197+
skipReload := false
198+
if params.SkipReload != nil {
199+
skipReload = *params.SkipReload
200+
}
201+
forceReload := false
202+
if params.ForceReload != nil {
203+
forceReload = *params.ForceReload
204+
}
205+
206+
if skipReload {
207+
return storage.NewReplaceStorageGeneralFileNoContent()
208+
}
209+
210+
if forceReload {
211+
err := h.ReloadAgent.ForceReload()
212+
if err != nil {
213+
e := misc.HandleError(err)
214+
return storage.NewReplaceStorageGeneralFileDefault(int(*e.Code)).WithPayload(e)
215+
}
216+
return storage.NewReplaceStorageGeneralFileNoContent()
217+
}
218+
rID := h.ReloadAgent.Reload()
219+
return storage.NewReplaceStorageGeneralFileAccepted().WithReloadID(rID)
220+
}

0 commit comments

Comments
 (0)