mount data directory to adminserver

This commit is contained in:
Wenkai Yin 2017-03-20 13:41:02 +08:00
parent 37ea4273e1
commit 3a167ddfce
14 changed files with 558 additions and 64 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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)
}