From 3a167ddfceee3f6992730a91519eca354cde301f Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Mon, 20 Mar 2017 13:41:02 +0800 Subject: [PATCH] mount data directory to adminserver --- src/adminserver/api/cfg.go | 53 +------------ src/adminserver/api/cfg_test.go | 9 +-- src/adminserver/api/systeminfo.go | 45 +++++++++++ src/adminserver/api/systeminfo_test.go | 74 ++++++++++++++++++ src/adminserver/auth/auth.go | 65 ++++++++++++++++ src/adminserver/auth/auth_test.go | 57 ++++++++++++++ src/adminserver/handlers/handler.go | 76 +++++++++++++++++++ src/adminserver/handlers/handlers_test.go | 73 ++++++++++++++++++ src/adminserver/{ => handlers}/router.go | 5 +- src/adminserver/main.go | 7 +- .../systeminfo/imagestorage/driver.go | 35 +++++++++ .../imagestorage/filesystem/driver.go | 56 ++++++++++++++ .../imagestorage/filesystem/driver_test.go | 35 +++++++++ src/adminserver/systeminfo/systeminfo.go | 32 ++++++++ 14 files changed, 558 insertions(+), 64 deletions(-) create mode 100644 src/adminserver/api/systeminfo.go create mode 100644 src/adminserver/api/systeminfo_test.go create mode 100644 src/adminserver/auth/auth.go create mode 100644 src/adminserver/auth/auth_test.go create mode 100644 src/adminserver/handlers/handler.go create mode 100644 src/adminserver/handlers/handlers_test.go rename src/adminserver/{ => handlers}/router.go (88%) create mode 100644 src/adminserver/systeminfo/imagestorage/driver.go create mode 100644 src/adminserver/systeminfo/imagestorage/filesystem/driver.go create mode 100644 src/adminserver/systeminfo/imagestorage/filesystem/driver_test.go create mode 100644 src/adminserver/systeminfo/systeminfo.go diff --git a/src/adminserver/api/cfg.go b/src/adminserver/api/cfg.go index 269e81a0c..f61141554 100644 --- a/src/adminserver/api/cfg.go +++ b/src/adminserver/api/cfg.go @@ -19,40 +19,13 @@ import ( "encoding/json" "io/ioutil" "net/http" - "os" cfg "github.com/vmware/harbor/src/adminserver/systemcfg" "github.com/vmware/harbor/src/common/utils/log" ) -func isAuthenticated(r *http.Request) (bool, error) { - uiSecret := os.Getenv("UI_SECRET") - jobserviceSecret := os.Getenv("JOBSERVICE_SECRET") - c, err := r.Cookie("secret") - if err != nil { - if err == http.ErrNoCookie { - return false, nil - } - return false, err - } - return c != nil && (c.Value == uiSecret || - c.Value == jobserviceSecret), nil -} - // ListCfgs lists configurations func ListCfgs(w http.ResponseWriter, r *http.Request) { - authenticated, err := isAuthenticated(r) - if err != nil { - log.Errorf("failed to check whether the request is authenticated or not: %v", err) - handleInternalServerError(w) - return - } - - if !authenticated { - handleUnauthorized(w) - return - } - cfg, err := cfg.GetSystemCfg() if err != nil { log.Errorf("failed to get system configurations: %v", err) @@ -73,18 +46,6 @@ func ListCfgs(w http.ResponseWriter, r *http.Request) { // UpdateCfgs updates configurations func UpdateCfgs(w http.ResponseWriter, r *http.Request) { - authenticated, err := isAuthenticated(r) - if err != nil { - log.Errorf("failed to check whether the request is authenticated or not: %v", err) - handleInternalServerError(w) - return - } - - if !authenticated { - handleUnauthorized(w) - return - } - b, err := ioutil.ReadAll(r.Body) if err != nil { log.Errorf("failed to read request body: %v", err) @@ -107,19 +68,7 @@ func UpdateCfgs(w http.ResponseWriter, r *http.Request) { // ResetCfgs resets configurations from environment variables func ResetCfgs(w http.ResponseWriter, r *http.Request) { - authenticated, err := isAuthenticated(r) - if err != nil { - log.Errorf("failed to check whether the request is authenticated or not: %v", err) - handleInternalServerError(w) - return - } - - if !authenticated { - handleUnauthorized(w) - return - } - - if err = cfg.Reset(); err != nil { + if err := cfg.Reset(); err != nil { log.Errorf("failed to reset system configurations: %v", err) handleInternalServerError(w) return diff --git a/src/adminserver/api/cfg_test.go b/src/adminserver/api/cfg_test.go index f97e87947..bfaf2fd7f 100644 --- a/src/adminserver/api/cfg_test.go +++ b/src/adminserver/api/cfg_test.go @@ -79,19 +79,12 @@ func TestConfigAPI(t *testing.T) { return } - w := httptest.NewRecorder() - ListCfgs(w, r) - if w.Code != http.StatusUnauthorized { - t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusUnauthorized) - return - } - r.AddCookie(&http.Cookie{ Name: "secret", Value: secret, }) - w = httptest.NewRecorder() + w := httptest.NewRecorder() ListCfgs(w, r) if w.Code != http.StatusOK { t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK) diff --git a/src/adminserver/api/systeminfo.go b/src/adminserver/api/systeminfo.go new file mode 100644 index 000000000..e5f345b2b --- /dev/null +++ b/src/adminserver/api/systeminfo.go @@ -0,0 +1,45 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package api + +import ( + "encoding/json" + "net/http" + + "github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage" + "github.com/vmware/harbor/src/common/utils/log" +) + +// Capacity handles /api/systeminfo/capacity and returns system capacity +func Capacity(w http.ResponseWriter, r *http.Request) { + capacity, err := imagestorage.GlobalDriver.Cap() + if err != nil { + log.Errorf("failed to get capacity: %v", err) + handleInternalServerError(w) + return + } + + b, err := json.Marshal(capacity) + if err != nil { + log.Errorf("failed to marshal capacity: %v", err) + handleInternalServerError(w) + return + } + + if _, err = w.Write(b); err != nil { + log.Errorf("failed to write response: %v", err) + } +} diff --git a/src/adminserver/api/systeminfo_test.go b/src/adminserver/api/systeminfo_test.go new file mode 100644 index 000000000..9c37b7828 --- /dev/null +++ b/src/adminserver/api/systeminfo_test.go @@ -0,0 +1,74 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package api + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage" +) + +type fakeImageStorageDriver struct { + capacity *imagestorage.Capacity + err error +} + +func (f *fakeImageStorageDriver) Name() string { + return "fake" +} + +func (f *fakeImageStorageDriver) Cap() (*imagestorage.Capacity, error) { + return f.capacity, f.err +} + +func TestCapacity(t *testing.T) { + cases := []struct { + driver imagestorage.Driver + responseCode int + capacity *imagestorage.Capacity + }{ + {&fakeImageStorageDriver{nil, errors.New("error")}, http.StatusInternalServerError, nil}, + {&fakeImageStorageDriver{&imagestorage.Capacity{100, 90}, nil}, http.StatusOK, &imagestorage.Capacity{100, 90}}, + } + + req, err := http.NewRequest("", "", nil) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + for _, c := range cases { + imagestorage.GlobalDriver = c.driver + w := httptest.NewRecorder() + Capacity(w, req) + assert.Equal(t, c.responseCode, w.Code, "unexpected response code") + if c.responseCode == http.StatusOK { + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatalf("failed to read from response body: %v", err) + } + capacity := &imagestorage.Capacity{} + if err = json.Unmarshal(b, capacity); err != nil { + t.Fatalf("failed to unmarshal: %v", err) + } + assert.Equal(t, c.capacity, capacity) + } + } +} diff --git a/src/adminserver/auth/auth.go b/src/adminserver/auth/auth.go new file mode 100644 index 000000000..2345a9959 --- /dev/null +++ b/src/adminserver/auth/auth.go @@ -0,0 +1,65 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package auth + +import ( + "net/http" +) + +// Authenticator defines Authenticate function to authenticate requests +type Authenticator interface { + // Authenticate the request, if there is no error, the bool value + // determines whether the request is authenticated or not + Authenticate(req *http.Request) (bool, error) +} + +type secretAuthenticator struct { + secrets map[string]string +} + +// NewSecretAuthenticator returns an instance of secretAuthenticator +func NewSecretAuthenticator(secrets map[string]string) Authenticator { + return &secretAuthenticator{ + secrets: secrets, + } +} + +// Authenticate the request according the secret +func (s *secretAuthenticator) Authenticate(req *http.Request) (bool, error) { + if len(s.secrets) == 0 { + return true, nil + } + + secret, err := req.Cookie("secret") + if err != nil { + if err == http.ErrNoCookie { + return false, nil + } + return false, err + } + + if secret == nil { + return false, nil + } + + for _, v := range s.secrets { + if secret.Value == v { + return true, nil + } + } + + return false, nil +} diff --git a/src/adminserver/auth/auth_test.go b/src/adminserver/auth/auth_test.go new file mode 100644 index 000000000..e74028fa5 --- /dev/null +++ b/src/adminserver/auth/auth_test.go @@ -0,0 +1,57 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package auth + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAuthenticate(t *testing.T) { + secret := "correct" + req1, err := http.NewRequest("", "", nil) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + + req2, err := http.NewRequest("", "", nil) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + req2.AddCookie(&http.Cookie{ + Name: "secret", + Value: secret, + }) + + cases := []struct { + secrets map[string]string + req *http.Request + result bool + }{ + {nil, req1, true}, + {map[string]string{"secret1": "incorrect"}, req2, false}, + {map[string]string{"secret1": "incorrect", "secret2": secret}, req2, true}, + } + + for _, c := range cases { + authenticator := NewSecretAuthenticator(c.secrets) + authenticated, err := authenticator.Authenticate(c.req) + assert.Nil(t, err, "unexpected error") + assert.Equal(t, c.result, authenticated, "unexpected result") + } +} diff --git a/src/adminserver/handlers/handler.go b/src/adminserver/handlers/handler.go new file mode 100644 index 000000000..3ebc621cd --- /dev/null +++ b/src/adminserver/handlers/handler.go @@ -0,0 +1,76 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package handlers + +import ( + "net/http" + "os" + + gorilla_handlers "github.com/gorilla/handlers" + "github.com/vmware/harbor/src/adminserver/auth" + "github.com/vmware/harbor/src/common/utils/log" +) + +func NewHandler() http.Handler { + h := newRouter() + secrets := map[string]string{ + "uiSecret": os.Getenv("UI_SECRET"), + "jobserviceSecret": os.Getenv("JOBSERVICE_SECRET"), + } + h = newAuthHandler(auth.NewSecretAuthenticator(secrets), h) + h = gorilla_handlers.LoggingHandler(os.Stdout, h) + return h +} + +type authHandler struct { + authenticator auth.Authenticator + handler http.Handler +} + +func newAuthHandler(authenticator auth.Authenticator, handler http.Handler) http.Handler { + return &authHandler{ + authenticator: authenticator, + handler: handler, + } +} + +func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if a.authenticator == nil { + if a.handler != nil { + a.handler.ServeHTTP(w, r) + } + return + } + + valid, err := a.authenticator.Authenticate(r) + if err != nil { + log.Errorf("failed to authenticate request: %v", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), + http.StatusInternalServerError) + return + } + + if !valid { + http.Error(w, http.StatusText(http.StatusUnauthorized), + http.StatusUnauthorized) + return + } + + if a.handler != nil { + a.handler.ServeHTTP(w, r) + } + return +} diff --git a/src/adminserver/handlers/handlers_test.go b/src/adminserver/handlers/handlers_test.go new file mode 100644 index 000000000..ba1a393a3 --- /dev/null +++ b/src/adminserver/handlers/handlers_test.go @@ -0,0 +1,73 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package handlers + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/harbor/src/adminserver/auth" +) + +type fakeAuthenticator struct { + authenticated bool + err error +} + +func (f *fakeAuthenticator) Authenticate(req *http.Request) (bool, error) { + return f.authenticated, f.err +} + +type fakeHandler struct { + responseCode int +} + +func (f *fakeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(f.responseCode) +} + +func TestNewAuthHandler(t *testing.T) { + cases := []struct { + authenticator auth.Authenticator + handler http.Handler + responseCode int + }{ + + {nil, nil, http.StatusOK}, + {&fakeAuthenticator{ + authenticated: false, + err: nil, + }, nil, http.StatusUnauthorized}, + {&fakeAuthenticator{ + authenticated: false, + err: errors.New("error"), + }, nil, http.StatusInternalServerError}, + {&fakeAuthenticator{ + authenticated: true, + err: nil, + }, &fakeHandler{http.StatusNotFound}, http.StatusNotFound}, + } + + for _, c := range cases { + handler := newAuthHandler(c.authenticator, c.handler) + w := httptest.NewRecorder() + handler.ServeHTTP(w, nil) + assert.Equal(t, c.responseCode, w.Code, "unexpected response code") + } +} diff --git a/src/adminserver/router.go b/src/adminserver/handlers/router.go similarity index 88% rename from src/adminserver/router.go rename to src/adminserver/handlers/router.go index da996d0f9..755fd9f73 100644 --- a/src/adminserver/router.go +++ b/src/adminserver/handlers/router.go @@ -13,7 +13,7 @@ limitations under the License. */ -package main +package handlers import ( "net/http" @@ -22,10 +22,11 @@ import ( "github.com/vmware/harbor/src/adminserver/api" ) -func newHandler() http.Handler { +func newRouter() http.Handler { r := mux.NewRouter() r.HandleFunc("/api/configurations", api.ListCfgs).Methods("GET") r.HandleFunc("/api/configurations", api.UpdateCfgs).Methods("PUT") r.HandleFunc("/api/configurations/reset", api.ResetCfgs).Methods("POST") + r.HandleFunc("/api/systeminfo/capacity", api.Capacity).Methods("GET") return r } diff --git a/src/adminserver/main.go b/src/adminserver/main.go index 1fa0e5129..a50ecf18c 100644 --- a/src/adminserver/main.go +++ b/src/adminserver/main.go @@ -19,8 +19,9 @@ import ( "net/http" "os" - "github.com/gorilla/handlers" + "github.com/vmware/harbor/src/adminserver/handlers" syscfg "github.com/vmware/harbor/src/adminserver/systemcfg" + sysinfo "github.com/vmware/harbor/src/adminserver/systeminfo" "github.com/vmware/harbor/src/common/utils/log" ) @@ -47,13 +48,15 @@ func main() { } log.Info("system initialization completed") + sysinfo.Init() + port := os.Getenv("PORT") if len(port) == 0 { port = "80" } server := &Server{ Port: port, - Handler: handlers.LoggingHandler(os.Stdout, newHandler()), + Handler: handlers.NewHandler(), } if err := server.Serve(); err != nil { log.Fatal(err) diff --git a/src/adminserver/systeminfo/imagestorage/driver.go b/src/adminserver/systeminfo/imagestorage/driver.go new file mode 100644 index 000000000..bfba924a6 --- /dev/null +++ b/src/adminserver/systeminfo/imagestorage/driver.go @@ -0,0 +1,35 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package imagestorage + +// GlobalDriver is a global image storage driver +var GlobalDriver Driver + +// Capacity holds information about capaticy of image storage +type Capacity struct { + // total size(byte) + Total uint64 `json:"total"` + // available size(byte) + Free uint64 `json:"free"` +} + +// Driver defines methods that an image storage driver must implement +type Driver interface { + // Name returns a human-readable name of the driver + Name() string + // Cap returns the capacity of the image storage + Cap() (*Capacity, error) +} diff --git a/src/adminserver/systeminfo/imagestorage/filesystem/driver.go b/src/adminserver/systeminfo/imagestorage/filesystem/driver.go new file mode 100644 index 000000000..63ed2905f --- /dev/null +++ b/src/adminserver/systeminfo/imagestorage/filesystem/driver.go @@ -0,0 +1,56 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package filesystem + +import ( + "syscall" + + storage "github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage" +) + +const ( + driverName = "filesystem" +) + +type driver struct { + path string +} + +// NewDriver returns an instance of filesystem driver +func NewDriver(path string) storage.Driver { + return &driver{ + path: path, + } +} + +// Name returns a human-readable name of the fielsystem driver +func (d *driver) Name() string { + return driverName +} + +// Cap returns the capacity of the filesystem storage +func (d *driver) Cap() (*storage.Capacity, error) { + var stat syscall.Statfs_t + err := syscall.Statfs(d.path, &stat) + if err != nil { + return nil, err + } + + return &storage.Capacity{ + Total: stat.Blocks * uint64(stat.Bsize), + Free: stat.Bavail * uint64(stat.Bsize), + }, nil +} diff --git a/src/adminserver/systeminfo/imagestorage/filesystem/driver_test.go b/src/adminserver/systeminfo/imagestorage/filesystem/driver_test.go new file mode 100644 index 000000000..c51feca7b --- /dev/null +++ b/src/adminserver/systeminfo/imagestorage/filesystem/driver_test.go @@ -0,0 +1,35 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package filesystem + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestName(t *testing.T) { + path := "/tmp" + driver := NewDriver(path) + assert.Equal(t, driver.Name(), driverName, "unexpected driver name") +} + +func TestCap(t *testing.T) { + path := "/tmp" + driver := NewDriver(path) + _, err := driver.Cap() + assert.Nil(t, err, "unexpected error") +} diff --git a/src/adminserver/systeminfo/systeminfo.go b/src/adminserver/systeminfo/systeminfo.go new file mode 100644 index 000000000..8d98a4681 --- /dev/null +++ b/src/adminserver/systeminfo/systeminfo.go @@ -0,0 +1,32 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package systeminfo + +import ( + "os" + + "github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage" + "github.com/vmware/harbor/src/adminserver/systeminfo/imagestorage/filesystem" +) + +// Init image storage driver +func Init() { + path := os.Getenv("IMAGE_STORE_PATH") + if len(path) == 0 { + path = "/data" + } + imagestorage.GlobalDriver = filesystem.NewDriver(path) +}