mirror of https://github.com/docker/docs.git
Add defaults for cache headers, and add tests to ensure that default configs can successfully be parsed
Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
parent
66a39cb30c
commit
f1d78f8d6e
|
@ -7,10 +7,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/health"
|
||||
_ "github.com/docker/distribution/registry/auth/htpasswd"
|
||||
_ "github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/signer/client"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
|
@ -59,7 +59,7 @@ func grpcTLS(configuration *viper.Viper) (*tls.Config, error) {
|
|||
}
|
||||
|
||||
// parses the configuration and returns a backing store for the TUF files
|
||||
func getStore(configuration *viper.Viper, allowedBackends []string) (
|
||||
func getStore(configuration *viper.Viper, allowedBackends []string, hRegister healthRegister) (
|
||||
storage.MetaStore, error) {
|
||||
|
||||
storeConfig, err := utils.ParseStorage(configuration, allowedBackends)
|
||||
|
@ -76,7 +76,7 @@ func getStore(configuration *viper.Viper, allowedBackends []string) (
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("Error starting DB driver: %s", err.Error())
|
||||
}
|
||||
health.RegisterPeriodicFunc(
|
||||
hRegister(
|
||||
"DB operational", store.CheckHealth, time.Second*60)
|
||||
return store, nil
|
||||
}
|
||||
|
@ -135,29 +135,36 @@ func getTrustService(configuration *viper.Viper, sFactory signerFactory,
|
|||
return notarySigner, keyAlgo, nil
|
||||
}
|
||||
|
||||
// Gets the cache configuration for GET-ting current and checksummed metadata
|
||||
// This is mainly the max-age (an integer in seconds, just like in the
|
||||
// Cache-Control header) for consistent (content-addressable) downloads and
|
||||
// current (latest version) downloads. The max-age must be between 0 and 31536000
|
||||
// (one year in seconds, which is the recommended maximum time data is cached),
|
||||
// else parsing will return an error. A max-age of 0 will disable caching for
|
||||
// that type of download (consistent or current).
|
||||
func getCacheConfig(configuration *viper.Viper) (utils.CacheControlConfig, utils.CacheControlConfig, error) {
|
||||
var cccs []utils.CacheControlConfig
|
||||
types := []string{"current_metadata", "metadata_by_checksum"}
|
||||
// Parse the cache configurations for GET-ting current and checksummed metadata,
|
||||
// returning the configuration for current (non-content-addressed) metadata
|
||||
// first, then the configuration for consistent (content-addressed) metadata
|
||||
// second. The configuration consists mainly of the max-age (an integer in seconds,
|
||||
// just like in the Cache-Control header) for each type of metadata.
|
||||
// The max-age must be between 0 and 31536000 (one year in seconds, which is
|
||||
// the recommended maximum time data is cached), else parsing will return an error.
|
||||
// A max-age of 0 will disable caching for that type of download (consistent or current).
|
||||
func getCacheConfig(configuration *viper.Viper) (current, consistent utils.CacheControlConfig, err error) {
|
||||
cccs := make(map[string]utils.CacheControlConfig)
|
||||
currentOpt, consistentOpt := "current_metadata", "consistent_metadata"
|
||||
|
||||
for _, optionName := range types {
|
||||
m := configuration.GetString(fmt.Sprintf("caching.max_age.%s", optionName))
|
||||
if m == "" {
|
||||
continue
|
||||
}
|
||||
seconds, err := strconv.Atoi(m)
|
||||
if err != nil || seconds < 0 || seconds > maxMaxAge {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"must specify a cache-control max-age between 0 and %v", maxMaxAge)
|
||||
}
|
||||
|
||||
cccs = append(cccs, utils.NewCacheControlConfig(seconds, optionName == "current_metadata"))
|
||||
defaults := map[string]int{
|
||||
currentOpt: int(notary.CurrentMetadataCacheMaxAge.Seconds()),
|
||||
consistentOpt: int(notary.ConsistentMetadataCacheMaxAge.Seconds()),
|
||||
}
|
||||
return cccs[0], cccs[1], nil
|
||||
maxMaxAge := int(notary.CacheMaxAgeLimit.Seconds())
|
||||
|
||||
for optionName, seconds := range defaults {
|
||||
m := configuration.GetString(fmt.Sprintf("caching.max_age.%s", optionName))
|
||||
if m != "" {
|
||||
seconds, err = strconv.Atoi(m)
|
||||
if err != nil || seconds < 0 || seconds > maxMaxAge {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"must specify a cache-control max-age between 0 and %v", maxMaxAge)
|
||||
}
|
||||
}
|
||||
cccs[optionName] = utils.NewCacheControlConfig(seconds, optionName == currentOpt)
|
||||
}
|
||||
current = cccs[currentOpt]
|
||||
consistent = cccs[consistentOpt]
|
||||
return
|
||||
}
|
||||
|
|
|
@ -23,10 +23,6 @@ import (
|
|||
const (
|
||||
jsonLogFormat = "json"
|
||||
DebugAddress = "localhost:8080"
|
||||
// This is the generally recommended maximum age for Cache-Control headers
|
||||
// (one year, in seconds, since one year is forever in terms of internet
|
||||
// content)
|
||||
maxMaxAge = 60 * 60 * 24 * 365
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -34,11 +30,9 @@ var (
|
|||
logFormat string
|
||||
configFile string
|
||||
envPrefix = "NOTARY_SERVER"
|
||||
mainViper = viper.New()
|
||||
)
|
||||
|
||||
func init() {
|
||||
utils.SetupViper(mainViper, envPrefix)
|
||||
// Setup flags
|
||||
flag.StringVar(&configFile, "config", "", "Path to configuration file")
|
||||
flag.BoolVar(&debug, "debug", false, "Enable the debugging server on localhost:8080")
|
||||
|
@ -50,6 +44,64 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
func parseServerConfig(configFilePath string, hRegister healthRegister) (context.Context, server.Config, error) {
|
||||
config := viper.New()
|
||||
utils.SetupViper(config, envPrefix)
|
||||
|
||||
// parse viper config
|
||||
if err := utils.ParseViper(config, configFilePath); err != nil {
|
||||
return nil, server.Config{}, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// default is error level
|
||||
lvl, err := utils.ParseLogLevel(config, logrus.ErrorLevel)
|
||||
if err != nil {
|
||||
return nil, server.Config{}, err
|
||||
}
|
||||
logrus.SetLevel(lvl)
|
||||
|
||||
// parse bugsnag config
|
||||
bugsnagConf, err := utils.ParseBugsnag(config)
|
||||
if err != nil {
|
||||
return ctx, server.Config{}, err
|
||||
}
|
||||
utils.SetUpBugsnag(bugsnagConf)
|
||||
|
||||
trust, keyAlgo, err := getTrustService(config, client.NewNotarySigner, hRegister)
|
||||
if err != nil {
|
||||
return nil, server.Config{}, err
|
||||
}
|
||||
ctx = context.WithValue(ctx, "keyAlgorithm", keyAlgo)
|
||||
|
||||
store, err := getStore(config, []string{utils.MySQLBackend, utils.MemoryBackend}, hRegister)
|
||||
if err != nil {
|
||||
return nil, server.Config{}, err
|
||||
}
|
||||
ctx = context.WithValue(ctx, "metaStore", store)
|
||||
|
||||
currentCache, consistentCache, err := getCacheConfig(config)
|
||||
if err != nil {
|
||||
return nil, server.Config{}, err
|
||||
}
|
||||
|
||||
httpAddr, tlsConfig, err := getAddrAndTLSConfig(config)
|
||||
if err != nil {
|
||||
return nil, server.Config{}, err
|
||||
}
|
||||
|
||||
return ctx, server.Config{
|
||||
Addr: httpAddr,
|
||||
TLSConfig: tlsConfig,
|
||||
Trust: trust,
|
||||
AuthMethod: config.GetString("auth.type"),
|
||||
AuthOpts: config.Get("auth.options"),
|
||||
CurrentCacheControlConfig: currentCache,
|
||||
ConsistentCacheControlConfig: consistentCache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
@ -61,63 +113,13 @@ func main() {
|
|||
// when the server starts print the version for debugging and issue logs later
|
||||
logrus.Infof("Version: %s, Git commit: %s", version.NotaryVersion, version.GitCommit)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// parse viper config
|
||||
if err := utils.ParseViper(mainViper, configFile); err != nil {
|
||||
logrus.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// default is error level
|
||||
lvl, err := utils.ParseLogLevel(mainViper, logrus.ErrorLevel)
|
||||
if err != nil {
|
||||
logrus.Fatal(err.Error())
|
||||
}
|
||||
logrus.SetLevel(lvl)
|
||||
|
||||
// parse bugsnag config
|
||||
bugsnagConf, err := utils.ParseBugsnag(mainViper)
|
||||
if err != nil {
|
||||
logrus.Fatal(err.Error())
|
||||
}
|
||||
utils.SetUpBugsnag(bugsnagConf)
|
||||
|
||||
trust, keyAlgo, err := getTrustService(mainViper,
|
||||
client.NewNotarySigner, health.RegisterPeriodicFunc)
|
||||
if err != nil {
|
||||
logrus.Fatal(err.Error())
|
||||
}
|
||||
ctx = context.WithValue(ctx, "keyAlgorithm", keyAlgo)
|
||||
|
||||
store, err := getStore(mainViper, []string{utils.MySQLBackend, utils.MemoryBackend})
|
||||
if err != nil {
|
||||
logrus.Fatal(err.Error())
|
||||
}
|
||||
ctx = context.WithValue(ctx, "metaStore", store)
|
||||
|
||||
currentCache, consistentCache, err := getCacheConfig(mainViper)
|
||||
if err != nil {
|
||||
logrus.Fatal(err.Error())
|
||||
}
|
||||
|
||||
httpAddr, tlsConfig, err := getAddrAndTLSConfig(mainViper)
|
||||
ctx, serverConfig, err := parseServerConfig(configFile, health.RegisterPeriodicFunc)
|
||||
if err != nil {
|
||||
logrus.Fatal(err.Error())
|
||||
}
|
||||
|
||||
logrus.Info("Starting Server")
|
||||
err = server.Run(
|
||||
ctx,
|
||||
server.Config{
|
||||
Addr: httpAddr,
|
||||
TLSConfig: tlsConfig,
|
||||
Trust: trust,
|
||||
AuthMethod: mainViper.GetString("auth.type"),
|
||||
AuthOpts: mainViper.Get("auth.options"),
|
||||
CurrentCacheControlConfig: currentCache,
|
||||
ConsistentCacheControlConfig: consistentCache,
|
||||
},
|
||||
)
|
||||
err = server.Run(ctx, serverConfig)
|
||||
|
||||
logrus.Error(err.Error())
|
||||
return
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/signer/client"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
|
@ -301,8 +302,16 @@ func TestGetTrustServiceTLSFailure(t *testing.T) {
|
|||
func TestGetStoreInvalid(t *testing.T) {
|
||||
config := `{"storage": {"backend": "asdf", "db_url": "/tmp/1234"}}`
|
||||
|
||||
_, err := getStore(configure(config), []string{"mysql"})
|
||||
var registerCalled = 0
|
||||
var fakeRegister = func(_ string, _ func() error, _ time.Duration) {
|
||||
registerCalled++
|
||||
}
|
||||
|
||||
_, err := getStore(configure(config), []string{"mysql"}, fakeRegister)
|
||||
assert.Error(t, err)
|
||||
|
||||
// no health function ever registered
|
||||
assert.Equal(t, 0, registerCalled)
|
||||
}
|
||||
|
||||
func TestGetStoreDBStore(t *testing.T) {
|
||||
|
@ -314,36 +323,74 @@ func TestGetStoreDBStore(t *testing.T) {
|
|||
config := fmt.Sprintf(`{"storage": {"backend": "%s", "db_url": "%s"}}`,
|
||||
utils.SqliteBackend, tmpFile.Name())
|
||||
|
||||
store, err := getStore(configure(config), []string{utils.SqliteBackend})
|
||||
var registerCalled = 0
|
||||
var fakeRegister = func(_ string, _ func() error, _ time.Duration) {
|
||||
registerCalled++
|
||||
}
|
||||
|
||||
store, err := getStore(configure(config), []string{utils.SqliteBackend}, fakeRegister)
|
||||
assert.NoError(t, err)
|
||||
_, ok := store.(*storage.SQLStorage)
|
||||
assert.True(t, ok)
|
||||
|
||||
// health function registered
|
||||
assert.Equal(t, 1, registerCalled)
|
||||
}
|
||||
|
||||
func TestGetMemoryStore(t *testing.T) {
|
||||
var registerCalled = 0
|
||||
var fakeRegister = func(_ string, _ func() error, _ time.Duration) {
|
||||
registerCalled++
|
||||
}
|
||||
|
||||
config := fmt.Sprintf(`{"storage": {"backend": "%s"}}`, utils.MemoryBackend)
|
||||
store, err := getStore(configure(config),
|
||||
[]string{utils.MySQLBackend, utils.MemoryBackend})
|
||||
[]string{utils.MySQLBackend, utils.MemoryBackend}, fakeRegister)
|
||||
assert.NoError(t, err)
|
||||
_, ok := store.(*storage.MemStorage)
|
||||
assert.True(t, ok)
|
||||
|
||||
// no health function ever registered
|
||||
assert.Equal(t, 0, registerCalled)
|
||||
}
|
||||
|
||||
func TestGetCacheConfig(t *testing.T) {
|
||||
valid := `{"caching": {"max_age": {"current_metadata": 0, "metadata_by_checksum": 31536000}}}`
|
||||
defaults := `{}`
|
||||
valid := `{"caching": {"max_age": {"current_metadata": 0, "consistent_metadata": 31536000}}}`
|
||||
invalids := []string{
|
||||
`{"caching": {"max_age": {"current_metadata": 0, "metadata_by_checksum": 31539000}}}`,
|
||||
`{"caching": {"max_age": {"current_metadata": -1, "metadata_by_checksum": 300}}}`,
|
||||
`{"caching": {"max_age": {"current_metadata": "hello", "metadata_by_checksum": 300}}}`,
|
||||
`{"caching": {"max_age": {"current_metadata": 0, "consistent_metadata": 31539000}}}`,
|
||||
`{"caching": {"max_age": {"current_metadata": -1, "consistent_metadata": 300}}}`,
|
||||
`{"caching": {"max_age": {"current_metadata": "hello", "consistent_metadata": 300}}}`,
|
||||
}
|
||||
|
||||
current, consistent, err := getCacheConfig(configure(valid))
|
||||
current, consistent, err := getCacheConfig(configure(defaults))
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, utils.NoCacheControl{}, current)
|
||||
assert.IsType(t, utils.PublicCacheControl{}, consistent)
|
||||
assert.Equal(t,
|
||||
utils.PublicCacheControl{MaxAgeInSeconds: int(notary.CurrentMetadataCacheMaxAge.Seconds()),
|
||||
MustReValidate: true}, current)
|
||||
assert.Equal(t,
|
||||
utils.PublicCacheControl{MaxAgeInSeconds: int(notary.ConsistentMetadataCacheMaxAge.Seconds())}, consistent)
|
||||
|
||||
current, consistent, err = getCacheConfig(configure(valid))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, utils.NoCacheControl{}, current)
|
||||
assert.Equal(t, utils.PublicCacheControl{MaxAgeInSeconds: 31536000}, consistent)
|
||||
|
||||
for _, invalid := range invalids {
|
||||
_, _, err := getCacheConfig(configure(invalid))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
// For sanity, make sure we can always parse the sample config
|
||||
func TestSampleConfig(t *testing.T) {
|
||||
var registerCalled = 0
|
||||
var fakeRegister = func(_ string, _ func() error, _ time.Duration) {
|
||||
registerCalled++
|
||||
}
|
||||
_, _, err := parseServerConfig("../../fixtures/server-config.json", fakeRegister)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// once for the DB, once for the trust service
|
||||
assert.Equal(t, registerCalled, 2)
|
||||
}
|
||||
|
|
7
const.go
7
const.go
|
@ -42,6 +42,13 @@ const (
|
|||
NotaryTargetsExpiry = 3 * Year
|
||||
NotarySnapshotExpiry = 3 * Year
|
||||
NotaryTimestampExpiry = 14 * Day
|
||||
|
||||
ConsistentMetadataCacheMaxAge = 30 * Day
|
||||
CurrentMetadataCacheMaxAge = 5 * time.Minute
|
||||
// CacheMaxAgeLimit is the generally recommended maximum age for Cache-Control headers
|
||||
// (one year, in seconds, since one year is forever in terms of internet
|
||||
// content)
|
||||
CacheMaxAgeLimit = 1 * Year
|
||||
)
|
||||
|
||||
// NotaryDefaultExpiries is the construct used to configure the default expiry times of
|
||||
|
|
Loading…
Reference in New Issue