store WebAddress config in datastore (#421)

moves web address config to datastore so that it can be configured by a user
in the Web UI.
rename OrgInfo struct to AppConfig.

For #363
For #378
This commit is contained in:
Victor Vrantchan 2016-11-04 16:44:38 -04:00 committed by GitHub
parent 9be166b834
commit def24499b5
13 changed files with 166 additions and 93 deletions

View file

@ -94,7 +94,7 @@ the way that the kolide server works.
createDevHosts(ds, config)
createDevQueries(ds, config)
createDevLabels(ds, config)
createDevOrgInfo(svc, config)
createDevOrgInfo(ds, config)
}
fieldKeys := []string{"method", "error"}
@ -265,15 +265,12 @@ func createDevHosts(ds kolide.Datastore, config config.KolideConfig) {
}
}
func createDevOrgInfo(svc kolide.Service, config config.KolideConfig) {
devOrgInfo := &kolide.OrgInfo{
func createDevOrgInfo(ds kolide.Datastore, config config.KolideConfig) {
devOrgInfo := &kolide.AppConfig{
OrgName: "Kolide",
OrgLogoURL: fmt.Sprintf("%s/logo.png", config.Server.Address),
}
_, err := svc.NewOrgInfo(context.Background(), kolide.OrgInfoPayload{
OrgName: &devOrgInfo.OrgName,
OrgLogoURL: &devOrgInfo.OrgLogoURL,
})
_, err := ds.NewAppConfig(devOrgInfo)
if err != nil {
initFatal(err, "creating fake org info")
}

View file

@ -39,7 +39,6 @@ type AuthConfig struct {
// AppConfig defines configs related to HTTP
type AppConfig struct {
WebAddress string
TokenKeySize int
TokenKey string
InviteTokenValidityPeriod time.Duration
@ -161,7 +160,6 @@ func (man Manager) LoadConfig() KolideConfig {
SaltKeySize: man.getConfigInt("auth.salt_key_size"),
},
App: AppConfig{
WebAddress: man.getConfigString("app.web_address"),
TokenKeySize: man.getConfigInt("app.token_key_size"),
TokenKey: man.getConfigString("app.token_key"),
InviteTokenValidityPeriod: man.getConfigDuration("app.invite_token_validity_period"),

View file

@ -8,27 +8,27 @@ import (
)
func testOrgInfo(t *testing.T, ds kolide.Datastore) {
info := &kolide.OrgInfo{
info := &kolide.AppConfig{
OrgName: "Kolide",
OrgLogoURL: "localhost:8080/logo.png",
}
info, err := ds.NewOrgInfo(info)
info, err := ds.NewAppConfig(info)
assert.Nil(t, err)
info2, err := ds.OrgInfo()
info2, err := ds.AppConfig()
assert.Nil(t, err)
assert.Equal(t, info2.OrgName, info.OrgName)
info2.OrgName = "koolide"
err = ds.SaveOrgInfo(info2)
err = ds.SaveAppConfig(info2)
assert.Nil(t, err)
info3, err := ds.OrgInfo()
info3, err := ds.AppConfig()
assert.Nil(t, err)
assert.Equal(t, info3.OrgName, info2.OrgName)
info4, err := ds.NewOrgInfo(info3)
info4, err := ds.NewAppConfig(info3)
assert.Nil(t, err)
assert.Equal(t, info4.OrgName, info3.OrgName)
}

View file

@ -26,7 +26,7 @@ var tables = [...]interface{}{
&kolide.DistributedQueryCampaignTarget{},
&kolide.Query{},
&kolide.DistributedQueryExecution{},
&kolide.OrgInfo{},
&kolide.AppConfig{},
&kolide.Invite{},
}

View file

@ -5,7 +5,7 @@ import (
"github.com/kolide/kolide-ose/server/kolide"
)
func (orm gormDB) NewOrgInfo(info *kolide.OrgInfo) (*kolide.OrgInfo, error) {
func (orm gormDB) NewAppConfig(info *kolide.AppConfig) (*kolide.AppConfig, error) {
err := orm.DB.First(info).Error
switch err {
case gorm.ErrRecordNotFound:
@ -15,14 +15,14 @@ func (orm gormDB) NewOrgInfo(info *kolide.OrgInfo) (*kolide.OrgInfo, error) {
}
return info, nil
case nil:
return info, orm.SaveOrgInfo(info)
return info, orm.SaveAppConfig(info)
default:
return nil, err
}
}
func (orm gormDB) OrgInfo() (*kolide.OrgInfo, error) {
info := &kolide.OrgInfo{}
func (orm gormDB) AppConfig() (*kolide.AppConfig, error) {
info := &kolide.AppConfig{}
err := orm.DB.First(info).Error
if err != nil {
return nil, err
@ -30,6 +30,6 @@ func (orm gormDB) OrgInfo() (*kolide.OrgInfo, error) {
return info, nil
}
func (orm gormDB) SaveOrgInfo(info *kolide.OrgInfo) error {
func (orm gormDB) SaveAppConfig(info *kolide.AppConfig) error {
return orm.DB.Save(info).Error
}

View file

@ -26,7 +26,7 @@ type inmem struct {
packQueries map[uint]*kolide.PackQuery
packTargets map[uint]*kolide.PackTarget
orginfo *kolide.OrgInfo
orginfo *kolide.AppConfig
}
func (orm *inmem) Name() string {

View file

@ -2,15 +2,16 @@ package datastore
import "github.com/kolide/kolide-ose/server/kolide"
func (orm *inmem) NewOrgInfo(info *kolide.OrgInfo) (*kolide.OrgInfo, error) {
func (orm *inmem) NewAppConfig(info *kolide.AppConfig) (*kolide.AppConfig, error) {
orm.mtx.Lock()
defer orm.mtx.Unlock()
info.ID = 1
orm.orginfo = info
return info, nil
}
func (orm *inmem) OrgInfo() (*kolide.OrgInfo, error) {
func (orm *inmem) AppConfig() (*kolide.AppConfig, error) {
orm.mtx.Lock()
defer orm.mtx.Unlock()
@ -21,7 +22,7 @@ func (orm *inmem) OrgInfo() (*kolide.OrgInfo, error) {
return nil, ErrNotFound
}
func (orm *inmem) SaveOrgInfo(info *kolide.OrgInfo) error {
func (orm *inmem) SaveAppConfig(info *kolide.AppConfig) error {
orm.mtx.Lock()
defer orm.mtx.Unlock()

View file

@ -5,32 +5,44 @@ import "golang.org/x/net/context"
// AppConfigStore contains method for saving and retrieving
// application configuration
type AppConfigStore interface {
NewOrgInfo(info *OrgInfo) (*OrgInfo, error)
OrgInfo() (*OrgInfo, error)
SaveOrgInfo(info *OrgInfo) error
NewAppConfig(info *AppConfig) (*AppConfig, error)
AppConfig() (*AppConfig, error)
SaveAppConfig(info *AppConfig) error
}
// AppConfigService provides methods for configuring
// the Kolide application
type AppConfigService interface {
NewOrgInfo(ctx context.Context, p OrgInfoPayload) (info *OrgInfo, err error)
OrgInfo(ctx context.Context) (info *OrgInfo, err error)
ModifyOrgInfo(ctx context.Context, p OrgInfoPayload) (info *OrgInfo, err error)
NewAppConfig(ctx context.Context, p AppConfigPayload) (info *AppConfig, err error)
AppConfig(ctx context.Context) (info *AppConfig, err error)
ModifyAppConfig(ctx context.Context, p AppConfigPayload) (info *AppConfig, err error)
}
// OrgInfo holds information about the current
// organization using Kolide
// AppConfig holds configuration about the Kolide application.
// AppConfig data can be managed by a Kolide API user.
type AppConfig struct {
ID uint `gorm:"primary_key"`
OrgName string
OrgLogoURL string
KolideServerURL string
}
// AppConfigPayload contains request and response format of
// the AppConfig struct.
type AppConfigPayload struct {
OrgInfo *OrgInfo `json:"org_info,omitempty"`
ServerSettings *ServerSettings `json:"server_settings,omitempty"`
}
// OrgInfo contains general info about the organization using Kolide.
type OrgInfo struct {
ID uint `gorm:"primary_key"`
OrgName string
OrgLogoURL string
OrgName *string `json:"org_name,omitempty"`
OrgLogoURL *string `json:"org_logo_url,omitempty"`
}
// OrgInfoPayload is used to accept
// OrgInfo modifications by a client
type OrgInfoPayload struct {
OrgName *string `json:"org_name"`
OrgLogoURL *string `json:"org_logo_url"`
// ServerSettings contains general settings about the kolide App.
type ServerSettings struct {
KolideServerURL *string `json:"web_address_url,omitempty"`
}
type OrderDirection int

View file

@ -14,41 +14,62 @@ type getAppConfigResponse struct {
func (r getAppConfigResponse) error() error { return r.Err }
type appConfig map[string]map[string]string
func makeGetAppConfigEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
info, err := svc.OrgInfo(ctx)
config, err := svc.AppConfig(ctx)
if err != nil {
return getAppConfigResponse{Err: err}, nil
}
config := appConfig{
"org_info": map[string]string{
"org_name": info.OrgName,
"org_logo_url": info.OrgLogoURL,
},
}
return config, nil
response := appConfigPayload(*config)
return response, nil
}
}
type modifyAppConfigRequest struct {
OrgPayload kolide.OrgInfoPayload `json:"org_info"`
ConfigPayload kolide.AppConfigPayload
}
func makeModifyAppConfigRequest(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(modifyAppConfigRequest)
info, err := svc.ModifyOrgInfo(ctx, req.OrgPayload)
config, err := svc.ModifyAppConfig(ctx, req.ConfigPayload)
if err != nil {
return getAppConfigResponse{Err: err}, nil
}
config := appConfig{
"org_info": map[string]string{
"org_name": info.OrgName,
"org_logo_url": info.OrgLogoURL,
},
}
return config, nil
response := appConfigPayload(*config)
return response, nil
}
}
func appConfigPayload(config kolide.AppConfig) kolide.AppConfigPayload {
orgInfo := func() *kolide.OrgInfo {
if config.OrgName == "" && config.OrgLogoURL == "" {
return nil
}
return &kolide.OrgInfo{
OrgName: nilString(config.OrgName),
OrgLogoURL: nilString(config.OrgLogoURL),
}
}
serverSettings := func() *kolide.ServerSettings {
if config.KolideServerURL == "" {
return nil
}
return &kolide.ServerSettings{
KolideServerURL: nilString(config.KolideServerURL),
}
}
return kolide.AppConfigPayload{
OrgInfo: orgInfo(),
ServerSettings: serverSettings(),
}
}
func nilString(s string) *string {
if s == "" {
return nil
}
return &s
}

View file

@ -8,9 +8,9 @@ import (
"golang.org/x/net/context"
)
func (mw metricsMiddleware) NewOrgInfo(ctx context.Context, p kolide.OrgInfoPayload) (*kolide.OrgInfo, error) {
func (mw metricsMiddleware) NewAppConfig(ctx context.Context, p kolide.AppConfigPayload) (*kolide.AppConfig, error) {
var (
info *kolide.OrgInfo
info *kolide.AppConfig
err error
)
defer func(begin time.Time) {
@ -18,13 +18,13 @@ func (mw metricsMiddleware) NewOrgInfo(ctx context.Context, p kolide.OrgInfoPayl
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
info, err = mw.Service.NewOrgInfo(ctx, p)
info, err = mw.Service.NewAppConfig(ctx, p)
return info, err
}
func (mw metricsMiddleware) OrgInfo(ctx context.Context) (*kolide.OrgInfo, error) {
func (mw metricsMiddleware) AppConfig(ctx context.Context) (*kolide.AppConfig, error) {
var (
info *kolide.OrgInfo
info *kolide.AppConfig
err error
)
defer func(begin time.Time) {
@ -32,13 +32,13 @@ func (mw metricsMiddleware) OrgInfo(ctx context.Context) (*kolide.OrgInfo, error
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
info, err = mw.Service.OrgInfo(ctx)
info, err = mw.Service.AppConfig(ctx)
return info, err
}
func (mw metricsMiddleware) ModifyOrgInfo(ctx context.Context, p kolide.OrgInfoPayload) (*kolide.OrgInfo, error) {
func (mw metricsMiddleware) ModifyAppConfig(ctx context.Context, p kolide.AppConfigPayload) (*kolide.AppConfig, error) {
var (
info *kolide.OrgInfo
info *kolide.AppConfig
err error
)
defer func(begin time.Time) {
@ -46,6 +46,6 @@ func (mw metricsMiddleware) ModifyOrgInfo(ctx context.Context, p kolide.OrgInfoP
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
info, err = mw.Service.ModifyOrgInfo(ctx, p)
info, err = mw.Service.ModifyAppConfig(ctx, p)
return info, err
}

View file

@ -5,41 +5,41 @@ import (
"golang.org/x/net/context"
)
func (svc service) NewOrgInfo(ctx context.Context, p kolide.OrgInfoPayload) (*kolide.OrgInfo, error) {
info := &kolide.OrgInfo{}
if p.OrgName != nil {
info.OrgName = *p.OrgName
}
if p.OrgLogoURL != nil {
info.OrgLogoURL = *p.OrgLogoURL
}
info, err := svc.ds.NewOrgInfo(info)
func (svc service) NewAppConfig(ctx context.Context, p kolide.AppConfigPayload) (*kolide.AppConfig, error) {
newConfig, err := svc.ds.NewAppConfig(fromPayload(p, kolide.AppConfig{}))
if err != nil {
return nil, err
}
return info, nil
return newConfig, nil
}
func (svc service) OrgInfo(ctx context.Context) (*kolide.OrgInfo, error) {
return svc.ds.OrgInfo()
func (svc service) AppConfig(ctx context.Context) (*kolide.AppConfig, error) {
return svc.ds.AppConfig()
}
func (svc service) ModifyOrgInfo(ctx context.Context, p kolide.OrgInfoPayload) (*kolide.OrgInfo, error) {
info, err := svc.ds.OrgInfo()
func (svc service) ModifyAppConfig(ctx context.Context, p kolide.AppConfigPayload) (*kolide.AppConfig, error) {
config, err := svc.ds.AppConfig()
if err != nil {
return nil, err
}
if p.OrgName != nil {
info.OrgName = *p.OrgName
}
if p.OrgLogoURL != nil {
info.OrgLogoURL = *p.OrgLogoURL
}
updated := fromPayload(p, *config)
err = svc.ds.SaveOrgInfo(info)
if err != nil {
if err := svc.ds.SaveAppConfig(updated); err != nil {
return nil, err
}
return info, nil
return config, nil
}
func fromPayload(p kolide.AppConfigPayload, config kolide.AppConfig) *kolide.AppConfig {
if p.OrgInfo != nil && p.OrgInfo.OrgLogoURL != nil {
config.OrgLogoURL = *p.OrgInfo.OrgLogoURL
}
if p.OrgInfo != nil && p.OrgInfo.OrgName != nil {
config.OrgName = *p.OrgInfo.OrgName
}
if p.ServerSettings != nil && p.ServerSettings.KolideServerURL != nil {
config.KolideServerURL = *p.ServerSettings.KolideServerURL
}
return &config
}

View file

@ -0,0 +1,44 @@
package service
import (
"testing"
"github.com/kolide/kolide-ose/server/datastore"
"github.com/kolide/kolide-ose/server/kolide"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestCreateOrgInfo(t *testing.T) {
ds, err := datastore.New("inmem", "")
require.Nil(t, err)
svc, err := newTestService(ds)
require.Nil(t, err)
var orgInfoTests = []struct {
infoPayload kolide.AppConfigPayload
}{
{
infoPayload: kolide.AppConfigPayload{
OrgInfo: &kolide.OrgInfo{
OrgLogoURL: stringPtr("acme.co/images/logo.png"),
OrgName: stringPtr("Acme"),
},
ServerSettings: &kolide.ServerSettings{
KolideServerURL: stringPtr("https://acme.co:8080/"),
},
},
},
}
for _, tt := range orgInfoTests {
result, err := svc.NewAppConfig(context.Background(), tt.infoPayload)
require.Nil(t, err)
payload := tt.infoPayload
assert.NotEmpty(t, result.ID)
assert.Equal(t, *payload.OrgInfo.OrgLogoURL, result.OrgLogoURL)
assert.Equal(t, *payload.OrgInfo.OrgName, result.OrgName)
assert.Equal(t, *payload.ServerSettings.KolideServerURL, result.KolideServerURL)
}
}

View file

@ -9,7 +9,7 @@ import (
func decodeModifyAppConfigRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var req modifyAppConfigRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
if err := json.NewDecoder(r.Body).Decode(&req.ConfigPayload); err != nil {
return nil, err
}
return req, nil