mirror of
https://github.com/go-sigma/sigma.git
synced 2026-10-28 14:34:34 +08:00
feat(config): initialize namespaces from configuration
Replace the hard-coded library namespace seed with an idempotent bootstrap task driven by a new namespace.initialize config list, so users can declare any number of namespaces and set the visibility of each one (public or private). The task only creates namespaces that do not exist yet. Remove the seed INSERT from the four 0001 migrations and wire the new config into the example configs, the docs and the Helm chart (values + configmap).
This commit is contained in:
1 parent
93cf7c1946
commit
c70127b0d1
16 files changed
+248
-70
No files matched your search
@@ -65,6 +65,11 @@ namespace:
|
||||
autoCreate: false
|
||||
# the automatic created namespace visibility, available: public, private
|
||||
visibility: public
|
||||
# namespaces created at startup; each entry is created when missing,
|
||||
# and each entry may set its own visibility (public or private)
|
||||
initialize:
|
||||
- name: library
|
||||
visibility: public
|
||||
|
||||
http:
|
||||
# endpoint can be a domain or domain with port, eg: http://sigma.test.io, https://sigma.test.io:30080, http://127.0.0.1:3000
|
||||
|
||||
@@ -67,6 +67,11 @@ namespace:
|
||||
autoCreate: true
|
||||
# the automatic created namespace visibility, available: public, private
|
||||
visibility: public
|
||||
# namespaces created at startup; each entry is created when missing,
|
||||
# and each entry may set its own visibility (public or private)
|
||||
initialize:
|
||||
- name: library
|
||||
visibility: public
|
||||
|
||||
http:
|
||||
# endpoint can be a domain or domain with port, eg: http://sigma.test.io, https://sigma.test.io:30080, http://127.0.0.1:3000
|
||||
|
||||
@@ -71,6 +71,11 @@ namespace:
|
||||
autoCreate: false
|
||||
# the automatic created namespace visibility, available: public, private
|
||||
visibility: public
|
||||
# namespaces created at startup; each entry is created when missing,
|
||||
# and each entry may set its own visibility (public or private)
|
||||
initialize:
|
||||
- name: library
|
||||
visibility: public
|
||||
|
||||
http:
|
||||
# endpoint can be a domain or domain with port, eg: http://sigma.test.io, https://sigma.test.io:30080, http://127.0.0.1:3000
|
||||
|
||||
@@ -91,6 +91,11 @@ namespace:
|
||||
autoCreate: false
|
||||
# the automatic created namespace visibility, available: public, private
|
||||
visibility: public
|
||||
# namespaces created at startup; each entry is created when missing,
|
||||
# and each entry may set its own visibility (public or private)
|
||||
initialize:
|
||||
- name: library
|
||||
visibility: public
|
||||
|
||||
http:
|
||||
# endpoint can be a domain or domain with port, eg: http://sigma.test.io, https://sigma.test.io:30080, http://127.0.0.1:3000
|
||||
|
||||
@@ -49,6 +49,12 @@ data:
|
||||
minIdleConns: {{ .Values.external.redis.minIdleConns | default 0 }}
|
||||
maxRetries: {{ .Values.external.redis.maxRetries | default 0 }}
|
||||
{{- end }}
|
||||
namespace:
|
||||
initialize:
|
||||
{{- range .Values.config.namespace.initialize }}
|
||||
- name: {{ .name | quote }}
|
||||
visibility: {{ .visibility | quote }}
|
||||
{{- end }}
|
||||
http:
|
||||
internalEndpoint: http://{{ include "sigma.distribution" . }}:{{ .Values.service.distribution.port }}
|
||||
server: 0.0.0.0:{{.Values.service.server.port}}
|
||||
|
||||
@@ -154,6 +154,12 @@ config:
|
||||
redis:
|
||||
url: ""
|
||||
#ex: redis://:sigma@localhost:6789/0
|
||||
namespace:
|
||||
# namespaces created at startup; each entry is created when missing,
|
||||
# and each entry may set its own visibility (public or private)
|
||||
initialize:
|
||||
- name: library
|
||||
visibility: public
|
||||
http:
|
||||
server: 0.0.0.0:3000
|
||||
worker: 0.0.0.0:3001
|
||||
|
||||
@@ -76,6 +76,11 @@ namespace:
|
||||
autoCreate: false
|
||||
# the automatic created namespace visibility, available: public, private
|
||||
visibility: public
|
||||
# namespaces created at startup; each entry is created when missing,
|
||||
# and each entry may set its own visibility (public or private)
|
||||
initialize:
|
||||
- name: library
|
||||
visibility: public
|
||||
|
||||
http:
|
||||
# endpoint can be a domain or domain with port, eg: http://sigma.test.io, https://sigma.test.io:30080, http://127.0.0.1:3000
|
||||
|
||||
@@ -80,6 +80,11 @@ namespace:
|
||||
autoCreate: false
|
||||
# the automatic created namespace visibility, available: public, private
|
||||
visibility: public
|
||||
# namespaces created at startup; each entry is created when missing,
|
||||
# and each entry may set its own visibility (public or private)
|
||||
initialize:
|
||||
- name: library
|
||||
visibility: public
|
||||
|
||||
http:
|
||||
# endpoint can be a domain or domain with port, eg: http://sigma.test.io, https://sigma.test.io:30080, http://127.0.0.1:3000
|
||||
|
||||
@@ -32,6 +32,7 @@ var bootstrapTasks = []bootstrapTask{
|
||||
{name: "service", run: service.InitDigContainer},
|
||||
{name: "signing", run: signing},
|
||||
{name: "user", run: initUser},
|
||||
{name: "namespace", run: initNamespaces},
|
||||
}
|
||||
|
||||
// Initialize runs all registered bootstrap tasks.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2026 sigma
|
||||
//
|
||||
// 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 bootstrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"go.uber.org/dig"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/go-sigma/sigma/pkg/config"
|
||||
"github.com/go-sigma/sigma/pkg/dal/models"
|
||||
reponamespace "github.com/go-sigma/sigma/pkg/dal/repository/namespace"
|
||||
"github.com/go-sigma/sigma/pkg/utils/uuid"
|
||||
)
|
||||
|
||||
type initNamespacesParams struct {
|
||||
dig.In
|
||||
|
||||
Config *config.Configuration
|
||||
NamespaceRepository reponamespace.NamespaceRepository
|
||||
}
|
||||
|
||||
func initNamespaces(digCon *dig.Container) error {
|
||||
return digCon.Invoke(initNamespacesWithParams)
|
||||
}
|
||||
|
||||
// initNamespacesWithParams ensures every namespace declared under
|
||||
// namespace.initialize exists. It is idempotent and safe to run on every
|
||||
// startup, so users can declare any number of namespaces with their own
|
||||
// visibility.
|
||||
func initNamespacesWithParams(params initNamespacesParams) error {
|
||||
ctx := context.Background()
|
||||
for _, item := range params.Config.Namespace.Initialize {
|
||||
if err := initNamespace(ctx, params.NamespaceRepository, item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initNamespace(ctx context.Context, namespaceRepository reponamespace.NamespaceRepository, item config.ConfigurationNamespaceInit) error {
|
||||
_, err := namespaceRepository.GetByName(ctx, item.Name)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
slog.Error("get namespace failed", "err", err, "namespace", item.Name)
|
||||
return fmt.Errorf("get namespace %q failed: %w", item.Name, err)
|
||||
}
|
||||
|
||||
err = namespaceRepository.Create(ctx, &models.Namespace{
|
||||
ID: uuid.NewV7String(),
|
||||
Name: item.Name,
|
||||
Visibility: item.Visibility,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("create namespace failed", "err", err, "namespace", item.Name)
|
||||
return fmt.Errorf("create namespace %q failed: %w", item.Name, err)
|
||||
}
|
||||
slog.Info("namespace initialized", "namespace", item.Name, "visibility", item.Visibility)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2026 sigma
|
||||
//
|
||||
// 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 bootstrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/dig"
|
||||
"go.uber.org/mock/gomock"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/go-sigma/sigma/pkg/api/enums"
|
||||
"github.com/go-sigma/sigma/pkg/config"
|
||||
"github.com/go-sigma/sigma/pkg/dal/models"
|
||||
reponamespace "github.com/go-sigma/sigma/pkg/dal/repository/namespace"
|
||||
"github.com/go-sigma/sigma/pkg/logger"
|
||||
)
|
||||
|
||||
func TestInitNamespaces(t *testing.T) {
|
||||
logger.SetLevel("debug")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace config.ConfigurationNamespace
|
||||
genDigCon func(*testing.T, *config.Configuration) *dig.Container
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
namespace: config.ConfigurationNamespace{},
|
||||
genDigCon: func(t *testing.T, _ *config.Configuration) *dig.Container {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := reponamespace.NewMockNamespaceRepository(ctrl)
|
||||
digCon := dig.New()
|
||||
require.NoError(t, digCon.Provide(func() reponamespace.NamespaceRepository { return mock }))
|
||||
return digCon
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ensure",
|
||||
namespace: config.ConfigurationNamespace{
|
||||
Initialize: []config.ConfigurationNamespaceInit{
|
||||
{Name: "library", Visibility: enums.VisibilityPublic},
|
||||
{Name: "internal", Visibility: enums.VisibilityPrivate},
|
||||
},
|
||||
},
|
||||
genDigCon: func(t *testing.T, _ *config.Configuration) *dig.Container {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := reponamespace.NewMockNamespaceRepository(ctrl)
|
||||
mock.EXPECT().GetByName(gomock.Any(), "library").
|
||||
Return(&models.Namespace{ID: "namespace-1", Name: "library"}, nil)
|
||||
mock.EXPECT().GetByName(gomock.Any(), "internal").
|
||||
Return(nil, gorm.ErrRecordNotFound)
|
||||
mock.EXPECT().Create(gomock.Any(), gomock.Any()).
|
||||
DoAndReturn(func(_ context.Context, namespace *models.Namespace) error {
|
||||
require.Equal(t, "internal", namespace.Name)
|
||||
require.Equal(t, enums.VisibilityPrivate, namespace.Visibility)
|
||||
require.NotEmpty(t, namespace.ID)
|
||||
return nil
|
||||
})
|
||||
digCon := dig.New()
|
||||
require.NoError(t, digCon.Provide(func() reponamespace.NamespaceRepository { return mock }))
|
||||
return digCon
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get error",
|
||||
namespace: config.ConfigurationNamespace{
|
||||
Initialize: []config.ConfigurationNamespaceInit{
|
||||
{Name: "library", Visibility: enums.VisibilityPublic},
|
||||
},
|
||||
},
|
||||
genDigCon: func(t *testing.T, _ *config.Configuration) *dig.Container {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := reponamespace.NewMockNamespaceRepository(ctrl)
|
||||
mock.EXPECT().GetByName(gomock.Any(), "library").
|
||||
Return(nil, gorm.ErrInvalidDB)
|
||||
digCon := dig.New()
|
||||
require.NoError(t, digCon.Provide(func() reponamespace.NamespaceRepository { return mock }))
|
||||
return digCon
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := &config.Configuration{Namespace: tt.namespace}
|
||||
digCon := tt.genDigCon(t, cfg)
|
||||
require.NoError(t, digCon.Provide(func() *config.Configuration { return cfg }))
|
||||
err := initNamespaces(digCon)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -387,10 +387,18 @@ func (c *ConfigurationLocker) WithDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigurationNamespaceInit declares a namespace created at startup. Users can
|
||||
// declare any number of namespaces, each with its own visibility.
|
||||
type ConfigurationNamespaceInit struct {
|
||||
Name string `yaml:"name"`
|
||||
Visibility enums.Visibility `yaml:"visibility"`
|
||||
}
|
||||
|
||||
// ConfigurationNamespace controls whether namespaces are created automatically on push and the visibility assigned to them.
|
||||
type ConfigurationNamespace struct {
|
||||
AutoCreate bool `yaml:"autoCreate"`
|
||||
Visibility enums.Visibility `yaml:"visibility"`
|
||||
AutoCreate bool `yaml:"autoCreate"`
|
||||
Visibility enums.Visibility `yaml:"visibility"`
|
||||
Initialize []ConfigurationNamespaceInit `yaml:"initialize"`
|
||||
}
|
||||
|
||||
// WithDefaults applies default values for namespace.
|
||||
@@ -398,6 +406,11 @@ func (c *ConfigurationNamespace) WithDefaults() {
|
||||
if c.Visibility.String() == "" {
|
||||
c.Visibility = enums.VisibilityPrivate
|
||||
}
|
||||
for i := range c.Initialize {
|
||||
if c.Initialize[i].Visibility.String() == "" {
|
||||
c.Initialize[i].Visibility = c.Visibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigurationHttpTLS enables TLS termination for the HTTP server and points at the certificate and private key files.
|
||||
|
||||
@@ -368,23 +368,6 @@ CREATE TABLE IF NOT EXISTS `namespace_members` (
|
||||
CONSTRAINT `namespace_members_unique_with_user_ns_role` UNIQUE (`user_id`, `namespace_id`, `role`, `deleted_at`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO
|
||||
`namespaces` (
|
||||
`id`,
|
||||
`name`,
|
||||
`visibility`,
|
||||
`created_at`,
|
||||
`updated_at`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'019f56d8-633c-7d64-a115-f793aa8aad22',
|
||||
'library',
|
||||
'public',
|
||||
1775566388880,
|
||||
1775566388880
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `webhooks` (
|
||||
`id` varchar(36) PRIMARY KEY,
|
||||
`namespace_id` varchar(36),
|
||||
|
||||
@@ -426,23 +426,6 @@ CREATE TABLE IF NOT EXISTS "namespace_members" (
|
||||
CONSTRAINT "namespace_members_unique_with_user_ns_role" UNIQUE ("user_id", "namespace_id", "role", "deleted_at")
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
"namespaces" (
|
||||
"id",
|
||||
"name",
|
||||
"visibility",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'019f56d8-633c-7d64-a115-f793aa8aad22',
|
||||
'library',
|
||||
'public',
|
||||
1775566388880,
|
||||
1775566388880
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "webhooks" (
|
||||
"id" varchar(36) PRIMARY KEY,
|
||||
"namespace_id" varchar(36),
|
||||
|
||||
@@ -372,23 +372,6 @@ CREATE TABLE IF NOT EXISTS `namespace_members` (
|
||||
CONSTRAINT `namespace_members_unique_with_user_ns_role` UNIQUE (`user_id`, `namespace_id`, `role`, `deleted_at`)
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
`namespaces` (
|
||||
`id`,
|
||||
`name`,
|
||||
`visibility`,
|
||||
`created_at`,
|
||||
`updated_at`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'019f56d8-633c-7d64-a115-f793aa8aad22',
|
||||
'library',
|
||||
'public',
|
||||
1775566388880,
|
||||
1775566388880
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `webhooks` (
|
||||
`id` varchar(36) PRIMARY KEY,
|
||||
`namespace_id` varchar(36),
|
||||
|
||||
@@ -372,23 +372,6 @@ CREATE TABLE IF NOT EXISTS `namespace_members` (
|
||||
CONSTRAINT `namespace_members_unique_with_user_ns_role` UNIQUE (`user_id`, `namespace_id`, `role`, `deleted_at`)
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
`namespaces` (
|
||||
`id`,
|
||||
`name`,
|
||||
`visibility`,
|
||||
`created_at`,
|
||||
`updated_at`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'019f56d8-633c-7d64-a115-f793aa8aad22',
|
||||
'library',
|
||||
'public',
|
||||
1775566388880,
|
||||
1775566388880
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `webhooks` (
|
||||
`id` varchar(36) PRIMARY KEY,
|
||||
`namespace_id` varchar(36),
|
||||
|
||||
Reference in new issue
Block a user