diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index 80fb09bd..eb5fd3bc 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -7,23 +7,21 @@ import ( "log/slog" "net" "os" - "syscall" "time" "github.com/openkcm/common-sdk/pkg/commoncfg" "github.com/openkcm/common-sdk/pkg/health" "github.com/openkcm/common-sdk/pkg/logger" "github.com/openkcm/common-sdk/pkg/otlp" - "github.com/openkcm/common-sdk/pkg/status" "github.com/samber/oops" "github.com/openkcm/cmk/internal/config" "github.com/openkcm/cmk/internal/constants" "github.com/openkcm/cmk/internal/daemon" "github.com/openkcm/cmk/internal/db" - "github.com/openkcm/cmk/internal/db/dsn" "github.com/openkcm/cmk/internal/log" "github.com/openkcm/cmk/utils/cmd" + statusserver "github.com/openkcm/cmk/utils/status_server" ) var ( @@ -35,6 +33,8 @@ var ( // - Starts the status server // - Starts the CMK API Server +// +//nolint:funlen func run(ctx context.Context, cfg *config.Config) error { // Update Version err := commoncfg.UpdateConfigVersion(&cfg.BaseConfig, BuildInfo) @@ -60,7 +60,23 @@ func run(ctx context.Context, cfg *config.Config) error { } // Start status server - startStatusServer(ctx, cfg) + statusserver.StartStatusServer(ctx, cfg, health.WithCheck(health.Check{ + Name: "HTTP Server", + Check: func(ctx context.Context) error { + dialer := &net.Dialer{ + Timeout: time.Second * 1, + } + conn, err := dialer.DialContext(ctx, "tcp", cfg.HTTP.Address) + if err != nil { + return fmt.Errorf("health check: cannot connect to %s: %w", cfg.HTTP.Address, err) + } + defer func() { _ = conn.Close() }() + + return nil + }, + })) + + go daemon.MonitorKeystorePoolSize(ctx, cfg) // Database initialisation dbCon, err := db.StartDB(ctx, cfg) @@ -91,47 +107,6 @@ func run(ctx context.Context, cfg *config.Config) error { return nil } -func startStatusServer(ctx context.Context, cfg *config.Config) { - dsnFromConfig, err := dsn.FromDBConfig(cfg.Database) - if err != nil { - log.Error(ctx, "Could not load DSN from database config", err) - } - - healthOptions := []health.Option{ - health.WithDatabaseChecker( - constants.DBDriver, - dsnFromConfig, - ), - health.WithCheck(health.Check{ - Name: "HTTP Server", - Check: func(ctx context.Context) error { - dialer := &net.Dialer{ - Timeout: time.Second * 1, - } - - conn, err := dialer.DialContext(ctx, "tcp", cfg.HTTP.Address) - if err != nil { - return fmt.Errorf("health check: cannot connect to %s: %w", cfg.HTTP.Address, err) - } - defer func() { _ = conn.Close() }() - - return nil - }, - }), - } - - go daemon.MonitorKeystorePoolSize(ctx, cfg) - - go func() { - err := status.Serve(ctx, &cfg.BaseConfig, healthOptions...) - if err != nil { - log.Error(ctx, "Failure on the status server", err) - - _ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) - } - }() -} - // main is the entry point for the application. It is intentionally kept small // because it is hard to test, which would lower test coverage. func main() { diff --git a/cmd/event-reconciler/main.go b/cmd/event-reconciler/main.go index 9ea1b77c..66dbe6b6 100644 --- a/cmd/event-reconciler/main.go +++ b/cmd/event-reconciler/main.go @@ -4,25 +4,21 @@ import ( "context" "flag" "os" - "syscall" "github.com/openkcm/common-sdk/pkg/commoncfg" - "github.com/openkcm/common-sdk/pkg/health" "github.com/openkcm/common-sdk/pkg/logger" "github.com/openkcm/common-sdk/pkg/otlp" - "github.com/openkcm/common-sdk/pkg/status" "github.com/samber/oops" "github.com/openkcm/cmk/internal/clients" "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/constants" "github.com/openkcm/cmk/internal/db" - "github.com/openkcm/cmk/internal/db/dsn" eventprocessor "github.com/openkcm/cmk/internal/event-processor" "github.com/openkcm/cmk/internal/log" cmkpluginregistry "github.com/openkcm/cmk/internal/pluginregistry" "github.com/openkcm/cmk/internal/repo/sql" "github.com/openkcm/cmk/utils/cmd" + statusserver "github.com/openkcm/cmk/utils/status_server" ) var ( @@ -57,7 +53,7 @@ func run(ctx context.Context, cfg *config.Config) error { } // Start status server - startStatusServer(ctx, cfg) + statusserver.StartStatusServer(ctx, cfg) // Database initialisation dbCon, err := db.StartDB(ctx, cfg) @@ -96,29 +92,6 @@ func run(ctx context.Context, cfg *config.Config) error { return nil } -func startStatusServer(ctx context.Context, cfg *config.Config) { - dsnFromConfig, err := dsn.FromDBConfig(cfg.Database) - if err != nil { - log.Error(ctx, "Could not load DSN from database config", err) - } - - healthOptions := []health.Option{ - health.WithDatabaseChecker( - constants.DBDriver, - dsnFromConfig, - ), - } - - go func() { - err := status.Serve(ctx, &cfg.BaseConfig, healthOptions...) - if err != nil { - log.Error(ctx, "Failure on the status server", err) - - _ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) - } - }() -} - func main() { flag.Parse() diff --git a/cmd/task-cli/commands/root.go b/cmd/task-cli/commands/root.go index 04627f18..ada542f2 100644 --- a/cmd/task-cli/commands/root.go +++ b/cmd/task-cli/commands/root.go @@ -5,12 +5,14 @@ import ( "github.com/spf13/cobra" + "github.com/openkcm/cmk/internal/config" cliUtils "github.com/openkcm/cmk/utils/cli" ) -func NewRootCmd(ctx context.Context) *cobra.Command { +func NewRootCmd(ctx context.Context, cfg *config.Config) *cobra.Command { return cliUtils.NewRootCmdWithInfinitySleep( ctx, + cfg, "task", "Async Task CLI", "CLI tool to manage and invoke CMK asynchronous tasks.", diff --git a/cmd/task-cli/commands/root_test.go b/cmd/task-cli/commands/root_test.go index 675771ca..cfbe4a61 100644 --- a/cmd/task-cli/commands/root_test.go +++ b/cmd/task-cli/commands/root_test.go @@ -11,18 +11,19 @@ import ( "github.com/stretchr/testify/assert" "github.com/openkcm/cmk/cmd/task-cli/commands" + "github.com/openkcm/cmk/internal/config" ) func TestRootCmdProvidesSleepFlag(t *testing.T) { ctx := context.Background() - cmd := commands.NewRootCmd(ctx) + cmd := commands.NewRootCmd(ctx, &config.Config{}) assert.NotNil(t, cmd.PersistentFlags().Lookup("sleep")) } func TestRootCmdSleepModePrintsStartAndShutdown(t *testing.T) { ctx := context.Background() - cmd := commands.NewRootCmd(ctx) + cmd := commands.NewRootCmd(ctx, &config.Config{}) var out bytes.Buffer cmd.SetOut(&out) diff --git a/cmd/task-cli/main.go b/cmd/task-cli/main.go index a5bbfa88..0c429976 100644 --- a/cmd/task-cli/main.go +++ b/cmd/task-cli/main.go @@ -65,7 +65,7 @@ func run(ctx context.Context, cfg *config.Config) error { asyncClient := asyncApp.Client() asyncInspector := asyncApp.Inspector() - rootCmd := commands.NewRootCmd(ctx) + rootCmd := commands.NewRootCmd(ctx, cfg) rootCmd.AddCommand(commands.NewStatsCmd(ctx, asyncInspector)) rootCmd.AddCommand(commands.NewQueuesCmd(ctx, asyncInspector)) rootCmd.AddCommand(commands.NewInvokeCmd(ctx, asyncClient)) diff --git a/cmd/task-scheduler/main.go b/cmd/task-scheduler/main.go index f6ad874b..ade8b2d9 100644 --- a/cmd/task-scheduler/main.go +++ b/cmd/task-scheduler/main.go @@ -2,10 +2,14 @@ package main import ( "context" - "log" + "errors" + "flag" + "fmt" + "log/slog" "os" "os/signal" "syscall" + "time" "github.com/openkcm/common-sdk/pkg/commoncfg" "github.com/openkcm/common-sdk/pkg/logger" @@ -14,31 +18,29 @@ import ( "github.com/openkcm/cmk/internal/async" "github.com/openkcm/cmk/internal/config" "github.com/openkcm/cmk/internal/constants" - cmklog "github.com/openkcm/cmk/internal/log" + "github.com/openkcm/cmk/internal/log" + statusserver "github.com/openkcm/cmk/utils/status_server" ) -const AppName = "scheduler" - -func start() error { - ctx, cancelOnSignal := signal.NotifyContext( - context.Background(), - os.Interrupt, syscall.SIGTERM, - ) - - defer cancelOnSignal() +var ( + BuildInfo = "{}" + gracefulShutdownSec = flag.Int64("graceful-shutdown", 1, "graceful shutdown seconds") + gracefulShutdownMessage = flag.String("graceful-shutdown-message", "Graceful shutdown in %d seconds", + "graceful shutdown message") + ErrRegistryEnabled = errors.New("failed to create registry client") +) - cfg, err := config.LoadConfig(commoncfg.WithEnvOverride(constants.APIName + "_task_scheduler")) - if err != nil { - return oops.In("main").Wrapf(err, "failed to load the config") - } +const AppName = "scheduler" - // LoggerConfig initialisation - err = logger.InitAsDefault(cfg.Logger, cfg.Application) +func run(ctx context.Context, cfg *config.Config) error { + err := logger.InitAsDefault(cfg.Logger, cfg.Application) if err != nil { return oops.In("main"). Wrapf(err, "Failed to initialise the logger") } + statusserver.StartStatusServer(ctx, cfg) + cronJob, err := async.New(cfg) if err != nil { return oops.In("main").Wrapf(err, "failed to create the scheduler") @@ -56,14 +58,50 @@ func start() error { return oops.In("main").Wrapf(err, "failed to shutdown the scheduler") } - cmklog.Info(ctx, "shutting down scheduler") + log.Info(ctx, "shutting down scheduler") return nil } -func main() { - err := start() +// runFuncWithSignalHandling runs the given function with signal handling. When +// a CTRL-C is received, the context will be cancelled on which the function can +// act upon. +// It returns the exitCode +func runFuncWithSignalHandling(f func(context.Context, *config.Config) error) int { + ctx, cancelOnSignal := signal.NotifyContext( + context.Background(), + os.Interrupt, syscall.SIGTERM, + ) + defer cancelOnSignal() + + cfg, err := config.LoadConfig(commoncfg.WithEnvOverride(constants.APIName + "_task_scheduler")) if err != nil { - log.Fatal(err) + log.Error(ctx, "Failed to load the configuration", err) + _, _ = fmt.Fprintln(os.Stderr, err) + + return 1 } + + log.Debug(ctx, "Starting the application", slog.Any("config", *cfg)) + + err = f(ctx, cfg) + if err != nil { + log.Error(ctx, "Failed to start the application", err) + _, _ = fmt.Fprintln(os.Stderr, err) + + return 1 + } + + // graceful shutdown so running goroutines may finish + _, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf(*gracefulShutdownMessage, *gracefulShutdownSec)) + time.Sleep(time.Duration(*gracefulShutdownSec) * time.Second) + + return 0 +} + +func main() { + flag.Parse() + + exitCode := runFuncWithSignalHandling(run) + os.Exit(exitCode) } diff --git a/cmd/task-worker/main.go b/cmd/task-worker/main.go index 0f2c99ed..5057c2c5 100644 --- a/cmd/task-worker/main.go +++ b/cmd/task-worker/main.go @@ -12,10 +12,8 @@ import ( "time" "github.com/openkcm/common-sdk/pkg/commoncfg" - "github.com/openkcm/common-sdk/pkg/health" "github.com/openkcm/common-sdk/pkg/logger" "github.com/openkcm/common-sdk/pkg/otlp" - "github.com/openkcm/common-sdk/pkg/status" "github.com/samber/oops" "github.com/openkcm/cmk/internal/async" @@ -26,7 +24,6 @@ import ( "github.com/openkcm/cmk/internal/config" "github.com/openkcm/cmk/internal/constants" "github.com/openkcm/cmk/internal/db" - "github.com/openkcm/cmk/internal/db/dsn" "github.com/openkcm/cmk/internal/errs" eventprocessor "github.com/openkcm/cmk/internal/event-processor" "github.com/openkcm/cmk/internal/log" @@ -35,6 +32,7 @@ import ( cmkpluginregistry "github.com/openkcm/cmk/internal/pluginregistry" "github.com/openkcm/cmk/internal/repo" "github.com/openkcm/cmk/internal/repo/sql" + statusserver "github.com/openkcm/cmk/utils/status_server" ) var ( @@ -72,7 +70,7 @@ func run(ctx context.Context, cfg *config.Config) error { } // Start status server - startStatusServer(ctx, cfg) + statusserver.StartStatusServer(ctx, cfg) cron, err := async.New(cfg) if err != nil { @@ -179,29 +177,6 @@ func registerTasks( return nil } -func startStatusServer(ctx context.Context, cfg *config.Config) { - dsnFromConfig, err := dsn.FromDBConfig(cfg.Database) - if err != nil { - log.Error(ctx, "Could not load DSN from database config", err) - } - - healthOptions := []health.Option{ - health.WithDatabaseChecker( - constants.DBDriver, - dsnFromConfig, - ), - } - - go func() { - err := status.Serve(ctx, &cfg.BaseConfig, healthOptions...) - if err != nil { - log.Error(ctx, "Failure on the status server", err) - - _ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) - } - }() -} - // runFuncWithSignalHandling runs the given function with signal handling. When // a CTRL-C is received, the context will be cancelled on which the function can // act upon. diff --git a/cmd/tenant-manager-cli/cli_test.go b/cmd/tenant-manager-cli/cli_test.go index 8d40fc78..eef7e75f 100644 --- a/cmd/tenant-manager-cli/cli_test.go +++ b/cmd/tenant-manager-cli/cli_test.go @@ -111,7 +111,7 @@ func (s *CLISuite) SetupSuite() { factory, err := commands.NewCommandFactory(ctx, cfg, s.db, svcRegistry) s.NoError(err) - s.rootCmd = factory.NewRootCmd(s.T().Context()) + s.rootCmd = factory.NewRootCmd(s.T().Context(), cfg) s.createGroupsCmd = factory.NewCreateGroupsCmd(s.T().Context()) s.rootCmd.AddCommand(s.createGroupsCmd) diff --git a/cmd/tenant-manager-cli/commands/rootcmd.go b/cmd/tenant-manager-cli/commands/rootcmd.go index 70a788ba..25234a75 100644 --- a/cmd/tenant-manager-cli/commands/rootcmd.go +++ b/cmd/tenant-manager-cli/commands/rootcmd.go @@ -5,12 +5,14 @@ import ( "github.com/spf13/cobra" + "github.com/openkcm/cmk/internal/config" cliUtils "github.com/openkcm/cmk/utils/cli" ) -func (f *CommandFactory) NewRootCmd(ctx context.Context) *cobra.Command { +func (f *CommandFactory) NewRootCmd(ctx context.Context, cfg *config.Config) *cobra.Command { return cliUtils.NewRootCmdWithInfinitySleep( ctx, + cfg, "tm", "Tenant Manager CLI Application", "Tenant Manager is a simple CLI tool to manage tenants, supporting: creating tenant, "+ diff --git a/cmd/tenant-manager-cli/main.go b/cmd/tenant-manager-cli/main.go index c27dddee..d14d0b52 100644 --- a/cmd/tenant-manager-cli/main.go +++ b/cmd/tenant-manager-cli/main.go @@ -100,7 +100,7 @@ func setupCommands( return nil, err } - rootCmd := factory.NewRootCmd(ctx) + rootCmd := factory.NewRootCmd(ctx, cfg) createGroupsCmd := factory.NewCreateGroupsCmd(ctx) rootCmd.AddCommand(createGroupsCmd) diff --git a/cmd/tenant-manager/export_test.go b/cmd/tenant-manager/export_test.go index b0739c5f..38e93bd7 100644 --- a/cmd/tenant-manager/export_test.go +++ b/cmd/tenant-manager/export_test.go @@ -4,5 +4,4 @@ var ( Run = run RunFunctionWithSigHandling = runFuncWithSignalHandling LoadConfig = loadConfig - StartStatusServer = startStatusServer ) diff --git a/cmd/tenant-manager/main.go b/cmd/tenant-manager/main.go index a6565535..5d7bdfe8 100644 --- a/cmd/tenant-manager/main.go +++ b/cmd/tenant-manager/main.go @@ -10,10 +10,8 @@ import ( "time" "github.com/openkcm/common-sdk/pkg/commoncfg" - "github.com/openkcm/common-sdk/pkg/health" "github.com/openkcm/common-sdk/pkg/logger" "github.com/openkcm/common-sdk/pkg/otlp" - "github.com/openkcm/common-sdk/pkg/status" "github.com/openkcm/orbital" "github.com/openkcm/orbital/client/amqp" "github.com/openkcm/orbital/codec" @@ -22,9 +20,7 @@ import ( "github.com/openkcm/cmk/internal/auditor" "github.com/openkcm/cmk/internal/clients" "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/constants" "github.com/openkcm/cmk/internal/db" - "github.com/openkcm/cmk/internal/db/dsn" eventprocessor "github.com/openkcm/cmk/internal/event-processor" "github.com/openkcm/cmk/internal/log" "github.com/openkcm/cmk/internal/manager" @@ -32,6 +28,7 @@ import ( cmkpluginregistry "github.com/openkcm/cmk/internal/pluginregistry" "github.com/openkcm/cmk/internal/repo" "github.com/openkcm/cmk/internal/repo/sql" + statusserver "github.com/openkcm/cmk/utils/status_server" ) const ( @@ -70,7 +67,7 @@ func run(ctx context.Context, cfg *config.Config) error { return err } - startStatusServer(ctx, cfg) + statusserver.StartStatusServer(ctx, cfg) dbConn, err := db.StartDB(ctx, cfg) if err != nil { @@ -238,29 +235,6 @@ func validateAndGetClients(cfg *config.Config) ( return clientsFactory, nil } -func startStatusServer(ctx context.Context, cfg *config.Config) { - dsnFromConfig, err := dsn.FromDBConfig(cfg.Database) - if err != nil { - log.Error(ctx, "Could not load DSN from database config", err) - } - - healthOptions := []health.Option{ - health.WithDatabaseChecker( - constants.DBDriver, - dsnFromConfig, - ), - } - - go func() { - err := status.Serve(ctx, &cfg.BaseConfig, healthOptions...) - if err != nil { - log.Error(ctx, errMsgStatusServer, err) - - _ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) - } - }() -} - func loadConfig() (*config.Config, error) { cfg := &config.Config{} diff --git a/go.mod b/go.mod index d4750df0..7ce4ba26 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/bartventer/gorm-multitenancy/middleware/nethttp/v8 v8.9.0 github.com/bartventer/gorm-multitenancy/postgres/v8 v8.9.0 github.com/bartventer/gorm-multitenancy/v8 v8.9.0 + github.com/docker/docker v28.5.2+incompatible github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa github.com/getkin/kin-openapi v0.137.0 github.com/gogo/protobuf v1.3.2 @@ -24,6 +25,7 @@ require ( github.com/looplab/fsm v1.0.3 github.com/magodo/slog2hclog v0.0.0-20240614031327-090ebd72a033 github.com/microcosm-cc/bluemonday v1.0.27 + github.com/moby/moby/api v1.54.2 github.com/oapi-codegen/nethttp-middleware v1.1.2 github.com/oapi-codegen/runtime v1.4.0 github.com/openkcm/api-sdk v0.17.0 @@ -119,7 +121,6 @@ require ( github.com/mfridman/interpolate v0.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.2.0 // indirect - github.com/moby/moby/api v1.54.2 // indirect github.com/moby/moby/client v0.4.1 // indirect github.com/moby/patternmatcher v0.6.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect diff --git a/internal/plugins/identity-management/noop/plugin.go b/internal/plugins/identity-management/noop/plugin.go index 7f3e2af3..e8d494cd 100755 --- a/internal/plugins/identity-management/noop/plugin.go +++ b/internal/plugins/identity-management/noop/plugin.go @@ -5,11 +5,8 @@ import ( "log/slog" "github.com/hashicorp/go-hclog" - "github.com/openkcm/common-sdk/pkg/commoncfg" "github.com/openkcm/plugin-sdk/pkg/catalog" "github.com/openkcm/plugin-sdk/pkg/hclog2slog" - "github.com/pkg/errors" - "gopkg.in/yaml.v3" idmangv1 "github.com/openkcm/plugin-sdk/proto/plugin/identity_management/v1" configv1 "github.com/openkcm/plugin-sdk/proto/service/common/config/v1" @@ -31,8 +28,6 @@ type Plugin struct { logger *slog.Logger buildInfo string - - staticConfiguration *StaticIdentityManagement } var ( @@ -61,24 +56,6 @@ func (p *Plugin) SetLogger(logger hclog.Logger) { func (p *Plugin) Configure(_ context.Context, req *configv1.ConfigureRequest) (*configv1.ConfigureResponse, error) { slog.Info("Configuring plugin") - cfg := Config{} - - err := yaml.Unmarshal([]byte(req.GetYamlConfiguration()), &cfg) - if err != nil { - return nil, errors.Wrapf(err, "Failed to get yaml Configuration") - } - - content, err := commoncfg.ExtractValueFromSourceRef(&cfg.StaticJsonContent) - if err != nil { - return nil, errors.Wrapf(err, "Failed to get yaml configuration") - } - - p.staticConfiguration = &StaticIdentityManagement{} - err = yaml.Unmarshal(content, p.staticConfiguration) - if err != nil { - return nil, errors.Wrapf(err, "Failed to get yaml static configuration") - } - return &configv1.ConfigureResponse{ BuildInfo: &p.buildInfo, }, nil @@ -100,16 +77,6 @@ func (p *Plugin) GetGroup( _ context.Context, req *idmangv1.GetGroupRequest, ) (*idmangv1.GetGroupResponse, error) { - for _, group := range p.staticConfiguration.Groups { - if group.Name == req.GetGroupName() { - return &idmangv1.GetGroupResponse{ - Group: &idmangv1.Group{ - Id: group.ID, - Name: group.Name, - }, - }, nil - } - } return &idmangv1.GetGroupResponse{}, nil } @@ -117,55 +84,19 @@ func (p *Plugin) GetAllGroups( _ context.Context, req *idmangv1.GetAllGroupsRequest, ) (*idmangv1.GetAllGroupsResponse, error) { - groups := make([]*idmangv1.Group, 0, len(p.staticConfiguration.Groups)) - for _, group := range p.staticConfiguration.Groups { - groups = append(groups, &idmangv1.Group{ - Id: group.ID, - Name: group.Name, - }) - } - return &idmangv1.GetAllGroupsResponse{ - Groups: groups, - }, nil + return &idmangv1.GetAllGroupsResponse{}, nil } func (p *Plugin) GetUsersForGroup( _ context.Context, req *idmangv1.GetUsersForGroupRequest, ) (*idmangv1.GetUsersForGroupResponse, error) { - users := make([]*idmangv1.User, 0) - for _, group := range p.staticConfiguration.Groups { - if group.ID == req.GetGroupId() { - for _, user := range group.Users { - users = append(users, &idmangv1.User{ - Id: user.ID, - Name: user.Name, - Email: user.Email, - }) - } - } - } - return &idmangv1.GetUsersForGroupResponse{ - Users: users, - }, nil + return &idmangv1.GetUsersForGroupResponse{}, nil } func (p *Plugin) GetGroupsForUser( _ context.Context, req *idmangv1.GetGroupsForUserRequest, ) (*idmangv1.GetGroupsForUserResponse, error) { - groups := make([]*idmangv1.Group, 0, len(p.staticConfiguration.Groups)) - for _, group := range p.staticConfiguration.Groups { - for _, user := range group.Users { - if user.ID == req.GetUserId() { - groups = append(groups, &idmangv1.Group{ - Id: group.ID, - Name: group.Name, - }) - } - } - } - return &idmangv1.GetGroupsForUserResponse{ - Groups: groups, - }, nil + return &idmangv1.GetGroupsForUserResponse{}, nil } diff --git a/internal/plugins/identity-management/noop/plugin_test.go b/internal/plugins/identity-management/noop/plugin_test.go deleted file mode 100644 index a79f655a..00000000 --- a/internal/plugins/identity-management/noop/plugin_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package noop_test - -import ( - "context" - "os" - "reflect" - "testing" - - "github.com/hashicorp/go-hclog" - "github.com/openkcm/plugin-sdk/pkg/hclog2slog" - - idmangv1 "github.com/openkcm/plugin-sdk/proto/plugin/identity_management/v1" - configv1 "github.com/openkcm/plugin-sdk/proto/service/common/config/v1" - - "github.com/openkcm/cmk/internal/plugins/identity-management/noop" -) - -const testStaticConfig = ` -groups: - - id: "group-1" - name: "group1" - users: - - id: "user-1" - name: "user1" - email: "user1@example.com" - - id: "group-2" - name: "group2" - users: - - id: "user-2" - name: "user2" - email: "user2@example.com" -` - -func setupPlugin(t *testing.T) *noop.Plugin { - t.Helper() - p := noop.NewPlugin() - p.SetLogger(hclog.NewNullLogger()) - - tmpFile, err := os.CreateTemp(t.TempDir(), "static-config-*.yaml") - if err != nil { - t.Fatalf("Failed to create temp file: %v", err) - } - t.Cleanup(func() { os.Remove(tmpFile.Name()) }) - - if _, err := tmpFile.WriteString(testStaticConfig); err != nil { - t.Fatalf("Failed to write to temp file: %v", err) - } - if err := tmpFile.Close(); err != nil { - t.Fatalf("Failed to close temp file: %v", err) - } - - configYAML := ` -staticJsonContent: - source: file - file: - path: "` + tmpFile.Name() + `"` - - req := &configv1.ConfigureRequest{ - YamlConfiguration: configYAML, - } - - if _, err = p.Configure(context.Background(), req); err != nil { - t.Fatalf("Failed to configure plugin: %v", err) - } - - return p -} - -func TestNewPlugin(t *testing.T) { - p := noop.NewPlugin() - if p == nil { - t.Fatal("NewPlugin() returned nil") - } - if p.GetBuildInfo() != "{}" { - t.Errorf("Expected buildInfo to be '{}', got '%s'", p.GetBuildInfo()) - } -} - -func TestSetLogger(t *testing.T) { - p := noop.NewPlugin() - logger := hclog.NewNullLogger() - p.SetLogger(logger) - expectedLogger := hclog2slog.New(logger) - if !reflect.DeepEqual(p.GetLogger(), expectedLogger) { - t.Errorf("Expected logger to be %v, got %v", expectedLogger, p.GetLogger()) - } -} - -func TestConfigure(t *testing.T) { - p := noop.NewPlugin() - - t.Run("Invalid YAML", func(t *testing.T) { - req := &configv1.ConfigureRequest{ - YamlConfiguration: "invalid-yaml", - } - _, err := p.Configure(context.Background(), req) - if err == nil { - t.Error("Expected an error for invalid YAML, but got nil") - } - }) - - t.Run("Invalid static config path", func(t *testing.T) { - req := &configv1.ConfigureRequest{ - YamlConfiguration: "staticJsonContent: { filePath: \"/non-existent-file\" }", - } - _, err := p.Configure(context.Background(), req) - if err == nil { - t.Error("Expected an error for non-existent file, but got nil") - } - }) -} - -func TestGetGroup(t *testing.T) { - p := setupPlugin(t) - - t.Run("Group found", func(t *testing.T) { - req := &idmangv1.GetGroupRequest{GroupName: "group1"} - resp, err := p.GetGroup(context.Background(), req) - if err != nil { - t.Fatalf("GetGroup failed: %v", err) - } - if resp.GetGroup() == nil { - t.Fatal("Expected group, but got nil") - } - if resp.GetGroup().GetId() != "group-1" { - t.Errorf("Expected group ID 'group-1', got '%s'", resp.GetGroup().GetId()) - } - if resp.GetGroup().GetName() != "group1" { - t.Errorf("Expected group name 'group1', got '%s'", resp.GetGroup().GetName()) - } - }) - - t.Run("Group not found", func(t *testing.T) { - req := &idmangv1.GetGroupRequest{GroupName: "non-existent-group"} - resp, err := p.GetGroup(context.Background(), req) - if err != nil { - t.Fatalf("GetGroup failed: %v", err) - } - if resp.GetGroup() != nil { - t.Errorf("Expected nil group, but got %v", resp.GetGroup()) - } - }) -} - -func TestGetAllGroups(t *testing.T) { - p := setupPlugin(t) - - req := &idmangv1.GetAllGroupsRequest{} - resp, err := p.GetAllGroups(context.Background(), req) - if err != nil { - t.Fatalf("GetAllGroups failed: %v", err) - } - if len(resp.GetGroups()) != 2 { - t.Errorf("Expected 2 groups, got %d", len(resp.GetGroups())) - } -} - -func TestGetUsersForGroup(t *testing.T) { - p := setupPlugin(t) - - t.Run("Group found", func(t *testing.T) { - req := &idmangv1.GetUsersForGroupRequest{GroupId: "group-1"} - resp, err := p.GetUsersForGroup(context.Background(), req) - if err != nil { - t.Fatalf("GetUsersForGroup failed: %v", err) - } - if len(resp.GetUsers()) != 1 { - t.Fatalf("Expected 1 user, got %d", len(resp.GetUsers())) - } - if resp.GetUsers()[0].GetId() != "user-1" { - t.Errorf("Expected user ID 'user-1', got '%s'", resp.GetUsers()[0].GetId()) - } - }) - - t.Run("Group not found", func(t *testing.T) { - req := &idmangv1.GetUsersForGroupRequest{GroupId: "non-existent-group"} - resp, err := p.GetUsersForGroup(context.Background(), req) - if err != nil { - t.Fatalf("GetUsersForGroup failed: %v", err) - } - if len(resp.GetUsers()) != 0 { - t.Errorf("Expected 0 users, got %d", len(resp.GetUsers())) - } - }) -} - -func TestGetGroupsForUser(t *testing.T) { - p := setupPlugin(t) - - t.Run("User found", func(t *testing.T) { - req := &idmangv1.GetGroupsForUserRequest{UserId: "user-1"} - resp, err := p.GetGroupsForUser(context.Background(), req) - if err != nil { - t.Fatalf("GetGroupsForUser failed: %v", err) - } - if len(resp.GetGroups()) != 1 { - t.Fatalf("Expected 1 group, got %d", len(resp.GetGroups())) - } - if resp.GetGroups()[0].GetId() != "group-1" { - t.Errorf("Expected group ID 'group-1', got '%s'", resp.GetGroups()[0].GetId()) - } - }) - - t.Run("User not found", func(t *testing.T) { - req := &idmangv1.GetGroupsForUserRequest{UserId: "non-existent-user"} - resp, err := p.GetGroupsForUser(context.Background(), req) - if err != nil { - t.Fatalf("GetGroupsForUser failed: %v", err) - } - if len(resp.GetGroups()) != 0 { - t.Errorf("Expected 0 groups, got %d", len(resp.GetGroups())) - } - }) -} diff --git a/internal/testutils/container.go b/internal/testutils/container.go index f343f156..37a25caf 100644 --- a/internal/testutils/container.go +++ b/internal/testutils/container.go @@ -107,6 +107,21 @@ func StartRedis( ) { tb.Helper() + if cfg != nil && cfg.TaskQueue.SecretRef.Type == "" { + cfg.TaskQueue.SecretRef.Type = commoncfg.InsecureSecretType + cfg.TaskQueue.ACL = config.RedisACL{ + Enabled: false, + Username: commoncfg.SourceRef{ + Source: commoncfg.EmbeddedSourceValue, + Value: "default", + }, + Password: commoncfg.SourceRef{ + Source: commoncfg.EmbeddedSourceValue, + Value: "secret", + }, + } + } + // Do it like this so the user specified override the defaults options := append([]testcontainers.ContainerCustomizer{ testcontainers.WithReuseByName(redisContainer), @@ -116,6 +131,7 @@ func StartRedis( "redis:7", options..., ) + redisContainer.TLSConfig() assert.NoError(tb, err) @@ -123,6 +139,13 @@ func StartRedis( port, err := redisContainer.MappedPort(tb.Context(), "6379") assert.NoError(tb, err) + host, err := redisContainer.Host(tb.Context()) + assert.NoError(tb, err) + cfg.TaskQueue.Port = port.Port() + cfg.TaskQueue.Host = commoncfg.SourceRef{ + Source: commoncfg.EmbeddedSourceValue, + Value: host, + } } } diff --git a/internal/testutils/plugin.go b/internal/testutils/plugin.go index 7ff596a5..d2435222 100644 --- a/internal/testutils/plugin.go +++ b/internal/testutils/plugin.go @@ -37,9 +37,10 @@ func NewTestPlugins(plugins ...plugincatalog.BuiltInPlugin) ( pluginCfgs := make([]plugincatalog.PluginConfig, 0, len(plugins)) for _, p := range plugins { pluginCfg := plugincatalog.PluginConfig{ - Name: p.Name(), - Type: p.Type(), - Tags: p.Tags(), + Name: p.Name(), + Type: p.Type(), + Tags: p.Tags(), + YamlConfiguration: "", } values, ok := pluginTags[p.Type()] diff --git a/test/integration/async_test.go b/test/integration/async_test.go new file mode 100644 index 00000000..87e2cb6d --- /dev/null +++ b/test/integration/async_test.go @@ -0,0 +1,200 @@ +package integration_test + +import ( + "bytes" + "encoding/json" + "strings" + "sync" + "testing" + "time" + + "github.com/docker/docker/pkg/stdcopy" + "github.com/openkcm/common-sdk/pkg/commoncfg" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + + plugincatalog "github.com/openkcm/plugin-sdk/pkg/catalog" + + "github.com/openkcm/cmk/internal/config" + "github.com/openkcm/cmk/internal/testutils" + integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" + "github.com/openkcm/cmk/utils/ptr" +) + +func TestAsync(t *testing.T) { + _, tenants, dbCfg := testutils.NewTestDB(t, testutils.TestDBConfig{}) + tenant := tenants[0] + + ctlg := plugincatalog.CreateBuiltInPluginRegistry() + integrationutils.RegisterNoopPlugins(ctlg) + _, pluginCfgs := testutils.NewTestPlugins(ctlg.Retrieve()...) + + _, grpcClient := testutils.NewGRPCSuite(t) + defer grpcClient.Close() + + cfg := &config.Config{ + Certificates: config.Certificates{ + ValidityDays: config.MinCertificateValidityDays, + }, + Database: dbCfg, + Plugins: pluginCfgs, + Services: config.Services{ + Registry: &commoncfg.GRPCClient{ + Enabled: true, + Address: grpcClient.Target(), + SecretRef: &commoncfg.SecretRef{ + Type: commoncfg.InsecureSecretType, + }, + }, + }, + } + + testutils.StartRedis(t, &cfg.Scheduler) + + var wg sync.WaitGroup + var taskCli testcontainers.Container + + wg.Add(2) + go func() { + defer wg.Done() + integrationutils.RunCMKService(t, integrationutils.ServiceConfig{ + Service: integrationutils.TaskWorker, + }, cfg) + }() + go func() { + defer wg.Done() + taskCli = integrationutils.RunCMKService(t, integrationutils.ServiceConfig{ + Service: integrationutils.TaskCLI, + Args: []string{"--sleep"}, + }, cfg) + }() + wg.Wait() + + exitCode, _, err := taskCli.Exec(t.Context(), []string{string(integrationutils.TaskCLI), "invoke", "--task", config.TypeHYOKSync, "--tenants", tenant}) + require.NoError(t, err) + require.Equal(t, 0, exitCode) + + require.NoError(t, err) + exitCode, reader, err := taskCli.Exec(t.Context(), []string{string(integrationutils.TaskCLI), "stats", "--queue", "default", "--pending-tasks"}) + require.NoError(t, err) + require.Equal(t, 0, exitCode) + + // The output seems to be written in stderr instead of stdout + var stdout, stderr bytes.Buffer + _, err = stdcopy.StdCopy(&stdout, &stderr, reader) + require.NoError(t, err) + require.Contains(t, stderr.String(), config.TypeHYOKSync) + + assert.Eventually(t, func() bool { + stdout.Reset() + stderr.Reset() + exitCode, reader, err = taskCli.Exec(t.Context(), []string{string(integrationutils.TaskCLI), "stats", "--queue", "default", "--queue-info"}) + if err != nil { + return false + } + _, err = stdcopy.StdCopy(&stdout, &stderr, reader) + if err != nil { + return false + } + return err == nil && exitCode == 0 && strings.Contains(stderr.String(), `"Processed": 1`) + }, 5*time.Second, 100*time.Millisecond) +} + +// This test checks that children tasks are created for the amount of tenants +// If tenant count is 10, for 1 task there should be at least 11 tasks (original + one per tenant) +func TestAsyncFanout(t *testing.T) { + nTenants := 10 + _, _, dbCfg := testutils.NewTestDB(t, testutils.TestDBConfig{}, testutils.WithGenerateTenants(nTenants)) + + ctlg := plugincatalog.CreateBuiltInPluginRegistry() + integrationutils.RegisterNoopPlugins(ctlg) + _, pluginCfgs := testutils.NewTestPlugins(ctlg.Retrieve()...) + + _, grpcClient := testutils.NewGRPCSuite(t) + defer grpcClient.Close() + + cfg := &config.Config{ + Certificates: config.Certificates{ + ValidityDays: config.MinCertificateValidityDays, + }, + Database: dbCfg, + Plugins: pluginCfgs, + Services: config.Services{ + Registry: &commoncfg.GRPCClient{ + Enabled: true, + Address: grpcClient.Target(), + SecretRef: &commoncfg.SecretRef{ + Type: commoncfg.InsecureSecretType, + }, + }, + }, + Scheduler: config.Scheduler{ + Tasks: []config.Task{ + { + TaskType: config.TypeHYOKSync, + Enabled: ptr.PointTo(true), + Cronspec: "@every 1s", + Retries: ptr.PointTo(3), + TimeOut: 5 * time.Minute, + FanOutTask: &config.FanOutTask{ + Enabled: true, + Retries: ptr.PointTo(0), + TimeOut: 5 * time.Minute, + }, + }, + }, + }, + } + + testutils.StartRedis(t, &cfg.Scheduler) + + var wg sync.WaitGroup + var taskCli testcontainers.Container + + wg.Add(3) + go func() { + defer wg.Done() + integrationutils.RunCMKService(t, integrationutils.ServiceConfig{ + Service: integrationutils.TaskWorker, + }, cfg) + }() + go func() { + defer wg.Done() + integrationutils.RunCMKService(t, integrationutils.ServiceConfig{ + Service: integrationutils.TaskScheduler, + }, cfg) + }() + go func() { + defer wg.Done() + taskCli = integrationutils.RunCMKService(t, integrationutils.ServiceConfig{ + Service: integrationutils.TaskCLI, + Args: []string{"--sleep"}, + }, cfg) + }() + wg.Wait() + + var stdout, stderr bytes.Buffer + assert.Eventually(t, func() bool { + stdout.Reset() + stderr.Reset() + exitCode, reader, err := taskCli.Exec(t.Context(), []string{string(integrationutils.TaskCLI), "stats", "--queue", "default", "--queue-info"}) + if err != nil { + return false + } + _, err = stdcopy.StdCopy(&stdout, &stderr, reader) + if err != nil { + return false + } + + // Extract the Processed value + var stats struct { + Processed int `json:"Processed"` //nolint:tagliatelle + } + if err := json.Unmarshal(stderr.Bytes(), &stats); err != nil { + return false + } + + return exitCode == 0 && stats.Processed > 0 && stats.Processed%nTenants != 0 && stats.Processed > nTenants+1 + }, 15*time.Second, 100*time.Millisecond) +} diff --git a/test/integration/async_test/certrotation_test.go b/test/integration/async_test/certrotation_test.go deleted file mode 100644 index 2e847941..00000000 --- a/test/integration/async_test/certrotation_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package async_test - -import ( - "slices" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/openkcm/cmk/internal/async" - "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/model" - "github.com/openkcm/cmk/internal/repo" - "github.com/openkcm/cmk/internal/repo/sql" - "github.com/openkcm/cmk/internal/testutils" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" - "github.com/openkcm/cmk/utils/ptr" -) - -func TestCertRotation(t *testing.T) { - if integrationutils.CheckAllPluginsMissingFiles(t) { - return - } - - testConfig := getConfig(t, config.Scheduler{ - TaskQueue: integrationutils.MessageService, - Tasks: []config.Task{{ - Cronspec: "@every 1s", - TaskType: config.TypeCertificateTask, - Retries: ptr.PointTo(3), - }}, - }) - SetupTestContainers(t, testConfig) - db, tenants, _ := testutils.NewTestDB(t, testutils.TestDBConfig{}) - - ctx := testutils.CreateCtxWithTenant(tenants[0]) - - r := sql.NewRepository(db) - - setupDatabase(ctx, t, r, false) - - cronWorker, err := async.New(testConfig) - assert.NoError(t, err) - - overrideDatabase(t, cronWorker, db, testConfig) - - // Start worker - go func() { - err := cronWorker.RunWorker(ctx, r) - assert.NoError(t, err) - }() - - // Start scheduler - go func() { - err := cronWorker.RunScheduler() - assert.NoError(t, err) - }() - - time.Sleep(5 * time.Second) - - // Check that new certificates have been created - certsAll := []*model.Certificate{} - err = r.List( - ctx, - model.Certificate{}, - &certsAll, - *repo.NewQuery(), - ) - assert.NoError(t, err) - assert.Greater(t, len(certsAll), 1) - - // Check that all rotated certs have a SupersedesID. Only original doesn't - certsMod := slices.DeleteFunc(certsAll, func(c *model.Certificate) bool { - return c.SupersedesID == nil - }) - assert.Len(t, certsMod, len(certsAll)-1) - - // Check only the head has AutoRotate remaining - certsAuto := []*model.Certificate{} - compositeKey := repo.NewCompositeKey().Where(repo.AutoRotateField, true) - err = r.List( - ctx, - model.Certificate{}, - &certsAuto, - *repo.NewQuery().Where(repo.NewCompositeKeyGroup(compositeKey)), - ) - assert.NoError(t, err) - assert.Len(t, certsAuto, 1) - - err = cronWorker.Shutdown(ctx) - assert.NoError(t, err) -} diff --git a/test/integration/async_test/common_test.go b/test/integration/async_test/common_test.go deleted file mode 100644 index 8cedde55..00000000 --- a/test/integration/async_test/common_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package async_test - -import ( - "context" - "crypto/x509" - "crypto/x509/pkix" - "encoding/json" - "reflect" - "testing" - "time" - "unsafe" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - - multitenancy "github.com/bartventer/gorm-multitenancy/v8" - plugincatalog "github.com/openkcm/plugin-sdk/pkg/catalog" - - "github.com/openkcm/cmk/internal/async" - "github.com/openkcm/cmk/internal/auditor" - "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/constants" - eventprocessor "github.com/openkcm/cmk/internal/event-processor" - "github.com/openkcm/cmk/internal/manager" - "github.com/openkcm/cmk/internal/model" - cmkpluginregistry "github.com/openkcm/cmk/internal/pluginregistry" - "github.com/openkcm/cmk/internal/repo" - "github.com/openkcm/cmk/internal/repo/sql" - "github.com/openkcm/cmk/internal/testutils" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" - "github.com/openkcm/cmk/utils/crypto" -) - -// There is no PKI mock, so credentials for this must be added below -func getConfig(t *testing.T, schCfg config.Scheduler) *config.Config { - t.Helper() - - return &config.Config{ - Database: integrationutils.DB, - Plugins: []plugincatalog.PluginConfig{ - integrationutils.SISPlugin(t), - integrationutils.PKIPlugin(t), - integrationutils.KeystorePlugin(t), - integrationutils.KeystoreProviderPlugin(t), - integrationutils.IDMangementPlugin(t), - }, - Scheduler: schCfg, - Certificates: config.Certificates{ - ValidityDays: 7, - }, - ContextModels: config.ContextModels{ - System: config.System{ - OptionalProperties: map[string]config.SystemProperty{ - SystemRole: {}, - SystemRoleID: {}, - SystemName: {}, - }, - }, - }, - KeystorePool: config.KeystorePool{ - Size: 5, - }, - } -} - -// The tests create a random database since the async app receives -// only the link to the database this would cause -// tests to target the wrong database -func overrideDatabase(t *testing.T, a *async.App, db *multitenancy.DB, cfg *config.Config) { - t.Helper() - - svcRegistry, err := cmkpluginregistry.New(t.Context(), cfg) - assert.NoError(t, err) - - tenancyRepo := sql.NewRepository(db) - - val := reflect.ValueOf(a).Elem() - - sis, err := manager.NewSystemInformationManager(tenancyRepo, svcRegistry, &cfg.ContextModels.System) - assert.NoError(t, err) - - sysCl := val.FieldByName("systemClient") - sysCl = reflect.NewAt(sysCl.Type(), unsafe.Pointer(sysCl.UnsafeAddr())).Elem() - sysCl.Set(reflect.ValueOf(sis)) - - cm := manager.NewCertificateManager(t.Context(), tenancyRepo, svcRegistry, cfg) - - certCl := val.FieldByName("certificateClient") - certCl = reflect.NewAt(certCl.Type(), unsafe.Pointer(certCl.UnsafeAddr())).Elem() - certCl.Set(reflect.ValueOf(cm)) - - eventFactory, err := eventprocessor.NewEventFactory(t.Context(), cfg, tenancyRepo) - assert.NoError(t, err) - - cmkAuditor := auditor.New(t.Context(), cfg) - tc := manager.NewTenantConfigManager(tenancyRepo, svcRegistry, nil) - um := manager.NewUserManager(tenancyRepo, cmkAuditor) - tam := manager.NewTagManager(tenancyRepo) - kc := manager.NewKeyConfigManager(tenancyRepo, cm, um, tam, nil, cfg) - km := manager.NewKeyManager(tenancyRepo, svcRegistry, tc, kc, um, cm, eventFactory, nil) - - hyokCl := val.FieldByName("hyokClient") - hyokCl = reflect.NewAt(hyokCl.Type(), unsafe.Pointer(hyokCl.UnsafeAddr())).Elem() - hyokCl.Set(reflect.ValueOf(km)) - - ksPoolCl := val.FieldByName("keystorePoolClient") - ksPoolCl = reflect.NewAt(ksPoolCl.Type(), unsafe.Pointer(ksPoolCl.UnsafeAddr())).Elem() - ksPoolCl.Set(reflect.ValueOf(km)) - - dbCon := val.FieldByName("dbCon") - dbCon = reflect.NewAt(dbCon.Type(), unsafe.Pointer(dbCon.UnsafeAddr())).Elem() - dbCon.Set(reflect.ValueOf(db)) -} - -func SetupTestContainers(t *testing.T, cfg *config.Config) { - t.Helper() - - testutils.StartRedis(t, &cfg.Scheduler) -} - -func setupDatabase(ctx context.Context, t *testing.T, r repo.Repo, keysEnabled bool) { - t.Helper() - - cert := createTestCertificate(t) - - if keysEnabled { - group, keyConfig, key := createTestKeyEntities() - testutils.CreateTestEntities(ctx, t, r, &key, &group, &keyConfig, &cert) - } else { - testutils.CreateTestEntities(ctx, t, r, &cert) - } -} - -func createTestCertificate(t *testing.T) model.Certificate { - t.Helper() - - privateKey, err := crypto.GeneratePrivateKey(manager.DefaultKeyBitSize) - assert.NoError(t, err) - - certPEM := testutils.CreateCertificatePEM(t, &x509.CertificateRequest{ - Subject: pkix.Name{ - Country: []string{"DE"}, - Organization: []string{"EXAMPLE_O"}, - OrganizationalUnit: []string{"EXAMPLE_OU"}, - Locality: []string{"LOCAL/CMK"}, - CommonName: "myCert", - }, - }, privateKey) - - return model.Certificate{ - ID: uuid.New(), - CommonName: "CommonName", - CertPEM: string(certPEM), - Purpose: model.CertificatePurposeTenantDefault, - ExpirationDate: time.Now().AddDate(0, 0, -8), - } -} - -func createTestKeyEntities() (model.Group, model.KeyConfiguration, model.Key) { - group := model.Group{ - ID: uuid.New(), - Name: "testgroup", - Description: "This is a test group", - Role: "testrole", - } - keyConfig := model.KeyConfiguration{ - ID: uuid.New(), - Name: "hyok", - Description: "This key configuration is used for HANA key store encryption.", - AdminGroupID: group.ID, - CreatorID: uuid.NewString(), - } - nativeID := "arn:aws:kms:eu-west-2:fake:key/fake-key-id" - key := model.Key{ - ID: uuid.New(), - Name: "hyok", - KeyType: constants.KeyTypeHYOK, - Description: "This key is used for HANA key store encryption.", - Algorithm: "AES256", - Provider: "AWS", - Region: "eu-west-2", - NativeID: &nativeID, - KeyConfigurationID: keyConfig.ID, - ManagementAccessData: json.RawMessage(`{"roleArn": "test"}`), - CryptoAccessData: json.RawMessage(`{}`), - IsPrimary: true, - State: "DISABLED", - } - - return group, keyConfig, key -} diff --git a/test/integration/async_test/hyoksync_test.go b/test/integration/async_test/hyoksync_test.go deleted file mode 100644 index 9f6eea29..00000000 --- a/test/integration/async_test/hyoksync_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package async_test - -import ( - "log/slog" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/openkcm/cmk/internal/async" - "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/log" - "github.com/openkcm/cmk/internal/model" - "github.com/openkcm/cmk/internal/repo" - "github.com/openkcm/cmk/internal/repo/sql" - "github.com/openkcm/cmk/internal/testutils" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" -) - -func TestSchedulerHYOKSync(t *testing.T) { - if integrationutils.CheckAllPluginsMissingFiles(t) { - return - } - - testConfig := getConfig(t, config.Scheduler{ - TaskQueue: integrationutils.MessageService, - Tasks: []config.Task{{ - Cronspec: "@every 4s", - TaskType: config.TypeHYOKSync, - }}, - }) - SetupTestContainers(t, testConfig) - - testDB, tenants, _ := testutils.NewTestDB(t, testutils.TestDBConfig{}) - - ctx := testutils.CreateCtxWithTenant(tenants[0]) - - r := sql.NewRepository(testDB) - - setupDatabase(ctx, t, r, true) - - cronWorker, err := async.New(testConfig) - assert.NoError(t, err) - - overrideDatabase(t, cronWorker, testDB, testConfig) - - // Start worker - go func() { - err := cronWorker.RunWorker(ctx, r) - assert.NoError(t, err) - }() - - // Start scheduler - go func() { - err := cronWorker.RunScheduler() - assert.NoError(t, err) - }() - - time.Sleep(5 * time.Second) - // Check that new keys have been created - keys := []*model.Key{} - err = r.List( - ctx, - model.Key{}, - &keys, - *repo.NewQuery(), - ) - assert.NoError(t, err) - assert.NotEmpty(t, keys, "No keys found after sync") - - for _, k := range keys { - log.Info(ctx, "Key found", slog.Any("Key", k)) - assert.Equal(t, "UNKNOWN", k.State, "Key state should be UNKNOWN after sync") - } - - err = cronWorker.Shutdown(ctx) - assert.NoError(t, err) -} diff --git a/test/integration/async_test/keystorepool_test.go b/test/integration/async_test/keystorepool_test.go deleted file mode 100644 index 4cdeabcd..00000000 --- a/test/integration/async_test/keystorepool_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package async_test - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/openkcm/cmk/internal/async" - "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/model" - "github.com/openkcm/cmk/internal/repo" - "github.com/openkcm/cmk/internal/repo/sql" - "github.com/openkcm/cmk/internal/testutils" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" - "github.com/openkcm/cmk/utils/ptr" -) - -func TestKeystorePoolFilling(t *testing.T) { - if integrationutils.CheckAllPluginsMissingFiles(t) { - return - } - - testConfig := getConfig(t, config.Scheduler{ - TaskQueue: integrationutils.MessageService, - Tasks: []config.Task{{ - Cronspec: "@every 1s", - TaskType: config.TypeKeystorePool, - Retries: ptr.PointTo(3), - }}, - }) - SetupTestContainers(t, testConfig) - - db, _, _ := testutils.NewTestDB(t, testutils.TestDBConfig{}) - - ctx := context.Background() - - r := sql.NewRepository(db) - - cronWorker, err := async.New(testConfig) - assert.NoError(t, err) - - overrideDatabase(t, cronWorker, db, testConfig) - - // Start worker - go func() { - err := cronWorker.RunWorker(ctx, r) - assert.NoError(t, err) - }() - - // Start scheduler - go func() { - err := cronWorker.RunScheduler() - assert.NoError(t, err) - }() - - time.Sleep(5 * time.Second) - - count, err := r.Count( - ctx, - &model.Keystore{}, - *repo.NewQuery(), - ) - assert.NoError(t, err) - assert.Equal(t, testConfig.KeystorePool.Size, count) -} diff --git a/test/integration/async_test/systeminformation_test.go b/test/integration/async_test/systeminformation_test.go deleted file mode 100644 index e202a4a8..00000000 --- a/test/integration/async_test/systeminformation_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package async_test - -import ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/openkcm/cmk/internal/async" - "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/model" - "github.com/openkcm/cmk/internal/repo" - "github.com/openkcm/cmk/internal/repo/sql" - "github.com/openkcm/cmk/internal/testutils" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" - "github.com/openkcm/cmk/utils/ptr" -) - -var ( - SystemRole = "roleName" - SystemRoleID = "roleID" - SystemName = "externalName" -) - -func TestSchedulerSystemRefresh(t *testing.T) { - if integrationutils.CheckAllPluginsMissingFiles(t) { - return - } - - testConfig := getConfig(t, config.Scheduler{ - TaskQueue: integrationutils.MessageService, - Tasks: []config.Task{{ - Cronspec: "@every 1s", - TaskType: config.TypeSystemsTask, - Retries: ptr.PointTo(3), - }}, - }) - - SetupTestContainers(t, testConfig) - - testDB, tenants, _ := testutils.NewTestDB(t, testutils.TestDBConfig{}) - ctx := testutils.CreateCtxWithTenant(tenants[0]) - - r := sql.NewRepository(testDB) - - id := 20 - externalID := fmt.Sprintf("External%d", id) - - testutils.CreateTestEntities(ctx, t, r, testutils.NewSystem(func(s *model.System) { - s.Identifier = externalID - })) - - cronWorker, err := async.New(testConfig) - assert.NoError(t, err) - - overrideDatabase(t, cronWorker, testDB, testConfig) - - // Start worker - go func() { - err := cronWorker.RunWorker(t.Context(), r) - assert.NoError(t, err) - }() - - // Start scheduler - go func() { - err := cronWorker.RunScheduler() - assert.NoError(t, err) - }() - - time.Sleep(2 * time.Second) - - sys := &model.System{Identifier: externalID} - ck := repo.NewCompositeKey(). - Where(repo.IdentifierField, externalID) - ok, err := r.First( - ctx, - sys, - *repo.NewQuery(). - Where(repo.NewCompositeKeyGroup(ck)), - ) - assert.NoError(t, err) - assert.True(t, ok) - - sys, err = repo.GetSystemByIDWithProperties(ctx, r, sys.ID, repo.NewQuery()) - assert.NoError(t, err) - assert.Equal( - t, - fmt.Sprintf("ExternalName%d", id), - sys.Properties[SystemName], - ) - assert.Equal( - t, - fmt.Sprintf("roleId%d", id), - sys.Properties[SystemRoleID], - ) - - ok, err = r.Delete(ctx, sys, *repo.NewQuery()) - assert.NoError(t, err) - assert.True(t, ok) - - err = cronWorker.Shutdown(ctx) - assert.NoError(t, err) -} diff --git a/test/integration/async_test/workflowassign_test.go b/test/integration/async_test/workflowassign_test.go deleted file mode 100644 index a378e7d0..00000000 --- a/test/integration/async_test/workflowassign_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package async_test - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - multitenancy "github.com/bartventer/gorm-multitenancy/v8" - - "github.com/openkcm/cmk/internal/async" - "github.com/openkcm/cmk/internal/auditor" - "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/manager" - "github.com/openkcm/cmk/internal/model" - cmkpluginregistry "github.com/openkcm/cmk/internal/pluginregistry" - "github.com/openkcm/cmk/internal/repo" - "github.com/openkcm/cmk/internal/repo/sql" - "github.com/openkcm/cmk/internal/testutils" - wfMechanism "github.com/openkcm/cmk/internal/workflow" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" - "github.com/openkcm/cmk/utils/base62" - ctxUtils "github.com/openkcm/cmk/utils/context" -) - -// The identity service group has been created for this tenant and group name specifically -const ( - tenantID = "tenant1" - groupName = "KeyAdminTest1" -) - -var schemaName, _ = base62.EncodeSchemaNameBase62(tenantID) - -func TestWorkflowApproversAssignment(t *testing.T) { - if integrationutils.CheckAllPluginsMissingFiles(t) { - return - } - - testConfig := getConfig(t, config.Scheduler{ - TaskQueue: integrationutils.MessageService, - }) - SetupTestContainers(t, testConfig) - db, _, _ := testutils.NewTestDB(t, - testutils.TestDBConfig{ - CreateDatabase: true, - }, - testutils.WithInitTenants(model.Tenant{ - ID: tenantID, - TenantModel: multitenancy.TenantModel{ - DomainURL: schemaName + ".example.com", - SchemaName: schemaName, - }, - }), - ) - - ctx := t.Context() - - asyncApp, err := async.New(testConfig) - assert.NoError(t, err) - - overrideDatabase(t, asyncApp, db, testConfig) - - r := sql.NewRepository(db) - - // Start worker - go func(ctx context.Context) { - err := asyncApp.RunWorker(ctx, r) - assert.NoError(t, err) - }(ctx) - - ctx = ctxUtils.CreateTenantContext(ctx, tenantID) - - var ( - adminGroup *model.Group - groups []model.Group - ) - - ck := repo.NewCompositeKey().Where(repo.Name, groupName) - err = r.List(ctx, model.Group{}, &groups, - *repo.NewQuery().Where(repo.NewCompositeKeyGroup(ck))) - assert.NoError(t, err) - - if len(groups) == 0 { - adminGroup = testutils.NewGroup(func(g *model.Group) { - g.Name = groupName - g.IAMIdentifier = model.NewIAMIdentifier(groupName, tenantID) - }) - err = r.Create(ctx, adminGroup) - assert.NoError(t, err) - } else { - adminGroup = &groups[0] - } - - keyConfig := testutils.NewKeyConfig(func(kc *model.KeyConfiguration) { - kc.AdminGroup = *adminGroup - }) - err = r.Create(ctx, keyConfig) - assert.NoError(t, err) - - workflow := testutils.NewWorkflow(func(w *model.Workflow) { - w.ArtifactID = keyConfig.ID - w.ArtifactType = wfMechanism.ArtifactTypeKeyConfiguration.String() - w.ActionType = wfMechanism.ActionTypeDelete.String() - }) - - svcRegistry, err := cmkpluginregistry.New(ctx, testConfig) - tenantConfigManager := manager.NewTenantConfigManager(r, svcRegistry, nil) - cmkAuditor := auditor.New(ctx, testConfig) - userManager := manager.NewUserManager(r, cmkAuditor) - tagManager := manager.NewTagManager(r) - keyConfigManager := manager.NewKeyConfigManager(r, nil, userManager, tagManager, nil, testConfig) - keyManager := manager.NewKeyManager(r, svcRegistry, nil, keyConfigManager, userManager, nil, nil, nil) - systemManager := manager.NewSystemManager(ctx, r, nil, nil, svcRegistry, testConfig, keyConfigManager, userManager) - groupManager := manager.NewGroupManager(r, svcRegistry, userManager) - workflowManager := manager.NewWorkflowManager(r, svcRegistry, keyManager, keyConfigManager, systemManager, - groupManager, userManager, asyncApp.Client(), tenantConfigManager, testConfig) - - assert.NoError(t, err) - - workflow, err = workflowManager.CreateWorkflow(ctx, workflow) - assert.NoError(t, err) - - time.Sleep(5 * time.Second) - - ck = repo.NewCompositeKey().Where("workflow_id", workflow.ID) - count, err := r.Count(ctx, &model.WorkflowApprover{}, - *repo.NewQuery().Where(repo.NewCompositeKeyGroup(ck))) - - assert.NoError(t, err) - assert.GreaterOrEqual(t, count, 1) -} diff --git a/test/integration/identity_management/identity_management_test.go b/test/integration/identity_management/identity_management_test.go deleted file mode 100644 index d956d729..00000000 --- a/test/integration/identity_management/identity_management_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package identity_management_test - -import ( - "fmt" - "log/slog" - "testing" - - "github.com/stretchr/testify/assert" - - plugincatalog "github.com/openkcm/plugin-sdk/pkg/catalog" - - "github.com/openkcm/cmk/internal/config" - cmkpluginregistry "github.com/openkcm/cmk/internal/pluginregistry" - "github.com/openkcm/cmk/internal/pluginregistry/service/api/identitymanagement" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" -) - -func IdentityManagementPlugin(t *testing.T) *cmkpluginregistry.Registry { - t.Helper() - - cat, err := cmkpluginregistry.New(t.Context(), &config.Config{ - Plugins: []plugincatalog.PluginConfig{integrationutils.IDMangementPlugin(t)}, - }) - assert.NoError(t, err) - - return cat -} - -func TestCreateNotificationManager(t *testing.T) { - requiredFiles := []string{ - integrationutils.IdentityManagementConfigPath, - } - if integrationutils.MissingFiles(t, requiredFiles) { - return - } - - catalog := IdentityManagementPlugin(t) - defer catalog.Close() - - idm, err := catalog.IdentityManagement() - assert.NoError(t, err) - - respUsers, err := idm.ListGroupUsers(t.Context(), - &identitymanagement.ListGroupUsersRequest{GroupID: "Test"}) - if respUsers != nil { - slog.Info(fmt.Sprintf("UsersForGroup:%v", respUsers.Users)) - } - - assert.NoError(t, err) - - respGroups, err := idm.ListUserGroups(t.Context(), - &identitymanagement.ListUserGroupsRequest{UserID: "Test"}) - if respGroups != nil { - slog.Info(fmt.Sprintf("GroupsForUser:%v", respGroups.Groups)) - } - - assert.NoError(t, err) -} diff --git a/test/integration/integration_utils/container.go b/test/integration/integration_utils/container.go new file mode 100644 index 00000000..ab193da8 --- /dev/null +++ b/test/integration/integration_utils/container.go @@ -0,0 +1,128 @@ +package integrationutils + +import ( + "bytes" + "io" + "os/exec" + "path" + "path/filepath" + "runtime" + "sync" + "testing" + + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" + "github.com/openkcm/common-sdk/pkg/commoncfg" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "gopkg.in/yaml.v3" + + "github.com/openkcm/cmk/internal/config" + "github.com/openkcm/cmk/internal/constants" + "github.com/openkcm/cmk/internal/testutils" +) + +type Service string + +const ( + ApiServer Service = "/bin/api-server" + TaskWorker Service = "/bin/task-worker" + TaskScheduler Service = "/bin/task-scheduler" + TaskCLI Service = "/bin/task-cli" + TenantManager Service = "/bin/tenant-manager" + TenantManagerCLI Service = "/bin/tenant-manager-cli" + EventReconciler Service = "/bin/event-reconciler" + DBMigrator Service = "/bin/db-migrator" +) + +var buildOnce sync.Once + +type ServiceConfig struct { + Service Service + Name string + Args []string +} + +// RunCMKService builds image and starts a testcontainer with the provided service +// This might take some time if there isn't an image built, but it has caching mechanisms +// Returns the container so you can execute commands or interact with it +func RunCMKService(t *testing.T, svcCfg ServiceConfig, cfg *config.Config) testcontainers.Container { + t.Helper() + + statusPort, err := testutils.GetFreePortString() + require.NoError(t, err) + + // Create copy so that if this is run in parallel there is no race condition + // as the cfg is changed + cfgCopy := *cfg + cfgCopy.Status = commoncfg.Status{ + Enabled: true, + Address: ":" + statusPort, + } + + cfgBytes, err := yaml.Marshal(&cfgCopy) + require.NoError(t, err) + + ctx := t.Context() + + req := testcontainers.ContainerRequest{ + Name: svcCfg.Name, + Image: BuildCMKImage(t), + Entrypoint: []string{string(svcCfg.Service)}, + Cmd: svcCfg.Args, + Files: []testcontainers.ContainerFile{ + { + ContainerFilePath: path.Join(constants.DefaultConfigPath1, "/config.yaml"), + Reader: bytes.NewReader(cfgBytes), + FileMode: 0o644, + }, + }, + HostConfigModifier: func(hc *container.HostConfig) { + hc.NetworkMode = network.NetworkHost + }, + } + + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + require.NoError(t, err) + + testutils.WaitForServer(t, cfgCopy.Status.Address) + + state, err := container.State(ctx) + require.NoError(t, err) + + if !state.Running { + logs, err := container.Logs(ctx) + if err == nil { + logBytes, _ := io.ReadAll(logs) + t.Logf("Container logs:\n%s", string(logBytes)) + logs.Close() + } + require.True(t, state.Running, "container should be running") + } + + return container +} + +// BuildCMKImage runs makefile step docker-dev-build to build the image +// This is only needed as there is a bug with testcontainers and docker buildkit +func BuildCMKImage(t *testing.T) string { + t.Helper() + + buildOnce.Do(func() { + _, filename, _, _ := runtime.Caller(0) + + baseDir := filepath.Dir(filename) + projectRoot, err := filepath.Abs(baseDir + "../../../../") + require.NoError(t, err) + + cmd := exec.Command("make", "docker-dev-build") + cmd.Dir = projectRoot + _, err = cmd.Output() + require.NoError(t, err) + }) + + return "cmk-api-server-dev:latest" +} diff --git a/test/integration/integration_utils/os.go b/test/integration/integration_utils/os.go index 01af435e..c501af02 100644 --- a/test/integration/integration_utils/os.go +++ b/test/integration/integration_utils/os.go @@ -6,20 +6,6 @@ import ( "testing" ) -func CheckAllPluginsMissingFiles(t *testing.T) bool { - t.Helper() - - return MissingFiles(t, - []string{ - PKIUAAConfigPath, - PKIServiceConfigPath, - NotificationUAAConfigPath, - NotificationEndpointsPath, - IdentityManagementConfigPath, - }, - ) -} - func MissingFiles(t *testing.T, requiredFiles []string) bool { t.Helper() diff --git a/test/integration/integration_utils/plugin.go b/test/integration/integration_utils/plugin.go index 38aaaa2f..df53524f 100644 --- a/test/integration/integration_utils/plugin.go +++ b/test/integration/integration_utils/plugin.go @@ -1,340 +1,21 @@ package integrationutils import ( - "encoding/json" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/openkcm/common-sdk/pkg/commoncfg" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" - - plugincatalog "github.com/openkcm/plugin-sdk/pkg/catalog" - certificate_issuerv1 "github.com/openkcm/plugin-sdk/proto/plugin/certificate_issuer/v1" - idmangv1 "github.com/openkcm/plugin-sdk/proto/plugin/identity_management/v1" - keystoremanv1 "github.com/openkcm/plugin-sdk/proto/plugin/keystore/management/v1" - keystoreopv1 "github.com/openkcm/plugin-sdk/proto/plugin/keystore/operations/v1" - notificationv1 "github.com/openkcm/plugin-sdk/proto/plugin/notification/v1" - systeminformationv1 "github.com/openkcm/plugin-sdk/proto/plugin/systeminformation/v1" -) - -const ( - PKIUAAConfigPath = "../../env/secret/cert-issuer-plugins/uaa.json" - PKIServiceConfigPath = "../../env/secret/cert-issuer-plugins/service.json" - NotificationUAAConfigPath = "../../env/secret/notification-plugins/uaa.json" - NotificationEndpointsPath = "../../env/secret/notification-plugins/endpoints.json" - IdentityManagementConfigPath = "../../env/secret/identity-management/scim.json" + "github.com/openkcm/plugin-sdk/pkg/catalog" + + certificateissuernoop "github.com/openkcm/cmk/internal/plugins/certificate-issuer/noop" + identitymanagementnoop "github.com/openkcm/cmk/internal/plugins/identity-management/noop" + keymanagementnoop "github.com/openkcm/cmk/internal/plugins/key-management/noop" + keystoremanagementnoop "github.com/openkcm/cmk/internal/plugins/keystore-management/noop" + notificationnoop "github.com/openkcm/cmk/internal/plugins/notification/noop" + systeminformationnoop "github.com/openkcm/cmk/internal/plugins/system-information/noop" ) -var KeystorePlugin = func(t *testing.T) plugincatalog.PluginConfig { - t.Helper() - - return plugincatalog.PluginConfig{ - Name: "AWS", - Type: keystoreopv1.Type, - LogLevel: "debug", - Path: keystorePath(), - Tags: []string{ - "default_keystore", - }, - } -} - -var keystorePath = func() string { - _, filename, _, _ := runtime.Caller(0) - baseDir := filepath.Dir(filename) - return filepath.Join(baseDir, "../../keystore-plugins/bin/keystoreop/aws") -} - -var PKIPlugin = func(t *testing.T) plugincatalog.PluginConfig { - t.Helper() - - return plugincatalog.PluginConfig{ - Name: "CERT_ISSUER", - Type: certificate_issuerv1.Type, - LogLevel: "debug", - YamlConfiguration: pkiYaml(t), - Path: pkiPath(), - } -} - -var pkiYaml = func(t *testing.T) string { - t.Helper() - cfg := struct { - UAA commoncfg.SourceRef `yaml:"uaa"` - CertificateService commoncfg.SourceRef `yaml:"certificateservice"` //nolint:tagliatelle - }{ - UAA: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: PKIUAAConfigPath, - Format: commoncfg.BinaryFileFormat, - }, - }, - CertificateService: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: PKIServiceConfigPath, - Format: commoncfg.BinaryFileFormat, - }, - }, - } - - bytes, _ := yaml.Marshal(cfg) - return string(bytes) -} - -var pkiPath = func() string { - _, filename, _, _ := runtime.Caller(0) - baseDir := filepath.Dir(filename) - return filepath.Join(baseDir, "../../cert-issuer-plugins/bin/cert-issuer") -} - -var notificationPath = func() string { - _, filename, _, _ := runtime.Caller(0) - baseDir := filepath.Dir(filename) - - return filepath.Join(baseDir, "../../notification-plugins/bin/notification") -} - -var notificationYaml = func(t *testing.T) string { - t.Helper() - - cfg := struct { - UAA commoncfg.SourceRef `yaml:"uaa"` - Endpoints commoncfg.SourceRef `yaml:"endpoints"` - }{ - UAA: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: NotificationUAAConfigPath, - Format: commoncfg.BinaryFileFormat, - }, - }, - Endpoints: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: NotificationEndpointsPath, - Format: commoncfg.BinaryFileFormat, - }, - }, - } - - bytes, _ := yaml.Marshal(cfg) - return string(bytes) -} - -var SISPlugin = func(t *testing.T) plugincatalog.PluginConfig { - t.Helper() - - return plugincatalog.PluginConfig{ - Name: "SYSINFO", - Type: systeminformationv1.Type, - Path: sisPath(), - LogLevel: "debug", - YamlConfiguration: sisYaml(t), - } -} - -var NotificationPlugin = func(t *testing.T) plugincatalog.PluginConfig { - t.Helper() - - return plugincatalog.PluginConfig{ - Name: "NOTIFICATION", - Type: notificationv1.Type, - LogLevel: "debug", - YamlConfiguration: notificationYaml(t), - Path: notificationPath(), - } -} - -var sisYaml = func(t *testing.T) string { - t.Helper() - - _, filename, _, _ := runtime.Caller(0) - baseDir := filepath.Dir(filename) - - ststemInfoCertPath := filepath.Join(baseDir, "../../sis-plugins/mocks/cld/local") - - certPath := filepath.Join(ststemInfoCertPath, "mtls_client_cert.pem") - keyPath := filepath.Join(ststemInfoCertPath, "mtls_client_key.pem") - - key, err := os.ReadFile(keyPath) - assert.NoError(t, err) - certificate, err := os.ReadFile(certPath) - assert.NoError(t, err) - - uaa := struct { - ID string `json:"clientid"` //nolint:tagliatelle - Certificate string `json:"certificate"` //nolint:tagliatelle - Key string `json:"key"` - CertURL string `json:"certurl"` //nolint:tagliatelle - CredentialType string `json:"credential-type"` //nolint:tagliatelle - }{ - ID: "validClientId", - Certificate: string(certificate), - Key: string(key), - CertURL: "https://localhost:8001", - CredentialType: "x509", - } - - uaaBytes, err := json.Marshal(uaa) - assert.NoError(t, err) - - cfg := struct { - CLDISEndpoint commoncfg.SourceRef `yaml:"cldisEndpoint"` - UAA commoncfg.SourceRef `yaml:"uaa"` - }{ - UAA: commoncfg.SourceRef{ - Source: commoncfg.EmbeddedSourceValue, - Value: string(uaaBytes), - }, - CLDISEndpoint: commoncfg.SourceRef{ - Source: commoncfg.EmbeddedSourceValue, - Value: "https://localhost:8001/cldPublic/v1", - }, - } - - bytes, _ := yaml.Marshal(cfg) - return string(bytes) -} - -var sisPath = func() string { - _, filename, _, _ := runtime.Caller(0) - baseDir := filepath.Dir(filename) - - return filepath.Join(baseDir, "../../sis-plugins/bin/uli") -} - -var KeystoreProviderPlugin = func(t *testing.T) plugincatalog.PluginConfig { - t.Helper() - - return plugincatalog.PluginConfig{ - Name: "AWS", - Type: keystoremanv1.Type, - Path: keystoreProviderPath(), - LogLevel: "debug", - } -} - -var keystoreProviderPath = func() string { - _, filename, _, _ := runtime.Caller(0) - baseDir := filepath.Dir(filename) - - return filepath.Join(baseDir, - "../../internal/testutils/testplugins/keystoreman/testpluginbinary") -} - -var IDMangementPlugin = func(t *testing.T) plugincatalog.PluginConfig { - t.Helper() - - return plugincatalog.PluginConfig{ - Name: "IDENTITY_MANAGEMENT", - Type: idmangv1.Type, - Path: IDManagementPath(), - LogLevel: "debug", - YamlConfiguration: IDManagementYaml(t), - } -} - -var IDManagementYaml = func(t *testing.T) string { - t.Helper() - - type Params struct { - GroupAttribute commoncfg.SourceRef `yaml:"groupAttribute"` - UserAttribute commoncfg.SourceRef `yaml:"userAttribute"` - GroupMembersAttribute commoncfg.SourceRef `yaml:"groupMembersAttribute"` - ListMethod commoncfg.SourceRef `yaml:"listMethod"` - AllowSearchUsersByGroup commoncfg.SourceRef `yaml:"allowSearchUsersByGroup"` - } - - cfg := struct { - Host commoncfg.SourceRef `yaml:"host"` - Auth commoncfg.SecretRef `yaml:"auth"` - Params Params `yaml:"params"` - }{ - Host: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: IdentityManagementConfigPath, - Format: commoncfg.JSONFileFormat, - JSONPath: "$['host']", - }, - }, - Auth: commoncfg.SecretRef{ - Type: commoncfg.MTLSSecretType, - MTLS: commoncfg.MTLS{ - Cert: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: IdentityManagementConfigPath, - Format: commoncfg.JSONFileFormat, - JSONPath: "$['auth']['cert']", - }, - }, - CertKey: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: IdentityManagementConfigPath, - Format: commoncfg.JSONFileFormat, - JSONPath: "$['auth']['certKey']", - }, - }, - }, - }, - Params: Params{ - GroupAttribute: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: IdentityManagementConfigPath, - Format: commoncfg.JSONFileFormat, - JSONPath: "$['params']['groupAttribute']", - }, - }, - UserAttribute: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: IdentityManagementConfigPath, - Format: commoncfg.JSONFileFormat, - JSONPath: "$['params']['userAttribute']", - }, - }, - GroupMembersAttribute: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: IdentityManagementConfigPath, - Format: commoncfg.JSONFileFormat, - JSONPath: "$['params']['groupMembersAttribute']", - }, - }, - ListMethod: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: IdentityManagementConfigPath, - Format: commoncfg.JSONFileFormat, - JSONPath: "$['params']['listMethod']", - }, - }, - AllowSearchUsersByGroup: commoncfg.SourceRef{ - Source: commoncfg.FileSourceValue, - File: commoncfg.CredentialFile{ - Path: IdentityManagementConfigPath, - Format: commoncfg.JSONFileFormat, - JSONPath: "$['params']['allowSearchUsersByGroup']", - }, - }, - }, - } - - bytes, _ := yaml.Marshal(cfg) - - return string(bytes) -} - -var IDManagementPath = func() string { - _, filename, _, _ := runtime.Caller(0) - baseDir := filepath.Dir(filename) - return filepath.Join(baseDir, "../../identity-management-plugins/bin/scim") +func RegisterNoopPlugins(registry catalog.BuiltInPluginRegistry) { + identitymanagementnoop.Register(registry) + notificationnoop.Register(registry) + systeminformationnoop.Register(registry) + certificateissuernoop.Register(registry) + keystoremanagementnoop.Register(registry) + keymanagementnoop.Register(registry) } diff --git a/test/integration/integration_utils/plugin_test.go b/test/integration/integration_utils/plugin_test.go deleted file mode 100644 index cf558212..00000000 --- a/test/integration/integration_utils/plugin_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package integrationutils_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" -) - -func TestPlugins(t *testing.T) { - t.Run("Should create SIS", func(t *testing.T) { - p := integrationutils.SISPlugin(t) - assert.Equal(t, "SYSINFO", p.Name) - }) - - t.Run("Should create PKI", func(t *testing.T) { - p := integrationutils.PKIPlugin(t) - assert.Equal(t, "CERT_ISSUER", p.Name) - }) - - t.Run("Should create AWS", func(t *testing.T) { - p := integrationutils.KeystorePlugin(t) - assert.Equal(t, "AWS", p.Name) - }) - - t.Run("Should create Notifications", func(t *testing.T) { - p := integrationutils.NotificationPlugin(t) - assert.Equal(t, "NOTIFICATION", p.Name) - }) -} diff --git a/test/integration/notification/notification_test.go b/test/integration/notification/notification_test.go deleted file mode 100644 index bc8dcbb0..00000000 --- a/test/integration/notification/notification_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package notification_test - -import ( - "path/filepath" - "runtime" - "testing" - - "github.com/stretchr/testify/assert" - - plugincatalog "github.com/openkcm/plugin-sdk/pkg/catalog" - - "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/notifier/client" - cmkpluginregistry "github.com/openkcm/cmk/internal/pluginregistry" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" -) - -var ansPath string - -func init() { - _, filename, _, _ := runtime.Caller(0) //nolint: dogsled - baseDir := filepath.Dir(filename) - - ansPath = filepath.Join(baseDir, "../../notification-plugins/bin/notification") -} - -func NotificationPlugin(t *testing.T) *cmkpluginregistry.Registry { - t.Helper() - plugins, err := cmkpluginregistry.New(t.Context(), &config.Config{ - Plugins: []plugincatalog.PluginConfig{ - integrationutils.NotificationPlugin(t), - }, - }) - assert.NoError(t, err) - - return plugins -} - -func TestCreateNotificationManager(t *testing.T) { - requiredFiles := []string{ - integrationutils.NotificationEndpointsPath, - integrationutils.NotificationUAAConfigPath, - } - - if integrationutils.MissingFiles(t, requiredFiles) { - return - } - - pluginCatalog := NotificationPlugin(t) - defer pluginCatalog.Close() - - m, err := client.New(t.Context(), pluginCatalog) - assert.NoError(t, err) - - err = m.CreateNotification(t.Context(), client.Data{ - Recipients: []string{"TestRecipient"}, - Subject: "Test Notification", - Body: "This was a test notification", - }) - assert.NoError(t, err) -} diff --git a/test/integration/systeminformation_tests/systeminformation_test.go b/test/integration/systeminformation_tests/systeminformation_test.go deleted file mode 100644 index d4a2cc5e..00000000 --- a/test/integration/systeminformation_tests/systeminformation_test.go +++ /dev/null @@ -1,169 +0,0 @@ -//go:build !unit - -package systeminformation_test - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - - plugincatalog "github.com/openkcm/plugin-sdk/pkg/catalog" - - "github.com/openkcm/cmk/internal/config" - "github.com/openkcm/cmk/internal/manager" - "github.com/openkcm/cmk/internal/model" - cmkpluginregistry "github.com/openkcm/cmk/internal/pluginregistry" - "github.com/openkcm/cmk/internal/repo" - "github.com/openkcm/cmk/internal/repo/sql" - "github.com/openkcm/cmk/internal/testutils" - integrationutils "github.com/openkcm/cmk/test/integration/integration_utils" -) - -const ( - SystemRole = "roleName" - SystemRoleID = "roleID" - SystemName = "externalName" -) - -type SystemInformationSuite struct { - suite.Suite -} - -func (s *SystemInformationSuite) TestUpdateSystems() { - t := s.T() - db, tenants, _ := testutils.NewTestDB(t, testutils.TestDBConfig{}) - ctx := testutils.CreateCtxWithTenant(tenants[0]) - - repository := sql.NewRepository(db) - - const startID = 20 - - const endID = 31 - for i := startID; i < endID; i++ { - sys := testutils.NewSystem(func(s *model.System) { - s.Identifier = fmt.Sprintf("Identifier%d", i) - }) - testutils.CreateTestEntities(ctx, t, repository, sys) - } - - clg, err := cmkpluginregistry.New( - t.Context(), - &config.Config{ - Plugins: []plugincatalog.PluginConfig{ - integrationutils.SISPlugin(t), - }, - }, - ) - assert.NoError(t, err) - - defer clg.Close() - - assert.NoError(t, err) - - si, err := manager.NewSystemInformationManager(repository, clg, &config.System{ - OptionalProperties: map[string]config.SystemProperty{ - SystemRole: {}, - SystemRoleID: {}, - SystemName: {}, - }, - }) - assert.NoError(t, err) - assert.NotNil(t, si) - - err = si.UpdateSystems(ctx) - assert.NoError(t, err) - - for i := startID; i < endID; i++ { - externalID := fmt.Sprintf("Identifier%d", i) - sys := &model.System{Identifier: externalID} - ck := repo.NewCompositeKey(). - Where(repo.IdentifierField, externalID) - ok, err := repository.First( - ctx, - sys, - *repo.NewQuery(). - Where(repo.NewCompositeKeyGroup(ck)), - ) - assert.NoError(t, err) - sys, err = repo.GetSystemByIDWithProperties(ctx, repository, sys.ID, repo.NewQuery()) - assert.NoError(t, err) - assert.True(t, ok) - assert.Equal(t, sys.Properties[SystemName], - fmt.Sprintf("ExternalName%d", i)) - assert.Equal(t, sys.Properties[SystemRoleID], - fmt.Sprintf("roleId%d", i)) - - ok, err = repository.Delete(ctx, sys, *repo.NewQuery()) - assert.NoError(t, err) - assert.True(t, ok) - } -} - -func (s *SystemInformationSuite) TestUpdateSystemByExternalID() { - t := s.T() - db, tenants, _ := testutils.NewTestDB(t, testutils.TestDBConfig{}) - ctx := testutils.CreateCtxWithTenant(tenants[0]) - - repository := sql.NewRepository(db) - - systemNumber := 7 - identifier := fmt.Sprintf("Identifier%d", systemNumber) - system := testutils.NewSystem(func(s *model.System) { - s.Identifier = identifier - }) - testutils.CreateTestEntities(ctx, t, repository, system) - - defer func() { - ck := repo.NewCompositeKey(). - Where(repo.IdentifierField, identifier) - ok, err := repository.Delete( - ctx, - system, - *repo.NewQuery(). - Where(repo.NewCompositeKeyGroup(ck)), - ) - assert.NoError(t, err) - assert.True(t, ok) - }() - - clg, err := cmkpluginregistry.New( - t.Context(), - &config.Config{Plugins: []plugincatalog.PluginConfig{integrationutils.SISPlugin(t)}}, - ) - assert.NoError(t, err) - - defer clg.Close() - - assert.NoError(t, err) - - si, err := manager.NewSystemInformationManager(repository, clg, &config.System{ - OptionalProperties: map[string]config.SystemProperty{ - SystemRole: {}, - SystemRoleID: {}, - SystemName: {}, - }, - }) - assert.NoError(t, err) - assert.NotNil(t, si) - - err = si.UpdateSystemByExternalID(ctx, identifier) - assert.NoError(t, err) - - sys := &model.System{Identifier: identifier} - ok, err := repository.First(ctx, sys, *repo.NewQuery()) - assert.NoError(t, err) - assert.True(t, ok) - - sys, err = repo.GetSystemByIDWithProperties(ctx, repository, sys.ID, repo.NewQuery()) - assert.NoError(t, err) - assert.Equal(t, sys.Properties[SystemName], - fmt.Sprintf("ExternalName%d", systemNumber)) - assert.Equal(t, sys.Properties[SystemRoleID], - fmt.Sprintf("roleId%d", systemNumber)) -} - -func TestSystemInformationTest(t *testing.T) { - suite.Run(t, new(SystemInformationSuite)) -} diff --git a/utils/cli/cli.go b/utils/cli/cli.go index 7b0eb062..bb427a80 100644 --- a/utils/cli/cli.go +++ b/utils/cli/cli.go @@ -7,6 +7,9 @@ import ( "syscall" "github.com/spf13/cobra" + + "github.com/openkcm/cmk/internal/config" + statusserver "github.com/openkcm/cmk/utils/status_server" ) // NewRootCmdWithInfinitySleep creates a new root cobra command with infinite sleep option. @@ -14,6 +17,7 @@ import ( // This is useful for containers so the CLI can be invoked by exec commands while keeping the container running. func NewRootCmdWithInfinitySleep( ctx context.Context, + cfg *config.Config, use string, shortDesc string, longDesc string, @@ -31,6 +35,7 @@ func NewRootCmdWithInfinitySleep( Run: func(cmd *cobra.Command, _ []string) { if sleep { + statusserver.StartStatusServer(ctx, cfg) infiniteRun(cmd) } }, diff --git a/utils/cli/cli_test.go b/utils/cli/cli_test.go index 74da7fa4..f0490a7e 100644 --- a/utils/cli/cli_test.go +++ b/utils/cli/cli_test.go @@ -9,12 +9,13 @@ import ( "github.com/stretchr/testify/assert" + "github.com/openkcm/cmk/internal/config" "github.com/openkcm/cmk/utils/cli" ) func TestRootCmdWithSleepFlagEnabledSleepsIndefinitely(t *testing.T) { ctx := context.Background() - cmd := cli.NewRootCmdWithInfinitySleep(ctx, "test", "short description", "long description") + cmd := cli.NewRootCmdWithInfinitySleep(ctx, &config.Config{}, "test", "short description", "long description") var out bytes.Buffer cmd.SetOut(&out) @@ -37,7 +38,7 @@ func TestRootCmdWithSleepFlagEnabledSleepsIndefinitely(t *testing.T) { func TestRootCmdWithoutSleepFlagExecutesWithoutSleeping(t *testing.T) { ctx := context.Background() - cmd := cli.NewRootCmdWithInfinitySleep(ctx, "test", "short description", "long description") + cmd := cli.NewRootCmdWithInfinitySleep(ctx, &config.Config{}, "test", "short description", "long description") var out bytes.Buffer cmd.SetOut(&out) @@ -52,7 +53,7 @@ func TestRootCmdWithoutSleepFlagExecutesWithoutSleeping(t *testing.T) { func TestRootCmdHandlesSignalGracefully(t *testing.T) { ctx := context.Background() - cmd := cli.NewRootCmdWithInfinitySleep(ctx, "test", "short description", "long description") + cmd := cli.NewRootCmdWithInfinitySleep(ctx, &config.Config{}, "test", "short description", "long description") var out bytes.Buffer cmd.SetOut(&out) diff --git a/utils/status_server/status.go b/utils/status_server/status.go new file mode 100644 index 00000000..46aa104b --- /dev/null +++ b/utils/status_server/status.go @@ -0,0 +1,37 @@ +package statusserver + +import ( + "context" + "syscall" + + "github.com/openkcm/common-sdk/pkg/health" + "github.com/openkcm/common-sdk/pkg/status" + + "github.com/openkcm/cmk/internal/config" + "github.com/openkcm/cmk/internal/constants" + "github.com/openkcm/cmk/internal/db/dsn" + "github.com/openkcm/cmk/internal/log" +) + +func StartStatusServer(ctx context.Context, cfg *config.Config, opts ...health.Option) { + dsnFromConfig, err := dsn.FromDBConfig(cfg.Database) + if err != nil { + log.Error(ctx, "Could not load DSN from database config", err) + } + + healthOptions := append([]health.Option{ + health.WithDatabaseChecker( + constants.DBDriver, + dsnFromConfig, + ), + }, opts...) + + go func() { + err := status.Serve(ctx, &cfg.BaseConfig, healthOptions...) + if err != nil { + log.Error(ctx, "Failure on the status server", err) + + _ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) + } + }() +}