Commit ad39c186 authored by Hayley Swimelar's avatar Hayley Swimelar
Browse files

feat(configuration): allow database.enabled to accept bool and string values

parent 5791ac2b
Loading
Loading
Loading
Loading
+67 −1
Original line number Diff line number Diff line
@@ -399,10 +399,67 @@ type DatabaseMetrics struct {
	LeaseDuration time.Duration `yaml:"leaseduration,omitempty"`
}

// DatabaseEnabled is an enum allowing the user to set various policies for
// the registry database enabling behavior.
type DatabaseEnabled int

const (
	// DatabaseEnabledFalse the database is explicitly disabled.
	DatabaseEnabledFalse DatabaseEnabled = iota
	// DatabaseEnabledTrue the database is explicitly enabled.
	DatabaseEnabledTrue
	// DatabaseEnabledPrefer the database remains enabled if already enabled OR
	// there is no data detected in the container registry (fresh install).
	DatabaseEnabledPrefer
)

var databaseEnabledStrings = map[DatabaseEnabled]string{
	DatabaseEnabledFalse:  "false",
	DatabaseEnabledTrue:   "true",
	DatabaseEnabledPrefer: "prefer",
}

var stringToDatabaseEnabled = map[string]DatabaseEnabled{
	"false":  DatabaseEnabledFalse,
	"true":   DatabaseEnabledTrue,
	"prefer": DatabaseEnabledPrefer,
}

func (d *DatabaseEnabled) UnmarshalYAML(unmarshal func(any) error) error {
	var s string
	if err := unmarshal(&s); err == nil {
		if enum, ok := stringToDatabaseEnabled[s]; ok {
			*d = enum
			return nil
		}
	}

	// Convert bools to enums for backwards compatibility.
	var b bool
	if err := unmarshal(&b); err == nil {
		if b {
			*d = DatabaseEnabledTrue
			return nil
		}
		*d = DatabaseEnabledFalse
		return nil
	}

	return fmt.Errorf("invalid database.enabled value: %q, valid values: false, true, prefer", s)
}

func (d DatabaseEnabled) String() string {
	return databaseEnabledStrings[d]
}

func (d DatabaseEnabled) MarshalYAML() (any, error) {
	return d.String(), nil
}

// Database is the configuration for the registry's metadata database
type Database struct {
	// Enabled can be used to enable or bypass the metadata database
	Enabled bool `yaml:"enabled"`
	Enabled DatabaseEnabled `yaml:"enabled"`
	// Host is the database server hostname
	Host string `yaml:"host"`
	// Port is the database server port
@@ -452,6 +509,15 @@ type Database struct {
	Metrics DatabaseMetrics `yaml:"metrics,omitempty"`
}

// IsEnabled returns true if the database is in prefer mode or explicitly enabled.
func (d Database) IsEnabled() bool {
	return d.Enabled != DatabaseEnabledFalse
}

func (d Database) IsPrefer() bool {
	return d.Enabled == DatabaseEnabledPrefer
}

// BackgroundMigrations represents the configuration for the asynchronous batched background migrations in the registry.
type BackgroundMigrations struct {
	// Enabled can be used to enable or bypass the asynchronous batched background migration process
+47 −4
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ var configStruct = Configuration{
		},
	},
	Database: Database{
		Enabled:  true,
		Enabled:  DatabaseEnabledTrue,
		Host:     "localhost",
		Port:     5432,
		User:     "postgres",
@@ -333,7 +333,7 @@ func (s *ConfigSuite) TestParseSimple() {
func (s *ConfigSuite) TestParseInmemory() {
	s.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
	s.expectedConfig.Database = Database{
		Enabled: true,
		Enabled: DatabaseEnabledTrue,
		BackgroundMigrations: BackgroundMigrations{
			Enabled:       true,
			MaxJobRetries: 1,
@@ -720,7 +720,7 @@ storage: inmemory
// TestParseWithDifferentEnvDatabase validates that environment variables properly override database parameters
func (s *ConfigSuite) TestParseWithDifferentEnvDatabase() {
	expected := Database{
		Enabled:  true,
		Enabled:  DatabaseEnabledTrue,
		Host:     "127.0.0.1",
		Port:     1234,
		User:     "user",
@@ -743,7 +743,7 @@ func (s *ConfigSuite) TestParseWithDifferentEnvDatabase() {
	}
	s.expectedConfig.Database = expected

	err := os.Setenv("REGISTRY_DATABASE_DISABLE", strconv.FormatBool(expected.Enabled))
	err := os.Setenv("REGISTRY_DATABASE_DISABLE", strconv.FormatBool(expected.IsEnabled()))
	require.NoError(s.T(), err)
	err = os.Setenv("REGISTRY_DATABASE_HOST", expected.Host)
	require.NoError(s.T(), err)
@@ -3242,3 +3242,46 @@ database:

	testParameter(t, yml, "REGISTRY_DATABASE_METRICS_LEASEDURATION", tt, validator)
}

func TestParseDatabaseEnabled(t *testing.T) {
	yml := `
version: 0.1
storage: inmemory
database:
    enabled: %s
`
	tt := []parameterTest{
		{
			name:  "string true",
			value: "true",
			want:  DatabaseEnabledTrue,
		},
		{
			name:  "string false",
			value: "false",
			want:  DatabaseEnabledFalse,
		},
		{
			name:  "prefer",
			value: "prefer",
			want:  DatabaseEnabledPrefer,
		},
		{
			name:    "typo",
			value:   "perfer",
			wantErr: true,
			err:     fmt.Sprintf("invalid database.enabled value: %q, valid values: false, true, prefer", "perfer"),
		},
		{
			name:  "default",
			value: "",
			want:  DatabaseEnabledFalse,
		},
	}

	validator := func(t *testing.T, want any, got *Configuration) {
		require.Equal(t, want, got.Database.Enabled)
	}

	testParameter(t, yml, "REGISTRY_DATABASE_ENABLED", tt, validator)
}
+1 −1
Original line number Diff line number Diff line
@@ -919,7 +919,7 @@ func generateDBConfig(t *testing.T, dbHost string, dbPort int, dbUser, dbPasswor
	config := configuration.Configuration{
		Version: "0.1",
		Database: configuration.Database{
			Enabled:  true,
			Enabled:  configuration.DatabaseEnabledTrue,
			Host:     dbHost,
			Port:     dbPort,
			User:     dbUser,
+4 −4
Original line number Diff line number Diff line
@@ -613,7 +613,7 @@ func baseURLAuth(t *testing.T, opts ...configOpt) {
	}

	// The v1 API base route returns 404s if the database is not enabled.
	if env.config.Database.Enabled {
	if env.config.Database.IsEnabled() {
		gitLabV1Base, err := env.builder.BuildGitlabV1BaseURL()
		require.NoError(t, err)

@@ -650,7 +650,7 @@ func baseURLAuth(t *testing.T, opts ...configOpt) {

			if test.wantExtFeatures {
				require.Equal(t, version.ExtFeatures, resp.Header.Get("Gitlab-Container-Registry-Features"))
				require.Equal(t, strconv.FormatBool(env.config.Database.Enabled), resp.Header.Get("Gitlab-Container-Registry-Database-Enabled"))
				require.Equal(t, strconv.FormatBool(env.config.Database.IsEnabled()), resp.Header.Get("Gitlab-Container-Registry-Database-Enabled"))
			} else {
				require.Empty(t, resp.Header.Get("Gitlab-Container-Registry-Features"))
			}
@@ -707,7 +707,7 @@ func baseURLPrefix(t *testing.T, opts ...configOpt) {
	defer resp.Body.Close()

	// The V1 API base route returns 404s if the database is not enabled.
	if env.config.Database.Enabled {
	if env.config.Database.IsEnabled() {
		require.Equal(t, http.StatusOK, resp.StatusCode)
		require.Equal(t, "application/json", resp.Header.Get("Content-Type"))
		require.Equal(t, "2", resp.Header.Get("Content-Length"))
@@ -3108,7 +3108,7 @@ func tagsGet(t *testing.T, opts ...configOpt) {

	for _, test := range tt {
		t.Run(test.name, func(t *testing.T) {
			if !test.runWithoutDBEnabled && !env.config.Database.Enabled {
			if !test.runWithoutDBEnabled && !env.config.Database.IsEnabled() {
				t.Skip("skipping test because the metadata database is not enabled")
			}

+11 −11
Original line number Diff line number Diff line
@@ -63,12 +63,12 @@ func TestBlobAPI_Get_BlobNotInDatabase(t *testing.T) {
	env := newTestEnv(t)
	defer env.Shutdown()

	if !env.config.Database.Enabled {
	if !env.config.Database.IsEnabled() {
		t.Skip("skipping test because the metadata database is not enabled")
	}

	// Disable the database so writes only go to the filesytem.
	env.config.Database.Enabled = false
	env.config.Database.Enabled = configuration.DatabaseEnabledFalse

	// create repository with a layer
	args := makeBlobArgs(t)
@@ -76,7 +76,7 @@ func TestBlobAPI_Get_BlobNotInDatabase(t *testing.T) {
	blobURL := pushLayer(t, env.builder, args.imageName, args.layerDigest, uploadURLBase, args.layerFile)

	// Enable the database again so that reads first check the database.
	env.config.Database.Enabled = true
	env.config.Database.Enabled = configuration.DatabaseEnabledTrue

	// fetch layer
	res, err := http.Get(blobURL)
@@ -836,7 +836,7 @@ func TestManifestAPI_Put_Schema2LayersNotAssociatedWithRepositoryButArePresentIn
	tagName := "schema2missinglayerstag"
	repoPath := "schema2/missinglayers"

	if !env.config.Database.Enabled {
	if !env.config.Database.IsEnabled() {
		t.Skip("skipping test because the metadata database is not enabled")
	}

@@ -875,13 +875,13 @@ func TestManifestAPI_Put_Schema2LayersNotAssociatedWithRepositoryButArePresentIn
		pushLayer(t, env.builder, fakeRepoRef, dgst, uploadURLBase, bytes.NewReader(layerBytes))

		// Disable the database so writes only go to the filesytem.
		env.config.Database.Enabled = false
		env.config.Database.Enabled = configuration.DatabaseEnabledFalse

		uploadURLBase, _ = startPushLayer(t, env, repoRef)
		pushLayer(t, env.builder, repoRef, dgst, uploadURLBase, bytes.NewReader(layerBytes))

		// Enable the database again so that reads first check the database.
		env.config.Database.Enabled = true
		env.config.Database.Enabled = configuration.DatabaseEnabledTrue

		testManifest.Layers[i] = distribution.Descriptor{
			Digest:    dgst,
@@ -1091,7 +1091,7 @@ func TestManifestAPI_Get_Schema1(t *testing.T) {
	defer env.Shutdown()

	// Seed manifest in database directly since schema1 manifests are unpushable.
	if env.config.Database.Enabled {
	if env.config.Database.IsEnabled() {
		repositoryStore := datastore.NewRepositoryStore(env.db.Primary())
		dbRepo, err := repositoryStore.CreateByPath(env.ctx, preseededSchema1RepoPath)
		require.NoError(t, err)
@@ -1206,7 +1206,7 @@ func TestManifestAPI_Delete_ManifestReferencedByList(t *testing.T) {
	env := newTestEnv(t, withDelete)
	defer env.Shutdown()

	if !env.config.Database.Enabled {
	if !env.config.Database.IsEnabled() {
		t.Skip("skipping test because the metadata database is not enabled")
	}

@@ -1233,7 +1233,7 @@ func TestManifestAPI_Put_DatabaseEnabled_InvalidConfigMediaType(t *testing.T) {
	env := newTestEnv(t)
	defer env.Shutdown()

	if !env.config.Database.Enabled {
	if !env.config.Database.IsEnabled() {
		t.Skip("skipping test because the metadata database is not enabled")
	}

@@ -2050,7 +2050,7 @@ func TestManifestAPI_Get_Config(t *testing.T) {
	defer env.Shutdown()

	// disable the database so writes only go to the filesystem
	env.config.Database.Enabled = false
	env.config.Database.Enabled = configuration.DatabaseEnabledFalse

	// create repository with a manifest
	repo, err := reference.WithName("foo/bar")
@@ -3102,7 +3102,7 @@ func TestManifestAPI_Put_ImmutableTags(t *testing.T) {
	env := newTestEnv(t)
	t.Cleanup(env.Shutdown)

	if !env.config.Database.Enabled {
	if !env.config.Database.IsEnabled() {
		t.Skip("skipping test because the metadata database is not enabled")
	}

Loading